spec(object-timeline): items describe names objectui as the item-shape owner - #20531
objectstack-fleet[bot] merged 4 commits into
Conversation
…the item shape" ObjectTimelinePropsSchema.items stays z.array(z.unknown()) — no schema-shape change. Its describe now names the actual owner: each element is objectui's declared timeline element, @object-ui/types's TimelineFeedItem (feed, variant absent / vertical / horizontal) or TimelineGanttItem (a gantt row, variant: 'gantt'), selected by the node's own variant, whose strict authoring face refuses a mismatched arm and any undeclared key. Regenerated content/docs/references/ui/component.mdx via gen:schema && gen:docs. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
…meline-items-element-owner
📓 Docs Drift Check1 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 — 137 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 97e416b8cb82c2649b3a47b61eb9fcef09072d47 && git checkout 97e416b8cb82c2649b3a47b61eb9fcef09072d47
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 03b19d9cfdb7f0811dc27398b51b44d332a2ed88 891830acdf29765700ac43c36a38aad9348543fd && git checkout -B drift-repro 03b19d9cfdb7f0811dc27398b51b44d332a2ed88 && git merge --no-ff 891830acdf29765700ac43c36a38aad9348543fd
node scripts/docs-audit/affected-docs.mjs --json 03b19d9cfdb7f0811dc27398b51b44d332a2ed88 |
Round-2 correction: the previous describe claimed objectui's strict
authoring face refuses a mismatched arm and undeclared keys for
object-timeline. Measured false at the objectui pin
dd3f7e1be3561d63267d7162f3fc0ac52e72834d: objectui's zod authoring face
(AnyComponentSchema / StrictAnyComponentSchema, a z.discriminatedUnion
on `type`) has no object-timeline arm at all. A live
StrictAnyComponentSchema.safeParse({ type: 'object-timeline', items: [...] })
against objectui's built types at this pin returns invalid_union / "No
matching discriminator" on both faces -- not an arm/key judgment. The
arm-selection and unrecognized_keys mechanism lives on the sibling
`timeline` zod node (TimelineSchema's superRefine), which object-timeline
types against only in TypeScript (ObjectTimeline.tsx's
schema: TimelineSchema & { ... }), never at runtime.
items stays z.array(z.unknown()) -- no schema-shape change. The describe's
second sentence now says only the measured-true part: each element is
objectui's declared timeline element, TimelineFeedItem or TimelineGanttItem,
the arm the node's variant selects. Regenerated
content/docs/references/ui/component.mdx via gen:schema && gen:docs.
Corrected the changeset prose the same way and recorded the correction in it.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
… prose The "Correction (round 2): ..." paragraph referenced round 1's wording, which never shipped and never left this branch -- the published CHANGELOG has no reader who saw it, so a paragraph explaining "the first cut of this describe" tells them about something that never happened to them. That record already lives in the PR body's verification section. The changeset keeps only its first two paragraphs (the actual change, and why it is documentation-only). Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
Contract reviewServed-tier: Inputs, and nothing else: card #20283 (body; triage grade 5859517446, unlock 5879567964, claim 5880073803, os-dev-reports 5881158750 / 5881621744 / 5881658978); PR #20531 (body, its one comment 5881140085, file list, and the net diff against ① Derived judgmentsNet diff: three files, +9 / -2 — one
Gate coverage — the 32 check-runs on the head, read once at judgment:
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
… commits that decided them (stage 4) (objectstack-ai#20548) Part of objectstack-ai#20234 Clause-②: no ## What changed This is stage 4 of the staged sweep: the `data/` remainder. It covers the six `packages/spec/src/data/` files stage 3 (PR objectstack-ai#20533, landed `03b19d9cfd`) left out because an open PR held them, and nothing else. They are `object.zod.ts`, `filter-logic-conformance.ts`, `object.form.ts`, `data-engine.zod.ts`, `data-engine.test.ts` and `hook.form.ts`. Later stages cover the other areas, so this PR says `Part of`. The census below measured all six. Three of them carry comment or docblock sites that cite a tracker number answering 404. `data-engine.zod.ts`, `data-engine.test.ts` and `hook.form.ts` carry none, so they are not in the diff. Every such site has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123). That is **19 sites on 19 lines in 3 files, covering 9 numbers**. Each rewritten line now cites the commit in `origin/main` history that decided what the line describes, and it says in its own words what that commit decided. Where a PR number was already on the line (`PR objectstack-ai#13529`), it stays beside the commit as the link. No ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/` records the decision behind any of the 9 numbers: a search for each number, with and without `#`, finds nothing there. So every anchor is a commit: **9 distinct shas**. Stage 3 had already read these commits and recorded them in PR objectstack-ai#20533's body. They were not copied from there. Each one was re-read against the current line it anchors: its own message or diff names the number it replaces, and it made the change the line describes. `object.zod.ts` and `filter-logic-conformance.ts` moved on `main` after stage 3 read them (PRs objectstack-ai#20521 and objectstack-ai#20523). Each site was therefore re-read at this base, `03b19d9cfd`. Only comments changed. Every source file keeps its line count (20 lines out, 20 in, over 3 files), so no line citation into these files moves. One of the 20 lines held no dead citation: `filter-logic-conformance.ts:249`, the first half of a sentence reflowed onto `:250`. No code token moves (see the guard below). **No tracker number is added.** Every tracker number on an added line was already in the hunk it replaces. `PR objectstack-ai#13529` stands on three added lines, and on the three removed lines of the same hunks. It is the link beside commit `9dac1ae01`, which stage 3 recorded the same way. No reference page under `content/docs/references/` moved: none of the rewritten docblocks projects into one (`check:docs` at the head: `226 generated files in sync`). The PR adds one `patch` changeset for `@objectstack/spec` (see Changeset below). ## Census: the six files, before and after **Instrument.** This is the instrument of stages 1 to 3. It sends REST `GET /repos/objectstack-ai/objectstack/issues/N` without following redirects, for every distinct number cited in `packages/spec/src/data`. The population is: - the citation gate's own exported `CITATION_RE` and `NON_CITATION_HEADS`, kept when the qualifier is none, `objectstack`, `objectstack-ai/objectstack`, `framework`, `pre-` or `post-`; - widened case-insensitively to `Pre-`, `POST-` and `Framework`, as in stage 3; - N of 100 or more, excluding `summon` heads. Each site is classified by the TypeScript parser as a line comment, a docblock, a block comment or a string. Two cross-checks close the population. First, a raw `#N` count in each of the six files equals the census rows plus the cross-repo rows in five files. In the other two it is one higher, and the extra is a second number after a slash inside a string (`objectstack-ai#5322/objectstack-ai#5134` in a `note`, `objectstack-ai#6262/objectstack-ai#6433` in a test title). Both answer 200. Second, no spelled citation (`issue N`, `PR N`, `card N`) occurs in any of the six. **Controls.** The lit controls were `objectstack-ai#16862`, `objectstack-ai#16847` and `objectstack-ai#17698`. The dead controls were `objectstack-ai#16714`, `objectstack-ai#16715` and `objectstack-ai#16697`. They were probed at the start, after every 100 numbers and at the end: 24 of 24 lit (200) and 24 of 24 dead (404) over 8 checkpoints in the base run, and 21 of 21 lit and 21 of 21 dead over 7 checkpoints in the head run. | reading | tree | numbers probed | 200 | 404 | 301 or other | dead sites, all of `data/` | dead sites, the six files | lines | files | numbers | |---|---|---|---|---|---|---|---|---|---|---| | before | base `03b19d9cfd`, probed 2026-09-29T01:11:59Z to 01:15:49Z | 601 | 572 | 29 | 0 | **77** | 19 | 19 | 3 | 9 | | after | head `53c9070dfd`, probed 2026-09-29T01:25:55Z to 01:29:35Z | 597 | 572 | 25 | 0 | **58** | 0 | 0 | 0 | 0 | The head probe found no number newly dead since the base probe: the same 572 numbers answer 200. The base reading of 77 equals stage 3's after reading at `96fd49caa2`. **Per file.** Cited sites here are every in-repo citation the population reads, live or dead. | file | cited sites (base) | dead sites before | by class | dead sites after | |---|---|---|---|---| | `object.zod.ts` | 120 | 15 | 8 docblock, 7 line comment | 0 | | `filter-logic-conformance.ts` | 97 | 3 | 2 docblock, 1 line comment | 0 | | `object.form.ts` | 31 | 1 | 1 line comment | 0 | | `data-engine.zod.ts` | 48 | 0 | | 0 | | `data-engine.test.ts` | 29 | 0 | | 0 | | `hook.form.ts` | 0 | 0 | | 0 | None of the 19 sites is a string, so this stage leaves no string token behind. ## Per-number table | number | sites / lines | anchor: what it decided | |---|---|---| | `objectstack-ai#8772` | 4 / 4, `object.zod.ts:2718`, `:2731`, `:2744`, `:2910` | `75b7c240a`: Direction 2 of the 2026-08-16 maintainer ruling. `ObjectSchema.create()` forces `required: true` on a `master_detail` reference under `controlled_by_parent` and refuses an explicit `required: false`. Raw parse stays tolerant, and runtime tolerance is the ruling's other half. Its changeset records the measurement that only the security gate closed that shape while the declaration surface accepted it (`:2731`). ADR-0055 stays cited beside it. It is the same anchor stage 3 gave `object.test.ts` | | `objectstack-ai#10165` | 2 / 2, `object.zod.ts:818`, `:1036` | `801296050`: `ttl.onlyWhen` with the canonical null predicate (maintainer ruling 2026-08-20, option A). One shared `onlyWhen` union, and both of `retention.onlyWhen`'s conflicts mirrored. Its diff wrote both `[objectstack-ai#10165]` blocks | | `objectstack-ai#10347` | 3 / 3, `object.zod.ts:1006`, `:1042`, `:1049` | `530c1df65`: the Archiver honours a declared `ttl`. It selects by the ttl cutoff on `ttl.field` when `ttl` is declared, and by `created_at` / `archive.after` otherwise (maintainer ruling 2026-08-20) | | `objectstack-ai#10527` | 1 / 1, `object.zod.ts:1005` | `5649efbf9`: refuses a diverging retention + ttl + archive triple at parse time. Its diff wrote this very paragraph | | `objectstack-ai#11195` | 1 / 1, `object.zod.ts:1791` | `b37231883`: `UserActionsConfigSchema` adopts `group` / `hideFields` / `rowColor` (the "last three" the line names) | | `objectstack-ai#11408` | 1 / 1, `object.zod.ts:2189` | `f11fc61c5`: declares `editMode` on the object document (maintainer ruling 2026-08-24, the `objectstack-ai#10144` declare-or-rule-out family, which stays cited) | | `objectstack-ai#13608` | 3 / 3, `object.zod.ts:2317`, `:2354`, `:2366` | `fc9ba76a5`: `publicSharing.eligibility` is held at redemption, not only at mint, fail-closed, with the undifferentiated `null` refusal. Its changeset heads with objectstack-ai#13608. It is the same anchor stage 1 gave `contracts/share-link-service.ts` | | `objectstack-ai#13195` | 3 / 3, `filter-logic-conformance.ts:190`, `:250`, `:525` | `9dac1ae01`, PR objectstack-ai#13529's squash commit, which stays as the link: `$exists` means has-a-value on driver-memory's live mingo path, its analytics face and driver-mongodb's `translateFilter` (the "last three key-presence exits") | | `objectstack-ai#12868` | 1 / 1, `object.form.ts:256` | `c459da6bc`: narrows the per-option `default` key out of the form-view options vocabulary, which offered a key nothing on that surface read. Commit `e808890958`, which wrote this line, names objectstack-ai#12868 as the same offer-vs-door class | The shas were checked at the base and again at `origin/main` `288611e3e5`. Every one matches exactly one commit (`git rev-parse --disambiguate`, count 1). Every one is an ancestor (`git merge-base --is-ancestor`, exit 0 for 9 of 9). The control leg `e9584681a4` also exits 0, and the repository is not shallow. For each commit, a grep of its own message or diff finds the number it replaces. Seven of the nine name it in the message. `fc9ba76a5` names it in its diff (20 lines, including its changeset heading), and so does `c459da6bc` (8 lines, including its changeset heading). Wordings to check, each true of its commit: - `object.zod.ts:2731` now reads 「closes that shape, and commit 75b7c24 records that the declaration and the enforcement disagree」. The measurement was the card's. The commit's changeset records it: "only the security gate closed that shape while the declaration surface accepted it". - `object.zod.ts:2189` reads 「Declared here by commit f11fc61's maintainer ruling」, and `:2744` reads 「the other half of commit 75b7c24's ruling」. This is stage 3's wording for the same relation (`object.test.ts`, 「the other half of commit 75b7c24's ruling」): the commit that landed the ruling and quotes it. - `object.zod.ts:1049` reads 「That is the whole of what [commit 530c1df] changed here」. Commit `52db1d1f2a` wrote the paragraph. `530c1df65` is the change it describes. ## Mechanical guard: no code token moves The check compares leaf tokens with comments stripped, base `03b19d9cfd` against head `53c9070dfd`. It uses the TypeScript parser's leaf tokens (TypeScript from the head's lockfile), so template literals are scanned in context, and it excludes JSDoc nodes. It ran over all 3 touched `.ts` files. It is the stage-3 instrument, unchanged. - Real run: 13,624 base tokens (object.zod.ts 8,774, object.form.ts 3,226, filter-logic-conformance.ts 1,624), **0 files with a token change** (exit 0). - Comment-insertion control (`object.form.ts`): 0 files changed, as expected (exit 0). - Positive control (a declaration inserted into `object.zod.ts`): 1 file reads DIFFER at token 1629 (exit 1). - Positive control (one digit changed inside the `objectstack-ai#5322/objectstack-ai#5134` `note` string in `filter-logic-conformance.ts`): 1 file reads DIFFER at token 889 (exit 1). Line balance: `object.zod.ts` +15 / -15, `filter-logic-conformance.ts` +4 / -4, `object.form.ts` +1 / -1. Line counts are equal at base and head: 3,240, 621 and 751. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/spec` is included. It says only that the provenance comments were re-anchored. `Clause-②: no`: no export, key, value or type moves (the guard above). Measured on the head's built package: `object.zod.ts` is `src/**/*.zod.ts`, which `files[]` ships verbatim. The rewritten comments also reach `dist`: - `9dac1ae01` appears in `dist/data/index.d.ts` (the `filter-logic-conformance.ts` docblock) and in 4 bundled `.js` files; - `fc9ba76a5`, `f11fc61c5` and `b37231883` each appear in 22 bundled `.js` files, and `c459da6bc` in 12; - the positive control, the pre-existing `object.zod.ts` sentence 「Fail-CLOSED at both points」, appears in 11 bundled `.js` files. ## Gates (head `53c9070dfd`) - **Citation judging pass, run as CI runs it:** `pnpm check:issue-citations && node scripts/check-issue-citations.mjs` exits 0. The self-test passes 73 cases in 7 batteries. The live run judged 6 citations across 3 files: 3 resolve (`objectstack-ai#9138` twice, `objectstack-ai#11410`) and 3 resolve as a pull request (`objectstack-ai#13529`, the link). - **Doc authoring:** `pnpm check:doc-authoring` exits 0. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at the head derived 79 families, and all 79 exit 0. `--ran` reports 79 run, 0 NOT MEASURED, 0 unrun, and exits 0. A full `turbo run build` of `./packages/*` ran first, under the shared verify lock: 71 of 71 tasks, VERDICT command-exit 0. So no gate met an unbuilt prerequisite. - `pnpm --filter @objectstack/spec run check:generated`: under the lock against that build, `All 15 generated artifacts are up to date`, VERDICT command-exit 0. - **Tests and typecheck:** - `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/data` under the lock: Test Files 107 passed (107), Tests 3527 passed, 1 todo (3528), VERDICT command-exit 0. It covers every test in `data/`, among them `object.test.ts`, which reads these schemas. - The 13 spec suites outside `src/data` that read the touched files' source text or pin their line numbers, under the lock: Test Files 13 passed (13), Tests 544 passed (544). They are stage 3's 12 (`scripts/{file-description,root-index,skill-map-guards,strictness-ledger}.test.ts`, `src/api/api-entry-graph.pin.test.ts`, `src/contracts/scoped-context.test.ts`, `src/shared/{alias-integrity,evaluated-slot-population,retired-key-migrate-sentence}.test.ts`, `src/system/constants/platform-object-names.test.ts`, `src/type-alias-convention.pin.test.ts`, `src/ui/dashboard.test.ts`) plus `src/shared/union-author-message-pins.test.ts`, which pins `data/object.zod.ts:855`. - `pnpm --filter @objectstack/spec typecheck` under the lock exits 0, including `check:test-typecheck` (53 files, 251 errors, 138 pinned signatures held). - **Lint, as a proven narrowing at the head:** `eslint --no-inline-config --format json` over the 3 touched `.ts` files gives 3 files, 0 errors and 0 warnings. All 3 are in eslint's own population (`isPathIgnored` is false for each). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, which its own line 328 states), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. ## Acceptance notes - **Base.** The branch forked from `03b19d9cfd`, stage 3's landing. `origin/main` then moved two commits (`05077d4c26`, PR objectstack-ai#20532, and `288611e3e5`, PR objectstack-ai#20536), and neither touches `data/`. `dispatch-gates` flagged its derivation as stale because `scripts/regen-artifacts.mjs` had moved, so `origin/main` was merged in (`53c9070dfd`, a clean merge with no driver-deferred path) before the gates ran. The PR's delta against `origin/main` is exactly its 4 files. `origin/main` has since moved two more commits: `7e36a3cd7c` (PR objectstack-ai#20531) and `ba5927f714` (PR objectstack-ai#20460). Neither touches `data/` or anything the gate derivation reads, and a re-derivation prints the same 79 commands. A no-driver `merge-tree` of the head onto `ba5927f714`, from a bare shared clone, exits 0. So there is no second merge. - **Open PRs, re-read at 2026-09-29T02:01Z:** 9 open PRs, and none touches any of the six files. The `data/` files open PRs touch are objectstack-ai#20458's `analytics*` files, objectstack-ai#20504's `driver/turso.*`, and objectstack-ai#20545's `filter-number-comparand-declared-type.*`, which is disjoint. Since the claim, PR objectstack-ai#20460 has landed (`ba5927f714`) without touching `filter-subtree-provenance.ts`. That file's 3 dead sites are outside this claim's fence, so they are left for a later stage. - **The rung.** Two anchored changes also have ADR-0087 entries in `packages/spec/src/migrations`: `cbp-master-detail-required-forced` for objectstack-ai#8772, and `form-view-option-default-retired` for objectstack-ai#12868. The second entry's own header names commit `c459da6bc`. This PR takes the commit rung, as stages 1 to 3 did. The D3 id is the more durable in-repo record, if the ruling's first rung is later read to include those entries. - **What stays in `data/` after this stage: 58 dead sites.** - **12 comment sites in files other open work still holds.** `analytics.zod.ts`, `analytics-strictness-batchd.test.ts` and `analytics-date-range-two-bound-window.test.ts` hold 5 (objectstack-ai#20300, PR objectstack-ai#20458). `driver/turso.zod.ts` and `driver/turso.test.ts` hold 4 (objectstack-ai#20437, PR objectstack-ai#20504). `filter-subtree-provenance.ts` holds 3. It was held by objectstack-ai#20367 and is now free (see above). - **3 comment sites stage 3 left on purpose.** They are the test-read `[objectstack-ai#6259]` marker at `api-derivation.ts:163`, the test comment at `api-derivation.test.ts:232` that names it, and `field.zod.ts:370`, whose `objectstack-ai#6111` is objectui's number. - **43 string sites**, left as tokens: 41 test strings (2 of them in the held analytics and turso test files) and the 2 exported `AGGREGATION_CASES` note strings in `aggregation-conformance.ts` (`:398`, `:407`, objectstack-ai#11065), which objectstack-ai#20489's claim holds. - **Outside `data/`,** the card's other remaining items are unchanged: the migrations and ui areas, the `liveness/**` notes, the `why` strings, the `PROVENANCE_WAIVERS` reason, and `rest-server.zod.ts`. - **The citation gate's reach.** It defers `packages/**/*.test.ts`. No test file is touched here, so all 3 touched files are in its judging population. --- _Generated by [Claude Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #20283
Clause-②: no
What changed
ObjectTimelinePropsSchema.items(packages/spec/src/ui/component.zod.ts) staysz.array(z.unknown())— no schema-shape change. Its.describe()no longer says "the author owns the item shape". It now names the actual owner: each element is objectui's declared timeline element,@object-ui/types'sTimelineFeedItem(variantabsent /vertical/horizontal) orTimelineGanttItem(variant: 'gantt'), the arm this node's ownvariantselects.content/docs/references/ui/component.mdxwas regenerated viagen:schema && gen:docs— never hand-edited..changeset/20283-object-timeline-items-element-owner-describe.md—@objectstack/specpatch.Route taken (per the card, not re-opened)
itemskeepsz.array(z.unknown()); the value contract stays owned by objectui. Declaring the arms here would fork a second vocabulary.mappingandobject-gantt.markershold the same VALUE posture unchanged.Verification record
Premise re-measure (required before touching code). At
.objectui-shadd3f7e1be3561d63267d7162f3fc0ac52e72834d(the pin this branch built against),TimelineFeedItemandTimelineGanttItemare declared as plain TypeScript interfaces in objectui'spackages/types/src/data-display.ts(lines 2773 and 2834), pinned by objectui's ownpackages/types/src/__tests__/timeline-item-element-6356.test.ts.data-display.tsimportsChartAxis/ChartType/I18nLabelfrom@objectstack/spec/uifor the unrelated chart surface, but neitherTimelineFeedItemnorTimelineGanttItemimports or re-declares anything from@objectstack/spec— the premise holds, so the route is followed as given (no fork to report).Four read-point re-reads,
objectuiObjectTimeline.tsxat the same pin (git show dd3f7e1be3561d63267d7162f3fc0ac52e72834d:packages/plugin-timeline/src/ObjectTimeline.tsx)::254—const hasInlineItems = Array.isArray(schema.items) && schema.items.length > 0;(loading-state seed):374—const fetchesForItself = !!schema.objectName && !boundData && !schema.items && !(props as any).data;(fetch-skip gate):546—if (schema.items) return schema.items;(matches the docblock's citation exactly):771—const hasAuthoredItems = !!schema.items;(matches the docblock's citation exactly)All four anchors in the existing docblock (
packages/spec/src/ui/component.zod.ts, theobject-timelinedoc comment) are byte-accurate at this pin — no anchor drift, so no docblock line-number edit was needed.pnpm --filter @objectstack/spec run check:spec-docblock-symbol-anchorsalso confirms: 4044 anchors resolve, 0 new anchor drift.The
variantdescribe ("ganttneeds authoreditems") still reads true beside the newitemsdescribe — confirmed againstObjectTimeline.tsx:809,if (!hasAuthoredItems && schema.variant === 'gantt')— left unchanged.Full-repo pin sweep for the stale sentence:
grep -rl "author owns the item shape"across the whole tree (excludingnode_modules/.git) found exactly the two carriers named at dispatch —packages/spec/src/ui/component.zod.tsand the generatedcontent/docs/references/ui/component.mdx— both fixed here. No test pins the old describe string (grepfor it and forbecomes a pass-throughoutside these two files: zero hits), so no fixture triage was needed.Round 2 correction — a false refusal clause, measured and dropped. The first cut of this describe (and the changeset) also said objectui's strict authoring face "refuses a mismatched arm and undeclared keys" for
object-timelineitself. A seat review measured that false at the pin, and this round independently re-measured it before editing: objectui's zod authoring face (AnyComponentSchema/StrictAnyComponentSchema,packages/types/src/zod/index.zod.ts) is az.discriminatedUnion('type', …)whose member literals — read viagit grep -n "z.literal('object-" dd3f7e1be3561d63267d7162f3fc0ac52e72834d -- packages/types/src/zod— areobject-grid/object-form/object-view/object-map/object-tree/object-gantt/object-calendar/object-kanban/object-chart/object-gallery/object-data-tableand, separately,timeline— neverobject-timeline. To measure the actual runtime answer rather than infer it,packages/typeswas exported read-only viagit archive dd3f7e1be3561d63267d7162f3fc0ac52e72834d packages/typesinto a throwaway probe run withtsxagainst this repo's own built@objectstack/spec(the objectui checkout itself was never built, checked out, or stashed):StrictAnyComponentSchema.safeParse({ type: 'object-timeline', items: [{ label: 'x' }], variant: 'vertical' })returns a singleinvalid_unionissue,"note": "No matching discriminator", on both the tolerant and the strict face —object-timelineis refused outright as an unrecognized nodetype, before any per-item judgment runs. A control on a genuine{ type: 'timeline', … }node reproduces the arm-selection andunrecognized_keysmechanism exactly (a gantt row'slabelundervariant: 'vertical'draws a namedcustomissue; a feed item's undeclareddatekey drawsunrecognized_keys) — that mechanism lives on the siblingtimelinezod node (TimelineSchema's.superRefine), whichobject-timelinetypes against only in TypeScript (ObjectTimeline.tsx'sschema: TimelineSchema & { … }), never at runtime for this node. The refusal clause is dropped from the describe and the changeset; nothing else about the route changed. Re-verified after the fix:check:generated(15/15 green), the two component/docs-facing unit test files (component.test.ts,component-element-navigation-17987.test.ts— 366 tests green),check:doc-authoringandcheck:spec-docblock-symbol-anchors(both green), and a freshdispatch-gates.mjsreconciliation on the new commit — same 102 derived families as round 1 (the diff only edits words inside the same two describe strings), 99 run green, the same 3PREREQUISITE NOT METNOT MEASURED as round 1.Tests run (this worktree,
objectstack-issue-20283, HEAD0197523247; round 1 ran at HEAD3635807a7f)pnpm --filter @objectstack/spec build— exit 0 (rebuilt after mergingorigin/main).pnpm --filter @objectstack/spec check:generated— exit 0, all 15 generated artifacts up to date (pre- and post-merge).pnpm --filter @objectstack/spec test— 573 test files passed (573), 16809 tests passed | 1 todo.pnpm --filter @objectstack/spec typecheck(tsc --noEmit+check:scripts-typecheck+check:test-typecheck) — exit 0.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran(an annotated ran-list file) reconciliation: 102 derived families, 99 run green, 3 NOT MEASURED (PREREQUISITE NOT MET, exit 3), 0 unrun —pnpm --filter @objectstack/spec run check:skill-examples,pnpm check:dual-build-cjs-loads,pnpm check:lean-entry-closureeach refused locally before measuring anything because they read BUILT output of unrelated packages (@objectstack/client-react, and — fordual-build-cjs-loads— the whole workspace'sdist/, 82+ packages) that this describe-only change does not touch and that a full local build did not fit this task's floor-tier scope; the sharedos-verify-lockqueue was also heavily contended by sibling agents' builds throughout this run. CI's full farm covers these.pnpm check:doc-authoring— exit 0: 16748 customer-facing strings across 1170 spec sources clean, no internal issue-id references (the new describe carries none).pnpm check:nul-bytes,pnpm check:merge-driver,pnpm check:objectui-pin-citations,check:changeset-gate-self-tests(check:empty-changeset/check:adr-0087-registration/check:changeset-no-major) — all exit 0.packages/spechas no workspace dependencies, so the closure is empty by construction..describe()text change only; the compiled.d.tstype surface is byte-identical (confirmed bycheck:api-surfaceandcheck:export-originsboth green with zero diff), so no downstream package's typecheck is implicated.Acceptance notes
No out-of-scope findings of class (a)/(b)/(c) surfaced while working this card.
origin/mainadvanced twice during this round (tofb194c70e5, then merged again tofb386074f5after a cleangit merge origin/mainwith no conflicts and no overlap with the two touched files); rebuilt and re-rancheck:generatedafter the merge, still all 15 green.Generated by Claude Code