The non-blocking findings from the #95 review, collected so they aren't lost when that PR merges. None of them block; #160 covers the two that did.
Three of these are the labelRules half of guards that statusMappings and priorityMappings already got. They fit naturally alongside the MAPPING_CONFIG_KEY + shared parseMappingConfig() consolidation already deferred out of #95 — doing them together is probably less work than doing them separately.
1. labelRules is not validated on read
readPersistedConfig (apps/web/src/app/api/sync/mappings/route.ts) validates statusMappings and priorityMappings per section, but passes labelRules through raw. A malformed value is served as-is, is omitted from invalidSections, and reaches LabelRulesPanel, which does ruleList.map(...) on it.
Same class as the read-path validation added for the other two sections — a row written by older code or hand-edited in the DB reaches the client unvalidated.
2. isValidLabelRulesShape is missing the empty-array rejection its sibling has
isValidMappingShape rejects { linear: [] } because loadStatusMap treats an empty array as "nothing persisted" and falls back to defaults — so saving it would show an empty list in the UI while the worker kept using defaults.
isValidLabelRulesShape has no equivalent check, so labelRules: { linear: [] } saves happily and loadLabelMapper then ignores it. That's the exact UI/engine divergence the sibling check exists to block.
3. A PUT that omits labelRules wipes previously persisted label rules
The upsert replaces the whole row, so omitting the key drops it. Same silent wipe the labelRules: {} rejection was added to prevent, reachable through a different door.
4. The priority label field is never rendered
#95 split the display text out of the persisted key — keys are now the raw adapter values ('0'–'4') with the human text in a display-only label. That fixed the real bug, but PriorityMappingPanel still renders externalPriority and PriorityMappingEntry has no label, so the priority tab now shows bare 0–4. The suggested fix on #95 was a display-only field the editor renders — the persistence half landed, the rendering half didn't.
5. handleSave never clears provenance
apps/web/src/app/sync/mappings/page.tsx — after a successful save, the "no saved mapping configuration is in effect" notice stays on screen next to "Mappings saved successfully."
The non-blocking findings from the #95 review, collected so they aren't lost when that PR merges. None of them block; #160 covers the two that did.
Three of these are the labelRules half of guards that
statusMappingsandpriorityMappingsalready got. They fit naturally alongside theMAPPING_CONFIG_KEY+ sharedparseMappingConfig()consolidation already deferred out of #95 — doing them together is probably less work than doing them separately.1.
labelRulesis not validated on readreadPersistedConfig(apps/web/src/app/api/sync/mappings/route.ts) validatesstatusMappingsandpriorityMappingsper section, but passeslabelRulesthrough raw. A malformed value is served as-is, is omitted frominvalidSections, and reachesLabelRulesPanel, which doesruleList.map(...)on it.Same class as the read-path validation added for the other two sections — a row written by older code or hand-edited in the DB reaches the client unvalidated.
2.
isValidLabelRulesShapeis missing the empty-array rejection its sibling hasisValidMappingShaperejects{ linear: [] }becauseloadStatusMaptreats an empty array as "nothing persisted" and falls back to defaults — so saving it would show an empty list in the UI while the worker kept using defaults.isValidLabelRulesShapehas no equivalent check, solabelRules: { linear: [] }saves happily andloadLabelMapperthen ignores it. That's the exact UI/engine divergence the sibling check exists to block.3. A PUT that omits
labelRuleswipes previously persisted label rulesThe upsert replaces the whole row, so omitting the key drops it. Same silent wipe the
labelRules: {}rejection was added to prevent, reachable through a different door.4. The priority
labelfield is never rendered#95 split the display text out of the persisted key — keys are now the raw adapter values (
'0'–'4') with the human text in a display-onlylabel. That fixed the real bug, butPriorityMappingPanelstill rendersexternalPriorityandPriorityMappingEntryhas nolabel, so the priority tab now shows bare0–4. The suggested fix on #95 was a display-only field the editor renders — the persistence half landed, the rendering half didn't.5.
handleSavenever clearsprovenanceapps/web/src/app/sync/mappings/page.tsx— after a successful save, the "no saved mapping configuration is in effect" notice stays on screen next to "Mappings saved successfully."