Skip to content

Commit 7e36a3c

Browse files
spec(object-timeline): items describe names objectui as the item-shape owner (#20531)
Fixes #20283 Clause-②: no ## What changed `ObjectTimelinePropsSchema.items` (`packages/spec/src/ui/component.zod.ts`) stays `z.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`'s `TimelineFeedItem` (`variant` absent / `vertical` / `horizontal`) or `TimelineGanttItem` (`variant: 'gantt'`), the arm this node's own `variant` selects. `content/docs/references/ui/component.mdx` was regenerated via `gen:schema && gen:docs` — never hand-edited. `.changeset/20283-object-timeline-items-element-owner-describe.md` — `@objectstack/spec` `patch`. ## Route taken (per the card, not re-opened) `items` keeps `z.array(z.unknown())`; the value contract stays owned by objectui. Declaring the arms here would fork a second vocabulary. `mapping` and `object-gantt.markers` hold the same VALUE posture unchanged. ## Verification record **Premise re-measure (required before touching code).** At `.objectui-sha` `dd3f7e1be3561d63267d7162f3fc0ac52e72834d` (the pin this branch built against), `TimelineFeedItem` and `TimelineGanttItem` are declared as plain TypeScript interfaces in objectui's `packages/types/src/data-display.ts` (lines 2773 and 2834), pinned by objectui's own `packages/types/src/__tests__/timeline-item-element-6356.test.ts`. `data-display.ts` imports `ChartAxis` / `ChartType` / `I18nLabel` from `@objectstack/spec/ui` for the unrelated chart surface, but neither `TimelineFeedItem` nor `TimelineGanttItem` imports 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**, `objectui` `ObjectTimeline.tsx` at 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`, the `object-timeline` doc 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-anchors` also confirms: 4044 anchors resolve, 0 new anchor drift. The `variant` describe ("`gantt` needs authored `items`") still reads true beside the new `items` describe — confirmed against `ObjectTimeline.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 (excluding `node_modules`/`.git`) found exactly the two carriers named at dispatch — `packages/spec/src/ui/component.zod.ts` and the generated `content/docs/references/ui/component.mdx` — both fixed here. No test pins the old describe string (`grep` for it and for `becomes a pass-through` outside 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-timeline` itself. 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 a `z.discriminatedUnion('type', …)` whose member literals — read via `git grep -n "z.literal('object-" dd3f7e1be3561d63267d7162f3fc0ac52e72834d -- packages/types/src/zod` — are `object-grid` / `object-form` / `object-view` / `object-map` / `object-tree` / `object-gantt` / `object-calendar` / `object-kanban` / `object-chart` / `object-gallery` / `object-data-table` and, separately, `timeline` — **never `object-timeline`**. To measure the actual runtime answer rather than infer it, `packages/types` was exported read-only via `git archive dd3f7e1be3561d63267d7162f3fc0ac52e72834d packages/types` into a throwaway probe run with `tsx` against 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 single `invalid_union` issue, `"note": "No matching discriminator"`, on both the tolerant and the strict face — `object-timeline` is refused outright as an unrecognized node `type`, before any per-item judgment runs. A control on a genuine `{ type: 'timeline', … }` node reproduces the arm-selection and `unrecognized_keys` mechanism exactly (a gantt row's `label` under `variant: 'vertical'` draws a named `custom` issue; a feed item's undeclared `date` key draws `unrecognized_keys`) — that 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 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-authoring` and `check:spec-docblock-symbol-anchors` (both green), and a fresh `dispatch-gates.mjs` reconciliation 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 3 `PREREQUISITE NOT MET` NOT MEASURED as round 1. ## Tests run (this worktree, `objectstack-issue-20283`, HEAD `0197523247`; round 1 ran at HEAD `3635807a7f`) - `pnpm --filter @objectstack/spec build` — exit 0 (rebuilt after merging `origin/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-closure` each refused locally before measuring anything because they read BUILT output of unrelated packages (`@objectstack/client-react`, and — for `dual-build-cjs-loads` — the whole workspace's `dist/`, 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 shared `os-verify-lock` queue 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. - Dependency-closure build (①): none — `packages/spec` has no workspace dependencies, so the closure is empty by construction. - Downstream consumer closure: not run — this is a `.describe()` text change only; the compiled `.d.ts` type surface is byte-identical (confirmed by `check:api-surface` and `check:export-origins` both 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/main` advanced twice during this round (to `fb194c70e5`, then merged again to `fb386074f5` after a clean `git merge origin/main` with no conflicts and no overlap with the two touched files); rebuilt and re-ran `check:generated` after the merge, still all 15 green. --- _Generated by [Claude Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 288611e commit 7e36a3c

3 files changed

Lines changed: 9 additions & 2 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
`ObjectTimelinePropsSchema.items`' describe no longer says "the author owns the item shape". `items` stays `z.array(z.unknown())` — no schema-shape change — but the sentence now names the actual owner: each element is objectui's declared timeline element, `@object-ui/types`'s `TimelineFeedItem` (`variant` absent / `vertical` / `horizontal`) or `TimelineGanttItem` (`variant: 'gantt'`), the arm this node's `variant` selects.
6+
7+
The element union is declared entirely inside objectui's `packages/types` (`TimelineFeedItem` / `TimelineGanttItem`, plain TypeScript interfaces) — nothing in this package imports or re-declares it, so this is a documentation-only correction, not a value-tightening. Value tightening (declaring the arms in this schema instead of `z.unknown()`) stays a later ratchet with its own inventory.

‎content/docs/references/ui/component.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -991,7 +991,7 @@ View filter rule
991991
| **sort** | `{ field: string; order: Enum<'asc' \| 'desc'> }[]` | optional | Row order for the fetched entries — the SortItem array form `[{ field, order }, ...]`, the one sort orthography every declared `sort` door on this platform shares; lowered to the wire `$orderby`. The legacy string clause (`name desc`) is refused — see migration `object-block-sort-item-array` |
992992
| **limit** | `integer` | optional | Maximum number of records loaded onto the rail (row cap); lowered to the query's top-level `$top` (renderer default 100). A timeline renders one rail with no pagination control, so this is the author's window rather than a page size |
993993
| **data** | `any[]` | optional | Pre-fetched records — read FIRST as the rail's row source, ahead of the data-scope binding and the fetch, and composed into entries through the same `timeline` field bindings a fetched row takes; authoring it suppresses the object query entirely. Distinct from `items`, which is the already-composed entry shape and wins over this key when both are written |
994-
| **items** | `any[]` | optional | Static inline entries — read ahead of every record source, `data` above included, and bypasses the object query entirely (the renderer becomes a pass-through and the author owns the item shape) |
994+
| **items** | `any[]` | optional | Static inline entries — read ahead of every record source, `data` above included, and bypasses the object query entirely (the renderer becomes a pass-through). Each element is objectui's declared timeline element, `@object-ui/types`'s `TimelineFeedItem` (`variant` absent / `vertical` / `horizontal`) or `TimelineGanttItem` (`variant: 'gantt'`), the arm this node's `variant` selects |
995995
| **variant** | `Enum<'vertical' \| 'horizontal' \| 'gantt'>` | optional | Rail layout (renderer default `vertical`). ⚠️ `gantt` needs authored `items`: the object-bound path composes flat feed entries, which the gantt branch cannot draw, and refuses that combination with a named diagnostic instead of drawing an empty chart |
996996
| **dateFormat** | `Enum<'short' \| 'long' \| 'iso'>` | optional | How each entry's date is rendered (renderer default `short`): `short` / `long` are locale-formatted, `iso` is the locale-free machine form |
997997
| **rowLabel** | `string` | optional | Header label for the gantt row column — read by the gantt branch only, which on this block needs authored `items` |

‎packages/spec/src/ui/component.zod.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5207,7 +5207,7 @@ export const ObjectTimelinePropsSchema = lazySchema(() => strictObject({
52075207
data: z.array(z.unknown()).optional()
52085208
.describe("Pre-fetched records — read FIRST as the rail's row source, ahead of the data-scope binding and the fetch, and composed into entries through the same `timeline` field bindings a fetched row takes; authoring it suppresses the object query entirely. Distinct from `items`, which is the already-composed entry shape and wins over this key when both are written"),
52095209
items: z.array(z.unknown()).optional()
5210-
.describe('Static inline entries — read ahead of every record source, `data` above included, and bypasses the object query entirely (the renderer becomes a pass-through and the author owns the item shape)'),
5210+
.describe("Static inline entries — read ahead of every record source, `data` above included, and bypasses the object query entirely (the renderer becomes a pass-through). Each element is objectui's declared timeline element, `@object-ui/types`'s `TimelineFeedItem` (`variant` absent / `vertical` / `horizontal`) or `TimelineGanttItem` (`variant: 'gantt'`), the arm this node's `variant` selects"),
52115211
variant: z.enum(['vertical', 'horizontal', 'gantt']).optional()
52125212
.describe("Rail layout (renderer default `vertical`). ⚠️ `gantt` needs authored `items`: the object-bound path composes flat feed entries, which the gantt branch cannot draw, and refuses that combination with a named diagnostic instead of drawing an empty chart"),
52135213
dateFormat: z.enum(['short', 'long', 'iso']).optional()

0 commit comments

Comments
 (0)