feat(spec)!: object-map, object-gantt and object-tree type navigation by reference, and one pin enumerates every z.unknown() member of ComponentPropsMap (#21464, stage 1) - #21502
Conversation
…by reference, and one pin enumerates every z.unknown() member of ComponentPropsMap (#21464) Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
…ation narrowing (#21464) Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
…avigation members (#21464) Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
…mponent-props-unknown-closeout
… stage reasons, re-measured on the merged head (#21464) Completes the wip commit 527957d. The D3 entry and the step-18 rationale fragment stand as written (order 64 is still one above the highest after merging origin/main 100c394). The changeset's measured census is re-anchored to the merged head, with its controls, and its summary now matches the ledger: 107 listed members, 28 renderer-read, 27 staged and one held for a ruling. The pin's object-metric and objectui-held stage reasons now name the real by-reference candidates. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 138 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0002894704ae3ffeac0bc2aa0dc1e50b77b5f2f9 && git checkout 0002894704ae3ffeac0bc2aa0dc1e50b77b5f2f9
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 85e29b8858b55a6dab39f8b7f004290743a25da8 c222ab563b65319ac713f766ad58b088ba46890a && git checkout -B drift-repro 85e29b8858b55a6dab39f8b7f004290743a25da8 && git merge --no-ff c222ab563b65319ac713f766ad58b088ba46890a
node scripts/docs-audit/affected-docs.mjs --json 85e29b8858b55a6dab39f8b7f004290743a25da8 |
Contract reviewServed-tier: Inputs read: card #21464 (body; comments Gate verdicts on this head, as they stood at this read (2026-10-03, about 00:55Z): 28 success · 2 failure — ① Derived judgments1. Writer census — SOUND; no measured writer refused. Re-derived read-only at the pin: every distinct authored 2. The enumeration pin — RIGHT, with one recorded blind spot. §1 fails on an unlisted 3. Staging under the claim's size rule — SOUND; 4. The held item, object-kanban 5. The retirement kit — three of four RIGHT, one STALE.
② Semver levelRIGHT, and identical in shape to the precedent. ③ Boundary flags
Implemented-by: VERDICT: FAIL Failed item (one): the regenerated Generated by Claude Code |
…mponent-props-unknown-closeout
…ree, so the typed navigation cells take main's optionality face (#21464) The merge of origin/main 85e29b8 brought fd96a84, which renders every defaulted member of a shared def with `?` in a Type-cell shape summary. The page was regenerated from the merged sources with `check:generated --fix` (gen:docs, the only stale artifact): the three new navigation cells on ObjectGanttProps, ObjectMapProps and ObjectTreeProps now read `{ mode?: …; preventNavigation?: …; openNewTab?: …; size?: …; … }`, like the grid, kanban, calendar and timeline cells. The branch's delta against main is unchanged: the same six files, +637/-12. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Re-review after record Gate verdicts on this head, as they stood at this read (2026-10-03, 01:38Z): 42 check-runs, all completed — 38 success · 0 failure · 0 in progress · 4 skipped ( ① Derived judgments1. The failed item,
2. The delta from 3. Step-18 rationale fragment order 64 — still unique. 4. The generated registry region — current after the merge. The 37-line region added to 5. Carried by reference from record ② Semver levelRIGHT — carried. The changeset blob ( ③ Boundary flags
Implemented-by: VERDICT: PASS The one item record Generated by Claude Code |
Part of #21464
Clause-②: yes (narrowing)
What this does
This is the first stage of the
ComponentPropsMapz.unknown()close-out (triage5961300594, claim5962381903). It does two things.navigationis typed onobject-map,object-ganttandobject-tree. Each row tookz.unknown(), although each renderer hands the member to the console's shared navigation hook, which readsnavigation.modeand falls back topage. Any value passed the component-props gate, and an off-shape one opened the record page in silence. Each row now takes the list view'sNavigationConfigSchemaby reference. That is the blockobject-grid,object-kanban,object-calendarandobject-timelinealready take.packages/spec/src/ui/component-props-unknown-members.pin.test.tswalks everyComponentPropsMaprow, into arrays, record values, union arms, lazy schemas and catchalls. It fails on anyz.unknown()member that is neither typed nor listed with a recorded reason. It also fails on a ledger line whose member is no longerz.unknown(), so the ledger can only shrink as later stages type members.Why
Part of, not a closing line: the census found more than one reviewable PR's worth (the dispatch's size rule, A7). There are 31 renderer-read members. The precedent, PR #21463, spent about 1,100 lines on 7 members. The other 28 are recorded in the pin asstaged, each with its reader at the pin and a named stage. The staging proposal below is in the report for a ruling. #21464 remains open for those stages.navigation, measured at the.objectui-shapin89cad75d55object-mapz.unknown()NavigationConfigSchema, by referenceplugin-map/src/ObjectMap.tsx:1189hands it touseNavigationOverlay, which readsnavigation?.mode ?? 'page'(react/src/hooks/useNavigationOverlay.ts:364)object-ganttz.unknown()NavigationConfigSchema, by referenceplugin-gantt/src/ObjectGantt.tsx:1960readsschema.navigation ?? { mode: 'drawer' }and hands it to the hook (:2025-2026)object-treez.unknown()NavigationConfigSchema, by referenceplugin-tree/src/ObjectTree.tsx:1054hands it to the hook (:1046)objectui's own node types and Zod mirrors already declare this member as
ViewNavigationConfigor the spec'sNavigationConfigSchema(types/src/objectql.ts,types/src/zod/objectql.zod.ts). The spec row now agrees with its sibling.No measured writer is refused (A3).
navigationon one of these blocks. The one co-mention writes app navigation arrays. Control:objectNameis found in 4 of the 5 files per row.object-map/object-gantt/object-tree. Control:objectNameis found in 55 / 70 / 40 of them. They contain 77navigation:lines. Every distinct authored literal parses on all three rows, 36 of 36:{ mode }with each of the seven modes, plussize: 'lg'andopenNewTab.navigation: 'anything'is a both-faces parity probe.navigation: 'drawer'sits under a@ts-expect-error.{ mode: 'sideways' }is an expectednavigation.modeissue.{ mode: 'drawer', width: '600px' }example belongs toobject-view(plugin-view/src/ObjectView.tsx:1060), not these rows.The census (A1), whole
The census covers 107
z.unknown()members across 56 rows, on the merged head. It is unchanged from the base minus the three this PR types. Writer counts are file co-mentions (a file naming the type and authoringKEY:), with a control key. They are an upper bound, not a value census: each later stage runs its own value-level census first.31 members a renderer reads with a fixed shape. Citations are objectui at
89cad75d55. Writers are "os N/M (control)" for objectstack and "ou N/M (control)" for objectui.navigationNavigationConfigSchemaaggregateplugin-dashboard/src/ObjectMetricWidget.tsx:250,.field/.functionat :404aggregate(chart.zod.ts:644)object-metrictrendObjectMetricWidget.tsx:254(typed :176)object-metricdrillDownObjectMetricWidget.tsx:265(ObjectMetricDrillDownConfig:218)drillDown(chart.zod.ts:663)object-metriccompareToObjectMetricWidget.tsx:267(CompareToConfig:241)compareTo(dashboard.zod.ts:1205)object-metricfields[]plugin-form/src/ObjectForm.tsx:961object-formcustomFieldsObjectForm.tsx:755, :1180object-formsections[]ObjectForm.tsx:364, :1518FormViewSchema.sectionsobject-formcontentLayoutplugin-form/src/ModalForm.tsx:854('simple'or'tabbed', :151)object-formsubmitBehaviorObjectForm.tsx:1312-1313FormViewSchema.submitBehaviorobject-formnavigateOnSuccessObjectForm.tsx:1373, :1412object-formmobileObjectForm.tsx:1857object-formsections[],fields[]plugin-form/src/MasterDetailForm.tsx:1692-1693, into the parent formobject-formobject-formcolumns[]plugin-grid/src/ObjectGrid.tsx:2158(normalizeColumns, string orListColumn)ListViewSchema.columnslist-familyfields[]ObjectGrid.tsx:1946list-familyselectionObjectGrid.tsx:4799-4810(.type)ListViewSchema.selectionlist-familyselectableObjectGrid.tsx:4813-4815list-familyrowActions[]ObjectGrid.tsx:1834-1835(string[])ListViewSchema.rowActionslist-familybulkActions[],batchActions[]ObjectGrid.tsx:4763(batchActions ?? bulkActions)ListViewSchema.bulkActionslist-familycolumns[]plugin-kanban/src/KanbanBoardCore.tsx:95list-familycalendarplugin-calendar/src/ObjectCalendar.tsx:296-297(ObjectCalendarConfig)ListViewSchema.calendarlist-familymarkers[]plugin-gantt/src/ObjectGantt.tsx:2497(GanttMarker)objectui-helditems[]plugin-timeline/src/ObjectTimeline.tsx:587objectui-heldmappingObjectTimeline.tsx:551, :576-579 (TimelineMappingSchema)objectui-heldactions[]{}components/src/renderers/action/action-group.tsx:303(UIActionSchema[])UIActionSchemais objectui's interface)objectui-heldactions[]{}components/src/renderers/action/action-menu.tsx:339(UIActionSchema[])objectui-heldconditionalFormattingKanbanBoardCore.tsx:114, evaluated atKanbanImpl.tsx:179ListViewSchema.conditionalFormatting76 members with a recorded reason, grouped:
page:tabs/page:accordionitems[].children[],page:cardchildren[]/footer[],page:footer/page:sidebar/page:sectionchildren[].action:buttonandaction:iconparams,bodyExtra,bodyShape,operation,patch,toast,resultDialogandonSuccess. Each.describe()says "forwarded to the runner" (§2 checks this).staticData[],data[],data.items[],initialValues{}/initialData{}, and the grid bulk action'spatch{}/params[].default.source(14), the HTTP data source'sparams{}/body(16), the inline JSON Schema document (4), and the bulk-action option entry, deliberately open (1).3 one-off reasons:
element:definition-listitems[].descriptionis shown as-is (components/src/renderers/basic/data-list.tsx:68,toText).object-gridpagination.*is a deliberately openz.looseObject, whose named members are typed.ai:chat_windowcontext{}has no reader: objectui registers no renderer for the row (components/src/renderers/placeholders.tsx:109-113). Per A2 it is an open question, not a verdict (see the report).Held for a ruling:
object-kanbanconditionalFormatting(A3)The list view's own
conditionalFormatting, the shape the grid took in PR #21463, refuses both rule dialects that objectui's own kanban fixtures author:{ field, operator, value, backgroundColor }(plugin-kanban/src/__tests__/ObjectKanban.structuredMembersReachTheirSinks-8313.test.tsx:463) is refused withunrecognized_keysandinvalid_type@style.{ condition, backgroundColor }(:479) is refused withinvalid_type@styleandunrecognized_keys[backgroundColor].types/src/__tests__/kanban-conditional-formatting.test.ts:29-52asserts that its kanban schema accepts both, and a mix of the two.So the narrowing is not shipped. It is recorded as
staged('held-for-decision', …)and reported with options.The pin, red then green (A4), and its ablation
vitest run --project local … component-props-unknown-members.pin.test.tsreportsTests 51 passed (51).object-map'snavigationtoz.unknown(), with no ledger line, throughnode scripts/ablation-replace.mjsin WRAP mode under the verify lock. It landed:anchor x1 → x0,replace x0 → x1, blob58b6e27fbc → 7e3b411604.Tests 8 failed | 43 passed (51).["object-map navigation"]against an expected[].expected 108 to be 107.blob after restore 58b6e27fbc… == blob at HEAD 58b6e27fbc…,git diff HEAD empty. The rerun was green again:Tests 51 passed (51)../component.zodfrom source, so no build sits between the mutation and the run.Gates, on
c222ab563bFix round after review
5963877733: mergedorigin/main85e29b8858and regeneratedcontent/docs/references/ui/component.mdx(the optionality facefd96a8473drenders). Onc222ab563b,check:generated,check:migration-registry, spectest, spectypecheckandcheck:doc-authoringall exit 0. The other five files are byte-unchanged sinced3431deed3, where the readings below were taken.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths; merge base100c394f6f, 6 changed paths) derived 114 commands. Every exit code was written to disk before anything read it. All 114 exit 0, reconciled with--ran.PREREQUISITE NOT MET(exit 3), becausedist/predated the final pin edit. They were rerun afterpnpm --filter @objectstack/spec buildat the final head, together with the rest of the spec family.pnpm --filter @objectstack/spec test:Test Files 602 passed | 1 skipped (603),Tests 17832 passed.pnpm --filter @objectstack/spec typecheck: exit 0, withcheck:test-typecheck: OK — … test layer compiles under packages/spec/tsconfig.test.json. The packagetsconfig.jsonexcludes**/*.test.ts, so it is the test layer that covers the pin.scripts/step18-rationale-merge.test.ts(--project repo):Tests 9 passed (9).check:generated:✓ All 15 generated artifacts are up to date.check:migration-registry:✓ src/migrations/registry.ts is current (353 semantic, 246 retired-key, 217 retired-def).check:liveness,check:strictness-ledgerandcheck:authorable-surfaceare green.authorable-surface/ui.jsonand the strictness counts did not move.component.mdxis regenerated.check:doc-authoring:✓ doc authoring guard: 17287 customer-facing string(s) … clean.check:nul-bytes:check-nul-bytes: OK (… no raw ASCII control bytes).Semver:
@objectstack/specminor, BREAKING banner,Clause-②: yes (narrowing), and the ADR-0087 markerregistered ui-object-map-gantt-tree-navigation-typed.check-adr-0087-registration:✓ 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.check-changeset-no-major --base origin/main:✓ This diff introduces no major bump.--eventpayload, is quoted in the report.The retirement kit
ui-object-map-gantt-tree-navigation-typed, a sibling ofui-object-grid-row-members-typed.migrations/registry.tsandcontent/docs/references/ui/component.mdx.propertiesis not parsed on the save or load path, and an off-shape value has no rewrite that honours what the author meant.Acceptance notes
ui/component.zod.ts,component.mdx,migrations/registry.ts,authorable-surface/ui.jsonor the pin. Seven of the eight open PRs were read in full. The bot Version Packages PR was read only to its first file page, because the proxy refused its pagination.NavigationConfig.ai:chat_windowrow has zero authored writers in either repository. objectui excludes it from the palette and treats it as a tombstone. Whether it retires is in the report's open questions, not in this PR.Resume
A container restart stopped the first run after it pushed
c7bb62ae6f,527957de24and9a8037abbe, and before it opened a PR. This run made the following changes:origin/mainthroughos-regen-merge.sh(no regeneration was owed);Generated by Claude Code