Key record source icon/label off the real source type - #224
Key record source icon/label off the real source type#224grimicorn-agent wants to merge 5 commits into
Conversation
Join sources into the record list/detail read queries and expose sourceType on the record resource, then drive sourceTypeIcon/ formatSourceLabel off it instead of a type prefix that the free-text records.source column never carried. Closes #153
Independent code review trailRan the independent reviewer (Opus) for 3 rounds. Findings and actions: Round 1 (6 findings)
Round 2 (4 findings)
Round 3 (6 findings)
Deliberate decision — write-path All 1756 unit/snapshot tests pass; |
…eal-type # Conflicts: # server/api/records/index.get.ts
… detail join and page column set
Independent code review trail (re-run after merging main)Ran the independent reviewer (Opus) for 3 rounds on the post-merge diff. The prior review trail (before the main merge) still stands; this covers what the merge + fresh review surfaced. Round 1 (5 findings)
Round 2 (3 live findings)
Round 3 (4 findings)
All 1852 unit/snapshot tests pass; |
What changed and why
Records rendered the same
zapicon and a garbled label for every source.sourceTypeIcon/formatSourceLabelkeyed off atype/-style prefix inrecords.source, but that column stores the source's free-text display name (e.g. "My Zapier hook"), never a type prefix — so the type detection matched nothing and every record fell through to the default.The real source type lives on
sources.type, reachable only by joiningrecords.sourceId → sources.uuid, and it was not exposed on the record read contract. This PR exposes it and drives the display off it.Server
leftJoinonsourcesto the record list (GET /api/records) and detail (GET /api/records/:uuid) queries, selectingsources.typeas a newsourceTypeattribute. The join is scopedAND sources.userId = userIdso it can never surface another tenant's source type, even if a future write path setssourceIdwithout the ownership check.recordSerializernow emitssourceTypeon the record resource.sourceTypeinpublic/openapi.json.Client
sourceTypeIcon(sourceType)maps the real type to an icon via an exhaustiveRecord<SourceType, string>(compiler flags a missing icon if a source type is added).formatSourceLabel(source, sourceType)shows the source's display name first (so two same-type sources like "Prod deploys" / "Staging deploys" stay distinguishable — the type is already conveyed by the icon), falling back to the resolved type name, then "unknown".record.attributes.sourceType.Key decisions
sourceTypeon the record resource (server join + contract + client type) provided no related PR existed; none did.ON DELETE SET NULL) keep their stored name instead of collapsing to "unknown".sourceTypeis null on create/patch responses. Those endpoints don't joinsources; the field is documented (OpenAPI + code comment) as a write-receipt null — re-read via GET for the resolved type. Populating write responses is left as a follow-up (see below).Where to view
/inbox— the source column (icon + label) per record row./inbox.Tests
sourceTypeIconper type + fallback;formatSourceLabelname-first, type fallback, legacy-type, and unknown paths.sourceType; serializer emits it.sourceType, label from the name; reverting the wiring now fails).Closes #153
Follow-up suggestions
Mirror sourceType in markpost-cli— the CLI hand-mirrors the record contract; addsourceTypeto its record type so consumers see the resolved type (suggested: P3, effort: S, evidence: cross-repo contract server/utils/response.ts)Populate sourceType on create/patch responses— join/re-read sources on POST/PATCH/bulk-PATCH so the write responses carry the resolved type instead of a documented null (suggested: P3, effort: M, evidence: server/api/records/index.post.ts, [uuid].patch.ts, index.patch.ts)Consolidate source-type→icon maps— SourceCard.vue's ICON_BY_TYPE and useRecords.ts's SOURCE_TYPE_ICONS duplicate the type→icon mapping; extract one shared map to prevent drift (suggested: P3, effort: S, evidence: app/components/SourceCard.vue, app/composables/useRecords.ts)