Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

[finding] TimelineSchema.items has no declared element type, and there are two of them #6356

Description

@os-support-ai

Path: none

Triage (North Star full-board re-grade, 2026-09-19): No checklist item covers the timeline element shape. Kept on the definition layer 「看数据的视图」 plus North Star priority 4: items is declared any[], which is declaration-shaped but enforces nothing, while the renderer actually reads two distinct element shapes discriminated by variant — so an AI authoring timeline items meets no refusal. Maintainer-confirmed.

Found while working #6170 (PR #6352); deliberately left out of that card's scope. Observational — filed for triage, not as a defect claim.

The fact

PR #6352 declared items on TimelineSchema for the first time, as any[]. That is faithful to what the renderer does (items.map((item: any) => …) at every site, and items as Array<any> at two), but it means the element is still undeclared.

The renderer reads two different element shapes, discriminated by variant:

variant element shape read
vertical / horizontal time, title, description, variant, icon, color, content, className, meta, group
gantt label, items: [{ title, startDate, endDate, variant }]

content/docs/plugins/plugin-timeline.mdx documents both as named shapes ("Timeline Item (Vertical/Horizontal)", "Gantt Item"), so the vocabulary is already written down — just nowhere a compiler or validator can read it.

Two of those keys are not in the docs at all: meta (inline chips beside the title) and group (sticky bucket headers). Both are produced by ObjectTimeline and consumed by TimelineRenderer, so they are a real internal contract with no declaration on either side.

Why it was left out

Declaring the element precisely is a genuine narrowing of a published type, not an additive declaration: items accepts anything today. It needs its own measurement — which keys are authored in the wild, whether meta/group are authorable or renderer-internal, and whether the two shapes should be one union or a discriminated pair keyed off variant. #6170's ruling did not cover it and PR #6352 declined to guess.

Blocked-by: #6170 — the alignment has to land first, since this refines a key that card introduced.

Reproduce

sed -n '250,300p' packages/plugin-timeline/src/renderer.tsx     # feed item reads
sed -n '447,470p' packages/plugin-timeline/src/renderer.tsx     # gantt row reads
sed -n '80,110p'  content/docs/plugins/plugin-timeline.mdx      # the two documented shapes

Refs: #6170, PR #6352.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    Unblock: pm:blocked → pm:queue. The prerequisite this card names is on main; it never waited on #6170's stage 2

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01W89enF2dYV7K4N2Fbfj33f · 2026-09-27T06:40Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card (no comments), #6170's body and thread, #6354 (closed duplicate), and PR #6352.

    Execution notes. They are unchanged from the body: it asks for its own measurement first.

    • Which element keys are authored in the wild.
    • Whether meta / group are authorable or renderer-internal.
    • One union, or a discriminated pair keyed off variant.

    The change narrows a published type ⇒ Clause-②: yes. It edits TimelineSchema in packages/types, the same file as #6170's stage 2, so if a claim on #6170 is in flight, the dispatching seat's in-flight check sequences the two. ⛔ This is not a wait on #6170.

  2. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1
    Session: session_01DuWo5bdP9SdVebamn99GGk
    Account: os-steve (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-6356-timeline-item-element
    Worktree: objectui-issue-6356
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: the TimelineSchema declaration region in packages/types/src/data-display.ts and its zod mirror region in packages/types/src/zod/data-display.zod.ts (the element type of items), any packages/plugin-timeline/src read the narrowing breaks, content/docs/plugins/plugin-timeline.mdx if its two documented shapes change, tests beside them, one .changeset/6356-…md. Measurement first: ⛔ no narrowing before the in-the-wild authoring reading exists (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (TIER_DEFAULT, objectstack scripts/pm/dispatch-gates.mjs:12534 at 0d3ec47137; --tier refuses objectui paths)
    Clause-②: yes
    Thread-read: 5853468236
    Serial constraints cleared: read 2026-09-27T15:09Z at objectui origin/main c907a9c87. Draft PR objectui#10714 (objectui#9590, seat #2) edits data-display.ts and data-display.zod.ts in the ListSchema / ListItem region, not TimelineSchema ⇒ region-disjoint; merge main after it lands. Draft PR objectui#8941 edits packages/types/src/objectql.ts, outside this surface. objectui#6170 (stage 2, same file) is pm:blocked and unclaimed ⇒ nothing to sequence against. Not in pm:epic objectui#6349's subtree (GET …/6356/parent answers 404).

    Why Clause-②: yes: triage 5853468236 — declaring the element narrows a published type, and a new exported element type grows the public face. A contract review is owed before enqueue.

    domain:ui seat #1 · claim · 2026-09-27T15:09Z


    Generated by Claude Code

  3. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    objectui#6356 — measurement first, then a stop: needs_decision

    domain:ui seat #1 dispatch, dev run session_01DuWo5bdP9SdVebamn99GGk (mode:subagent). Read at objectui bff63cc12 (branch base) and objectstack 0d3ec471. No PR: nothing was narrowed, per the claim's stop line. Three choices remain that change what an author may write, so they go to the seat as open_questions. The measurement below is the deliverable. Generics and placeholders are spelled in words on purpose, because GitHub strips tag-shaped text from bodies.

    What the card says, checked against main

    • The card says "items is declared any[]". That is stale. objectui#7164 and objectui#7365 have since declared both levels, on both halves, as OBJECTS whose keys stay open (.passthrough() in the mirror, a string index signature in the TS type). The element's own keys are still undeclared, so the card's core still holds: { title, date } type-checks and parses green (Table 3).
    • The card says there are "two element shapes". There are really three read sets. horizontal drops five keys that vertical reads (Table 2), so the card's feed row in its table is the vertical read set, not a shared one.
    • The card says meta / group come from ObjectTimeline and are read by TimelineRenderer. Confirmed, and they are read by the vertical branch only.
    • New fact. The strict authoring face (objectui#8345, deriveStrictAuthoringSchema) refuses every legitimate timeline element today, including the schema-catalog's own feed items and gantt rows and bars (Table 3). The element and the bar declare no keys, so the face closes them to nothing. The objectui#5250 ruling says objectui validate and the doc-snippet gates will run strict. Declaring the element is therefore a prerequisite for wiring that face without false refusals.
    • Zone 2 assumption 1 (file locations) holds. Assumption 2 holds too: PR objectui#10714's 18 file patches mention Timeline zero times in data-display.ts and data-display.zod.ts, so the two changes touch separate regions.

    Instrument

    • Static walk. A TypeScript-AST walk plus a JSON walk over git ls-files, on a clean worktree (so every file read is the HEAD blob). In objectui it covered 8785 files with 0 parse failures. Fenced code blocks in .md and .mdx files were parsed too.
    • What counts as a timeline node. An object literal whose type is timeline, plugin-timeline:timeline, object-timeline or view:timeline. For registration examples and render props, the walk also counts an object whose variant is vertical, horizontal or gantt and that carries items.
    • Lit control. A synthetic two-file repo authors meta, group, color, startDate, endDate, _data and date, once in an apps/ TS file and once in a skills/ JSON fence. The walk reported every one of those keys.
    • Render probes. A throwaway vitest file rendered the real TimelineRenderer. It was deleted uncommitted, and the tree was verified clean with git status --porcelain = 0.
    • Strict-face probe. tsx ran against packages/types/src.
    • Not re-derivable from this comment alone. The scripts lived in the session scratchpad. The follow-up implementation should re-take Table 1 with a committed instrument if it wants a gate.

    Table 1 — element keys authored in each corpus

    The figure after each key is the number of elements that carry it.

    corpus (files) feed elements feed keys gantt rows / bars row keys / bar keys
    schema-catalog JSON (3) 8 time 8, title 8, description 8, variant 8, icon 4 3 / 5 label 3, items 3 / title, startDate, endDate, variant: 5 each
    docs plugin-timeline.mdx (1) 10 time 10, title 10, variant 10, description 9, icon 7 2 / 3 label 2, items 2 / 3 each
    package md: TIMELINE.md, plugin-timeline README.md (2) 11 time 11, title 11, variant 10, description 9, icon 4, className 1, content 1 3 / 9 label 3, items 3 / 9 each
    registration defaultProps plus examples (plugin-timeline:timeline) 10 time 10, title 10, description 10, variant 10, icon 4 3 / 5 label 3, items 3 / 5 each
    apps/ (298 files), skills/ (27 files) 0 none 0 none
    objectstack, whole tree (9418 files; 21 timeline-typed nodes: 2 list views, 1 component-manifest entry in sdui.manifest.json, 18 in tests) 0 outside tests none 0 none
    test fixtures (15 files; they show which tests a narrowing would reach, not what authors write) 15 title 15, time 13, description 6, variant 3, date 2 7 / 7 label 7, items 7 / title, startDate, endDate: 7
    • Authored totals (the four authoring corpora combined):
      • Feed: 39 elements. time 39, title 39, variant 38, description 36, icon 19, className 1, content 1.
      • Feed keys never authored: color, meta, group, startDate and endDate all score 0.
      • Gantt: 11 rows, all carrying label and items. 22 bars, all carrying title, startDate, endDate and variant.
    • The seven feed keys agree everywhere. The authored feed vocabulary is exactly the seven keys the docs page documents. The registration's inputs description lists six of them and omits className.
    • Authored variant values on elements and bars are default 6, success 25, info 16, warning 12, danger 3: exactly the documented five. The marker also paints todo, in-progress and done (zero authored); the bar paints only the five. Lit control: the same grep hits the node-level values vertical 11, horizontal 4, gantt 5.
    • Test fixtures. ObjectTimeline.test.tsx authors { title, date } twice, typed schema: any. date is the TimelineEvent vocabulary and nothing reads it off an item, so the two tests assert the title only. This is the card's trap, live in-repo.

    Table 2 — what each feed branch reads, measured in render

    The probe rendered one item carrying every candidate key, with dateFormat: 'iso'.

    key vertical horizontal
    time, title, description, icon, className drawn drawn
    color (marker style) drawn dropped
    meta (chips) drawn dropped
    group (sticky bucket header) drawn dropped
    startDate (as the only date), endDate drawn dropped
    date dropped dropped
    • Not re-probed. variant and content are read by both branches in source (the TimelineMarker variant and renderChildren(item.content)).
    • Gantt read set. The gantt branch reads row label / items and bar title / startDate / endDate / variant. The existing gantt suites pin that, so it was not re-probed.
    • The only producer of the five composed-only keys is ObjectTimeline's effectiveItems memo, which composes { title, time, startDate, endDate, description, variant, color, group, meta, _data } per record. Its click handler reads _data. ListView and ObjectView hand it config and never items.

    Table 3 — what each door answers today

    input tolerant mirror (safeValidateSchema, what objectui validate runs) strict face (objectui#8345, not wired yet) render
    catalog feed item accept refuse: unrecognized_keys at items.0 [time, title, description, variant, icon] draws
    catalog gantt row and bar accept refuse: items.0 [label] and items.0.items.0 [title, startDate, endDate, variant] draws
    { title, date } accept refuse (both keys, the same way as any key) title only; date silently dropped
    a feed item under variant: 'gantt' accept refuse (every element key) empty unlabelled row, no diagnostic
    a gantt row under vertical / horizontal accept refuse (every element key) empty item, no diagnostic

    The two cross-shape rows are the evidence for the second question below. A union at the element level accepts both of them by construction, because each is a valid arm.

    Reproduce the cross-shape reading

    // vitest file under packages/plugin-timeline/src/__tests__/, run from the repo root
    import React from 'react';
    import { render } from '@testing-library/react';
    import { TimelineRenderer } from '../renderer';
    import { TimelineSchema } from '@object-ui/types/zod';
    const feed = { time: '2024-01-15', title: 'FEED_TITLE' };
    const row = { label: 'ROW_LABEL', items: [{ title: 'BAR_TITLE', startDate: '2024-01-01', endDate: '2024-02-01' }] };
    for (const schema of [
      { type: 'timeline', variant: 'gantt', items: [feed] },
      { type: 'timeline', variant: 'vertical', items: [row] },
    ]) {
      const ok = TimelineSchema.safeParse(schema).success;            // true, both
      const html = render(React.createElement(TimelineRenderer, { schema })).container.innerHTML;
      // neither FEED_TITLE nor ROW_LABEL nor BAR_TITLE, and no role="alert"
    }

    The same shapes under their own variant are the lit control: FEED_TITLE draws under vertical, and ROW_LABEL plus BAR_TITLE draw under gantt.

    {
      "issue": 6356,
      "status": "needs_decision",
      "branch": "claude/issue-6356-timeline-item-element",
      "pr": null,
      "session": "session_01DuWo5bdP9SdVebamn99GGk (dev subagent; the seat's session id)",
      "premise_still_valid": true,
      "summary": "Measured first and stopped before narrowing anything. The core premise holds on bff63cc12: the element's own keys are undeclared, and { title, date } passes both TS and the tolerant mirror. Three sub-claims moved. items is no longer any[] (objectui#7164 and #7365 declared row and bar as open objects). There are three read sets, not two: horizontal drops color, meta, group, startDate and endDate, measured in render. And the strict authoring face (objectui#8345) refuses every legitimate timeline element today. Authored vocabulary across four corpora is exactly the documented seven feed keys (39 feed elements) plus label/items rows (11) and title/startDate/endDate/variant bars (22). Zero color, meta, group, startDate or endDate were authored anywhere, zero in apps/ and skills/, and zero in objectstack. Cross-shape items (a feed item under gantt, a gantt row under vertical or horizontal) are accepted and render empty with no diagnostic. Three choices change what an author may write (open_questions), so this returns needs_decision, with one recommendation per question.",
      "tests": "No gate owed: zero-diff branch (no commits; origin branch == base bff63cc12). Measurements: (1) static walk over git ls-files: objectui 8785 files, 0 parse failures; objectstack 9418 files, 2 JSONC tsconfig parse failures (irrelevant). Lit control: a synthetic repo authoring meta, group, color, startDate, endDate, _data and date, all reported. (2) Render probe through bash os-verify-lock.sh -c 'pnpm exec vitest run packages/plugin-timeline/src/__tests__/zz-probe-6356.test.tsx', three runs, each 'Test Files 1 passed (1)' and 'os-verify-lock: VERDICT command-exit 0'. The probe file was deleted uncommitted; git status --porcelain = 0 before the worktree was removed. (3) Strict-face probe with tsx on packages/types/src (deriveStrictAuthoringSchema(TimelineSchema)), exit 0, readings in Table 3. (4) zod 4.4.3 check for question 2 option C: a superRefine'd object keeps type object, .shape and discriminatedUnion membership, but .extend() that OVERWRITES a key throws 'Cannot overwrite keys on object schemas containing refinements. Use .safeExtend() instead.'",
      "gates": [
        "none run: zero-diff branch, no files changed; measurement probes only (render probe VERDICT command-exit 0, x3)"
      ],
      "line_budget": "n/a: no skills/** or other governed file touched (no file touched at all)",
      "deviations": [
        "No PR opened, so neither pr_create nor the PR-assignee label-write was spent. Reason: needs_decision, and the claim's stop line forbids narrowing before a ruling.",
        "A temporary probe test file was created in the worktree to take the render readings, then deleted uncommitted; the tree was verified clean before git worktree remove.",
        "The pushed branch claude/issue-6356-timeline-item-element sits at base bff63cc12 with no commits. It remains on origin as the claim's landing marker (this session cannot delete remote refs).",
        "The report fields gates, line_budget, deviations and files_changed were added to the role-file template at the PM's request.",
        "The measurement table lives in this comment rather than in a PR body, because there is no PR."
      ],
      "files_changed": [],
      "mcp_calls": "0",
      "api_writes": "1: POST /repos/objectstack-ai/objectui/issues/6356/comments (this report, via objectstack scripts/pm/post-stamped.mjs). Reads, not writes: REST GET issue 6356 and its comments, and GET pulls/10714/files.",
      "open_questions": [
        {
          "question": "Q1. Are meta and group, and the other three keys that only the composed path produces on a feed item (color, startDate, endDate), authorable element keys or renderer-internal?",
          "options": [
            "A. Renderer-internal. The authored feed element declares only the documented seven (time, title, description, variant, icon, content, className). The composed-only five plus _data get an internal type inside plugin-timeline for the ObjectTimeline-to-TimelineRenderer handoff. The renderer keeps reading them, so it stays more lenient than validate (the house posture), and the strict face refuses them when authored. Cost: someone who authors meta or group on a vertical timeline today gets chips and headers, and would be refused once the strict face is wired. Measured authored instances: zero.",
            "B. Authorable, vertical only. This needs the element discriminated three ways (vertical, horizontal, gantt), because horizontal drops all five in render. Cost: five keys declared with zero authored pull, and a three-arm discrimination.",
            "C. Authorable on one shared feed element. Not viable: it declares keys horizontal silently drops (Table 2), which is a declared capability the runtime does not honour."
          ],
          "recommendation": "A. Axis 1 (real need): zero authored across four authoring corpora, apps/, skills/ and objectstack, and the sole producer is ObjectTimeline's own composition. Axis 2 (long term): an internal handoff is typed where it lives, and the author face becomes the documented face, which the docs, the registration and every authored corpus already agree on. Axis 3 (AI errors): fewer declared keys means fewer authorings that horizontal silently drops, and the strict face refuses the rest. Axis 4 (no expansion): no pull, so no declaration. This is the same posture objectui#10222 (letter A) took for the chip-field list that produces meta."
        },
        {
          "question": "Q2. Should the element be one union, or a pair discriminated by the node's variant? And if a pair, in which form?",
          "options": [
            "A. Element union (feed item or gantt row); TimelineSchema stays an interface and a ZodObject. Cheapest. It refuses unknown keys (TS literals get excess-key errors against the closed arms, and the strict face closes them) but NOT cross-shape items, which Table 3 shows are silent today.",
            "B. Pair as a split node: TS TimelineSchema becomes a union type alias (feed node or gantt node) and the zod mirror becomes a union. It refuses cross-shape in TS and in validate. Cost: the published zod TimelineSchema stops being a ZodObject, so .shape and .extend are gone. Four in-repo test files use them, and the parity ratchet pairs every mirror by reading its own .shape. The node-level tombstones (timeScale, body, children) and the three deprecated keys must ride on both arms.",
            "C. Pair as a refinement: the TS element union from A, and the zod mirror stays a ZodObject with a node-level superRefine that judges each element against the arm variant selects (absent means vertical). This is the house pattern for cross-key rules (calendarSelectionFitsMode, requireRecordSource). It refuses cross-shape at objectui validate only, because TS cannot see the parent key. Costs: .extend key-overwrite throws on a refined object in zod 4.4.3, so the two tests that do TimelineSchema.extend with an items override move to .safeExtend. On the tolerant, passthrough face, cross-shape is refusable only through at least one REQUIRED key per arm. Measured: every authored feed element carries title and time, every row carries label and items, every bar carries title, startDate and endDate. But row items is ruled optional (objectui#6750, objectui#7164), so label is the gantt arm's only discriminator, and making label (and feed title) required is new semantics."
          ],
          "recommendation": "C, with feed title and gantt label required, and the element and bar variant declared as the documented five (all authored values fall inside it; the composed path keeps record values through its internal type). Axis 3: it refuses both unknown keys and cross-shape at the JSON door AI authors through, and TS literals still get excess-key refusal. Axis 2: TimelineSchema stays an interface and a ZodObject, so the parity ratchet, the tombstones and DataDisplaySchema's discriminated union are untouched; B's restructuring buys only TS-level cross-shape refusal. Axis 4: no key beyond the measured ones, and the two required keys are present in 100% of authored elements. Axis 1: gantt is authored in all four authoring corpora, and the cross-shape failure is measured silent."
        },
        {
          "question": "Q3. How should this be sequenced with @objectstack/spec? The spec's object-timeline element declares items as z.array(z.unknown()) ('the author owns the item shape'), and its items reach the same TimelineRenderer.",
          "options": [
            "A. objectui first, on its own presentational node timeline. Then an objectstack follow-up takes the element vocabulary for object-timeline.items, the way the spec already defers to objectui for the objectui-owned mapping contract (z.unknown(), 'its contract still lives in objectui').",
            "B. Spec first; objectui mirrors after.",
            "C. objectui only; the spec door stays open."
          ],
          "recommendation": "A. Axis 2: the vocabulary's implementation, docs and only producer all live in objectui, and the spec's stated posture for an objectui-owned contract is to follow objectui. The base principle 'spec declaration over implementation' points toward B, so this is a real conflict, surfaced here rather than silently resolved. Axis 3: C leaves os validate open for the same element that objectui validate would refuse. Axis 4: A adds no gate and no staged window."
        }
      ],
      "out_of_scope_findings": [
        "carrier: objectui#6170 (stage 2, events/orientation/position) · packages/types/examples/data-display-examples.json authors a timeline with events (zero read points), not items · noted, not filed",
        "carrier: the implementation PR for this card after the ruling · ObjectTimeline.test.tsx authors { title, date } items (date is silently dropped; typed schema: any, so a narrowing will not redden them); the implementation should re-spell them time · noted, not filed",
        "carrier: none (承接者:无) · packages/components/src/renderers/complex/TIMELINE.md lists three gantt time scales where six exist · documentation drift, Acceptance-notes class, not filed"
      ]
    }
  4. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    Measurement delivered ⇒ two contract-shape questions to the box · domain:ui seat #1 · 2026-09-27T15:55Z

    session_01DuWo5bdP9SdVebamn99GGk (os-steve). The card is measurement-first. The dev measured, wrote nothing (the claim's stop line), and returned three questions (report 5857222802, which holds the tables). The seat re-read the one reading everything rests on: on objectui origin/main, TimelineSchema.items is z.array(TimelineRowSchema) (packages/types/src/zod/data-display.zod.ts:1135), not the body's any[]. The element is an object with its keys still open. ⇒ The card's core stands, and its body's framing is stale.

    Release: session_01DuWo5bdP9SdVebamn99GGk · cause: measurement done, two open contract-shape choices · destination: needs-user-decision. The claim 5857054090 is spent. The branch claude/issue-6356-timeline-item-element holds no commits beyond base. The next dispatch re-claims.

    Governing text: this card's triage block (「items … enforces nothing … an AI authoring timeline items meets no refusal」, maintainer-confirmed); triage 5853468236 (the three execution questions, Clause-②: yes); objectui#10222 letter A (the same posture for the chip-field list that produces meta); the pm-dispatch base principle 「spec 声明 > 实现 > 文档面」.

    One-line problem

    An author (human or AI) can put any keys on a timeline item, or put a gantt row on a feed timeline. Both are accepted, and the wrong parts just do not draw. In addition, the strict authoring check that objectui validate is due to run would refuse every legitimate timeline element today, because the element declares no keys at all (measured, report Table 3).

    Q1 — meta, group, color, startDate, endDate on a feed item: authorable, or renderer-internal?

    what it means cost, measured
    A Renderer-internal. The authored feed element declares the documented seven (time, title, description, variant, icon, content, className). The five composed-only keys get an internal type inside plugin-timeline for the ObjectTimeline → renderer handoff. Zero authored instances across four authoring corpora, apps/, skills/ and objectstack. Anyone who authors meta today would be refused once the strict face is wired.
    B Authorable, on vertical only. Needs a three-way element split (vertical / horizontal / gantt), because horizontal drops all five. Five keys declared with zero pull, plus a three-arm discrimination.
    C Authorable on one shared feed element. Declares keys that horizontal silently drops (report Table 2) ⇒ not viable.

    Q2 — one union, or a pair discriminated by the node's variant?

    what it means cost, measured
    A Element union (feed item or gantt row); TimelineSchema stays an interface and a ZodObject. Refuses unknown keys, but NOT a gantt row on a feed timeline (silent today, Table 3).
    B Split node: TS TimelineSchema becomes a union alias and the zod mirror a union. Refuses cross-shape in TS and in validate. The published zod TimelineSchema stops being a ZodObject (.shape / .extend gone; 4 test files and the parity ratchet use them); the tombstones ride on both arms.
    C TS element union as in A, plus a node-level superRefine in the zod mirror that judges each element against the arm variant selects (absent ⇒ vertical). This is the house pattern for cross-key rules (calendarSelectionFitsMode, requireRecordSource). Refuses unknown keys AND cross-shape at the JSON door AI authors use. TS literals still get excess-key errors; cross-shape is caught at validate, not at compile. Two tests move from .extend to .safeExtend.

    业务含义直译

    • Q1-A:时间线条目上作者能写的,就是文档写着的那七个;另外五个是组件内部自己拼出来的,不对外承诺。
    • Q1-B / C:把内部拼装用的键也变成对外承诺;可是没有人在写它们,而且横向布局根本不画。
    • Q2-A:能拦住乱写的键,拦不住「把甘特行塞进普通时间线」。
    • Q2-B:编译期也能拦住串用,但要把一个已发布的 schema 拆成两个,测试和一致性检查都得跟着改。
    • Q2-C:在作者和 AI 真正走的 JSON 校验入口,两种错都响亮拒绝,已发布的形状不动。

    四轴论证(业务立场)

    • ① 项目长远合理性:作者面只承诺文档写明、作者在用的形状;内部拼装的键在它所在的包里定型。已发布的 TimelineSchema 保持对象形状,一致性棘轮、墓碑、DataDisplaySchema 的判别联合都不动,所以 Q2 取 C。参照主流平台:这类组件的条目 schema 是闭合的,内部渲染字段不外露。
    • ② 实际业务拉动:四个作者语料里,那五个组合键的作者写入实测为零;甘特在四个语料里都有人写,而串用失败在今天是静默的。
    • ③ 防 AI 犯错:Q1-A 少声明五个键,就少五种会被横向布局静默丢掉的写法;Q2-C 在 JSON 入口同时拒绝未知键和串用,AI 走的正是这道门。
    • ④ 创业阶段不扩散:没有拉动就不声明;没有新增门禁;不设分阶段窗口。
    os-decision-facets
    ① 项目长远合理性:Q1-A + Q2-C 缩小契约增生——作者面收成文档写明的形状,已发布 schema 的对象形状不变;Q1-B 与 Q2-B 各扩大一块面。
    ② 实际业务拉动:组合键作者写入为零;甘特四个语料都有,串用今天静默失败。
    ③ 防 AI 犯错:闭合的条目 schema 加入口处按 variant 判别,未知键与串用都响亮拒绝。
    ④ 创业阶段不扩散:零拉动的键不声明,不加门禁,无过渡窗口。
    Prior rulings read: timeline items,meta group,discriminated variant → 1 hit; objectui#10222 ruling 5824012155 letter A (retire the undeclared metaFields read — the same posture for the list that produces meta); thread: triage 5853468236
    

    推荐:Q1 取 A,Q2 取 C(feed 的 title 与 gantt 的 label 设为必填,两者在已写元素中出现率均为 100%)。回退:Q2 取 A(先拦未知键,串用另立卡)。
    自检:只看①选 Q1-A / Q2-C;②③④ 是否翻转:否。
    置信缺口:租户存储里是否有人写了 meta / group 等键,本席看不见;若有,接上严格校验后这些记录会被拒(今天它们在横向布局下本就不画)。

    Q3 — sequencing with @objectstack/spec — decided by this seat, ⛔ not escalated

    Order of technical work is a non-escalation class. Decision: objectui first, then the spec follows (the dev's A). The vocabulary's implementation, docs and only producer all live in objectui, and the spec's own object-timeline.items is z.array(z.unknown()) with 「the author owns the item shape」. On landing, this seat files the objectstack follow-up (Blocked-by: this card's PR), which lets the spec take the element vocabulary for object-timeline.items. ⚠️ This runs against 「spec 声明 > 实现」 read literally, and it is recorded here so the ruling can overturn it in the same stroke.

    After the ruling

    Re-dispatch with Clause-②: yes, surface = the TimelineSchema region of packages/types/src/data-display.ts and its zod mirror region, packages/plugin-timeline/src (the internal handoff type, and re-spelling ObjectTimeline.test.tsx's { title, date } items), content/docs/plugins/plugin-timeline.mdx, tests, and a minor changeset for @object-ui/types. The measurement tables in 5857222802 are the dev's starting data and are not re-derived.

    domain:ui seat #1 · box · 2026-09-27T15:55Z


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    Ruling: maintainer · Q1 letter A 「A 内部键(推荐)」 · Q2 letter C 「C 联合+校验判别(推荐)」 · 2026-09-27T16:01Z

    Recorded by the domain:ui seat #1 (session_01DuWo5bdP9SdVebamn99GGk, os-steve). ⛔ Not this seat's adjudication: the maintainer answered the two box questions of 5857426543 in this session's chat. The quoted labels are the options chosen, verbatim. Freshness: the thread was re-read in this act, and 5857426543 is the newest comment.

    Ruled.

    • Q1 = A. The authored feed element declares the documented seven: time, title, description, variant, icon, content, className. meta, group, color, startDate, endDate (and _data) are renderer-internal. They get an internal handoff type inside plugin-timeline for ObjectTimeline → TimelineRenderer, and the strict face refuses them when authored.
    • Q2 = C. The TS element is a union (feed item or gantt row). The zod TimelineSchema stays a ZodObject, with a node-level superRefine that judges each element against the arm variant selects (absent ⇒ vertical). Feed title and gantt label are required, and the element / bar variant is declared as the documented five. The two .extend call sites move to .safeExtend.
    • Q3 (sequencing) was decided in-seat in 5857426543 and stands: objectui first; the objectstack follow-up for object-timeline.items is filed on landing.

    State note. The box's Release: line never reached the labels: the flip to needs-user-decision was refused by the write-pace budget, and the ruling arrived before it could be retried. ⇒ The card stayed pm:dispatched / os-steve throughout. The release is recorded as spent in the next act, which is a fresh Claim:.

    domain:ui seat #1 · ruling record · 2026-09-27T16:01Z


    Generated by Claude Code

  6. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1
    Session: session_01DuWo5bdP9SdVebamn99GGk
    Account: os-steve (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-6356-timeline-item-element
    Worktree: objectui-issue-6356
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: the TimelineSchema / timeline element region of packages/types/src/data-display.ts and of packages/types/src/zod/data-display.zod.ts (element union, node-level superRefine, required title / label, element and bar variant); packages/plugin-timeline/src (the internal handoff type for the composed-only keys; ObjectTimeline.test.tsx's { title, date } items re-spelled time); the two .extend → .safeExtend test call sites; content/docs/plugins/plugin-timeline.mdx where its documented shapes change; tests beside them; one minor changeset for @object-ui/types (plus @object-ui/plugin-timeline if its published source changes) (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (TIER_DEFAULT, objectstack scripts/pm/dispatch-gates.mjs:12534 at 0d3ec47137; --tier refuses objectui paths)
    Clause-②: yes
    Thread-read: 5857468846
    Serial constraints cleared: read 2026-09-27T16:02Z. Draft PR objectui#10714 (objectui#9590) edits data-display.ts / data-display.zod.ts in the ListSchema / ListItem region only (its patches name Timeline zero times, per report 5857222802) ⇒ region-disjoint; merge main after it lands. No other open PR or in-flight claim in this lane names these files. objectui#6170 (stage 2, same file) is pm:blocked and unclaimed.

    Re-claim under the maintainer's ruling 5857468846. Why Clause-②: yes: a published @object-ui/types element type and its authoring mirror narrow (unknown element keys and cross-shape elements become refused).

    domain:ui seat #1 · claim · 2026-09-27T16:02Z


    Generated by Claude Code

  7. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 6356,
      "status": "done",
      "branch": "claude/issue-6356-timeline-item-element",
      "pr": "https://github.com/objectstack-ai/objectui/pull/10822",
      "session": "session_01DuWo5bdP9SdVebamn99GGk — dev subagent (mode:subagent); the seat's session id, as the harness-stamped Claude-Session line carries it",
      "premise_still_valid": true,
      "summary": "Implemented the maintainer ruling 5857468846 (Q1 = A, Q2 = C) on objectui base c58b8e4a0, final head 42ef5763f, draft PR #10822. TS: TimelineSchema.items is now an array of TimelineFeedItem (the seven documented keys, title required) or TimelineGanttItem (label required, bars TimelineGanttItemBar with all-optional keys), item and bar variant = the documented five (TimelineItemVariant); arms are closed, so { title, date } is TS2353. zod: TimelineSchema stays a ZodObject and ends in .superRefine(timelineItemsFitVariant), which judges each element against the arm variant selects (absent => vertical): required key present, no key of the other arm; cross-shape is refused at the public validate door (safeValidateSchema answers custom @ items.0.title / items.0.label / items.0.items for a gantt row on a vertical timeline). The node SHAPE declares both arms' keys (optional) rather than an element-level z.union, because a union was measured to fold objectui#7164/#7365's pinned paths into one invalid_union at items.0; the strict face therefore names meta/group/color/startDate/endDate/_data/date by name (unrecognized_keys at items.0) and now accepts the schema-catalog documents it refused before. The composed-only keys are typed only in plugin-timeline/src/renderHandoff.ts (not exported), which is TimelineRenderer's prop type. Zone 2: all four assumptions hold as measured (details in PR body); superRefine leaves ZodObject class, .shape, DataDisplaySchema dispatch, the parity ratchet and the timeScale/body/children tombstones intact; .extend over an existing key throws, both control sites moved to .safeExtend. PR objectui#10714 was still an open draft at merge time, region-disjoint. ObjectTimeline.test.tsx items re-spelled time and typed, so the compiler now refuses date there. Choices beyond the ruling's letter are listed for the Clause-② contract review in the PR body (registered mirror pairs 159 -> 162, four new TS exports and three zod exports, requiredness kept to title/label only, bar dates = string | finite number | Date per objectui#6781, bar declared as a type alias).",
      "tests": "All heavy runs via os-verify-lock.sh, exit codes by redirect-then-status, on head 42ef5763f unless noted. (1) pnpm --filter @object-ui/types type-check -> exit 0 (tsc + tsconfig.examples + tsconfig.test, the last judges the parity ratchet and the new @ts-expect-error pins). (2) pnpm --filter @object-ui/types build -> exit 0; dist/data-display.d.ts carries TimelineFeedItem. (3) pnpm --filter @object-ui/plugin-timeline type-check -> exit 0 (reads @object-ui/types from that rebuilt dist; its closure was built first with turbo run build --filter=@object-ui/plugin-timeline... --concurrency=2, 11/11 tasks). (4) pnpm exec vitest run packages/types/ packages/plugin-timeline/ --maxWorkers=2 -> 'Test Files 280 passed (280)', 'Tests 5722 passed (5722)', exit 0. (5) REVERSE CHECK across the package boundary via objectstack scripts/ablation-replace.mjs (wrap mode): planting date on the typed item in ObjectTimeline.test.tsx -> plugin-timeline tsc -p tsconfig.test.json: 'error TS2353: Object literal may only specify known properties, and 'date' does not exist in type 'TimelineFeedItem | TimelineGanttItem'', child exit 2; mutation landed (anchor 1 -> 0, blob a6f953ca63f1 -> af4a03fb097c); restored blob == HEAD a6f953ca63f1, git diff HEAD empty. (6) ABLATION via ablation-replace.mjs: '}).superRefine(timelineItemsFitVariant);' -> '});' in data-display.zod.ts (anchor 1 -> 0, blob ee4326c02190 -> 02f2aa66b150) -> vitest on timeline-item-element-6356.test.ts: 'Failed Tests 10', exactly the refinement-dependent pins (cross-shape x5, required keys, strict-clone cross-shape, DataDisplaySchema dispatch, safeExtend keeps refinement, TS cross-shape validate leg); the strict by-name and vocabulary pins stayed green as expected; child exit 1; restored blob == HEAD ee4326c02190, git diff HEAD empty. Direction observed = the usual one (turns red). The pin file imports ../zod/data-display.zod.js relatively, so no dist/ is on the ablation's resolution path. (7) check:doc-snippets after its declared --build-filter closure (turbo build exit 0) -> 'Semantic phase: 676 of 676 block(s) judged, 0 failed.' exit 0; check:doc-examples exit 0. (8) eslint (root config, as each package's lint script runs it) over the 12 changed .ts/.tsx files, --format json -> 12 files, 0 errors, 80 warnings (warnings are not ratcheted; 2 are the new z.ZodType-any-any annotation in the 7365 control); population = the 12 files of git diff --name-only c58b8e4a0 HEAD filtered to ts/tsx, count read from the JSON output; the config enables no type-aware linting, so this diff cannot move any untouched file's verdict. Final tree after all legs: git status --porcelain = 0 lines.",
      "gates": [
        "pnpm --filter @object-ui/types type-check -> (inside verify2 under the lock) exit 0; lock line 'os-verify-lock: VERDICT command-exit 0 · held the lock 147s (2m27s) · waited 241s (4m01s)' @ 42ef5763f",
        "pnpm --filter @object-ui/types build -> exit 0 @ 42ef5763f",
        "pnpm --filter @object-ui/plugin-timeline type-check -> exit 0 @ 42ef5763f",
        "pnpm exec vitest run packages/types/ packages/plugin-timeline/ --maxWorkers=2 -> 'Test Files 280 passed (280)' / 'Tests 5722 passed (5722)' -> exit 0 @ 42ef5763f",
        "turbo run build --filter=@object-ui/plugin-timeline... --concurrency=2 -> 'Tasks: 11 successful, 11 total' -> exit 0 (verify round 1, 4a07d05d5; src unchanged since except one test annotation)",
        "pnpm check:doc-snippets (after turbo build over its --build-filter, exit 0) -> 'Semantic phase: 676 of 676 block(s) judged, 0 failed.' -> exit 0; lock line 'os-verify-lock: VERDICT command-exit 0 · held the lock 218s (3m38s) · waited 47s' @ 42ef5763f",
        "pnpm check:doc-examples -> 'Every covered `@example` compiles, or fails exactly as its ledger row declares.' -> exit 0 @ 42ef5763f",
        "pnpm check:control-bytes -> '✅  check-control-bytes: OK (scanned 8995 tracked text file(s); skipped 85 binary).' -> exit 0 @ 42ef5763f",
        "pnpm check:new-line-citations -> 'VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0' -> exit 0 @ 42ef5763f",
        "pnpm check:test-path-roots -> '✅  check-test-path-roots: OK' -> exit 0 @ 42ef5763f",
        "pnpm check:handler-key-reads -> 'OK  105 arm(s), 211 registration(s) ...' -> exit 0 @ 42ef5763f",
        "pnpm check:doc-fences -> '✅  check:doc-fences — every TypeScript block in 227 document(s) is fenced ...' -> exit 0 @ 42ef5763f",
        "pnpm check:doc-types -> '✅  Every documented component type is registered.' -> exit 0 @ 42ef5763f",
        "pnpm check:doc-example-ids -> '✅ 414 real reference(s) all resolve in the catalog registry.' -> exit 0 @ 42ef5763f",
        "pnpm check:doc-example-readers -> 'OK  79 documented symbol(s) ...' -> exit 0 (@ 42ef5763f tree, run once)",
        "node scripts/check-changeset-presence.mjs -> '✅  12 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s): .changeset/6356-timeline-item-element.md.' -> exit 0 @ 42ef5763f",
        "node scripts/check-changeset-no-major.mjs -> '✅  No changeset declares a `major` bump.' -> exit 0 @ 42ef5763f",
        "pnpm check:changeset-claims -> report-only; went-false requests on the pending objectui#7164 / objectui#7365 changesets answered in this PR's changeset text; self-contradiction reading '✅  Every package declared ... is either not negated' -> exit 0 @ 42ef5763f",
        "pnpm check:component-surface-parity -> 'component-surface-parity: REPORT-ONLY (objectui#4631) -- exits 0 with findings.' -> exit 0; its one timeline row (plugin-timeline:timeline.scale) exists on base, only its line moved",
        "node scripts/check-governed-queue-guard.mjs --test (14 changed paths) -> '✅ NOT GOVERNED — 14 path(s) checked against 5 governed surface(s); none matched.' -> exit 0",
        "eslint --format json over the 12 changed ts/tsx files -> 12 files, 0 errors -> exit 0 @ 42ef5763f",
        "CI on PR #10822 head 42ef5763f -> in_progress at report time (42 check runs: 22 success, 3 skipped, 17 in progress); not waited on, per the role file"
      ],
      "line_budget": "n/a — no skills/** or other governed file touched (governed-queue-guard --test: NOT GOVERNED, 14 paths)",
      "deviations": [
        "Zone 3 route said 'closed objects in both faces'. The TS arms are closed; the zod arms stay .passthrough() on the TOLERANT face and are closed only by the strict authoring face. Reason: the ruling says 'the strict face refuses them when authored' and the retirement posture names the strict face; closing the tolerant face would also refuse undeclared keys at objectui validate today, which no ruling ordered.",
        "The zod element in the node SHAPE is not a z.union of the two arms but an object declaring both arms' keys (optional), with the static type asserted to the TS union (the NODE's accept set, enforced by the refinement). Measured reason: an element-level union folds objectui#7164 / #7365's path-pinned refusals into one invalid_union at items.0 and names an unknown key twice on the strict face. Documented in the zod docblock; same assertion spelling as nodeUnion in base.zod.ts. Flagged for the Clause-② contract review.",
        "Registered TimelineFeedItemSchema / TimelineGanttItemSchema / TimelineGanttItemBarSchema as exported mirror pairs (zod-mirror-parity.test.ts MIRRORS + Declared, EXPECTED_MIRROR_PAIRS 159 -> 162) and put them on the ./zod barrel; added TimelineFeedItem / TimelineGanttItem / TimelineGanttItemBar / TimelineItemVariant to the root TS barrel. Not named by the brief; it is what keeps the element keys under the parity ratchet despite the static assertion. Public-surface growth, flagged for the contract review.",
        "Brief's gate list named type-check for all dependents of @object-ui/types; declared narrowing: only @object-ui/types and @object-ui/plugin-timeline were type-checked locally. Evidence: TimelineSchema is imported only by plugin-timeline (git grep over the repo), and the only other TS type: 'timeline' literals are list-view types (six hits in apps/console, core, plugin-view tests). CI runs the full set.",
        "The four commits (6929275cc, 955a457d5, 4a07d05d5, 42ef5763f) carry the harness's model-named Co-Authored-By trailer, which the role file says to report and not imitate (trailers model-free). Reported, not rewritten: history is not amended and objectui forbids force-push.",
        "Two lock acquisitions returned exit 99 (queue-timeout) behind another seat's 30+ minute locked vitest sweep (holder pid 31779, issue-10594 verify.sh, alive and CPU-busy); the slot objectui-6356-dev kept my place and the later runs acquired. Heavy runs were launched in the background and waited on in the foreground with tail --pid, because lock wait plus run exceeded one foreground call.",
        "Pending changesets for objectui#7164 and objectui#7365 (same release) state that row and bar keys stay undeclared and feed timelines are untouched; not edited (they are other changes' records). This PR's changeset names what it supersedes instead.",
        "TimelineGanttItemBar is a type alias, not an interface: the parity ratchet read the gantt pair's items as narrower-than-declared only because an interface has no implicit index signature against the passthrough bar's input type."
      ],
      "files_changed": [
        ".changeset/6356-timeline-item-element.md",
        "content/docs/plugins/plugin-timeline.mdx",
        "packages/plugin-timeline/src/ObjectTimeline.test.tsx",
        "packages/plugin-timeline/src/ObjectTimeline.tsx",
        "packages/plugin-timeline/src/renderHandoff.ts",
        "packages/plugin-timeline/src/renderer.tsx",
        "packages/types/src/__tests__/timeline-item-element-6356.test.ts",
        "packages/types/src/__tests__/timeline-items-bar-shape-7365.test.ts",
        "packages/types/src/__tests__/timeline-items-row-shape-7164.test.ts",
        "packages/types/src/__tests__/zod-mirror-parity.test.ts",
        "packages/types/src/data-display.ts",
        "packages/types/src/index.ts",
        "packages/types/src/zod/data-display.zod.ts",
        "packages/types/src/zod/index.zod.ts"
      ],
      "mcp_calls": "0 — no MCP GitHub tool called",
      "api_writes": "3 relay writes as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectui/pulls (draft PR #10822, via with-fleet.sh --via dispatch, run 36336820064); POST /repos/objectstack-ai/objectui/issues/10822/assignees (os-steve, via label-write.mjs, run 36336860100, read back MATCHES); POST /repos/objectstack-ai/objectui/issues/6356/comments (this report, via post-stamped.mjs). Plus 3 git push to the branch (not REST). Zero label writes. Reads only otherwise: REST GET issue 6356 and its comments, pulls/10714 (twice), pulls?head=, pulls/10822, commits/42ef5763f/check-runs.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: a · reach: public door — safeValidateSchema (what objectui validate runs) refuses the packages/plugin-timeline/README.md 'Schema-Driven Usage' document exactly as written (invalid_union @ type) and accepts it respelled type: 'timeline' · evidence: the README spells the node component: 'timeline', and README.md is in the package's published files list · dedupe words: plugin-timeline README component, Schema-Driven Usage component timeline, README component instead of type",
        "carrier: 承接者:无 · packages/components/src/renderers/complex/TIMELINE.md lists three gantt time scales where six exist (carried from the first dev's report) · noted in PR Acceptance notes, not filed"
      ]
    }

    Generated by Claude Code

  8. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    REWORK (round 1) — PR objectui#10822 · domain:ui seat #1 · 2026-09-27T17:47Z

    session_01DuWo5bdP9SdVebamn99GGk (os-steve). Same claim 5857481990, same branch; the patch round went to the original dev.

    Contract review 5858250761 (head 42ef5763f, Served-tier: CONTRACT_REVIEW_TIER) is FAIL on one defect. Everything else was judged conforming to ruling 5857468846: a 50-row probe on head and base found the TS face and the tolerant zod face pure narrowings; the strict face refuses the composed keys by name; the refinement holds for every variant; and the new exports are necessary for the ratchet.

    • Defect: .changeset/6356-timeline-item-element.md says of @object-ui/plugin-timeline 「Anything that type-checked against it before still does.」 That is false. TimelineRenderer is public and its schema prop narrowed, so an element literal carrying an undeclared key (for example date) was accepted at base and is TS2353 at head. That sentence publishes verbatim into the CHANGELOG. Fix: spell the break; the bump stays minor.
    • Same edit: 「a bar still accepts keys it does not declare」 is true of the tolerant face only, so it gets qualified.

    Not this PR's defects, noted: safeValidateSchema still accepts { title, date } on the tolerant face until objectui#5250 wires the strict face; the README component: 'timeline' refusal is filed as objectui#10824.

    domain:ui seat #1 · REWORK · 2026-09-27T17:47Z


    Generated by Claude Code

  9. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 6356,
      "status": "done",
      "branch": "claude/issue-6356-timeline-item-element",
      "pr": "https://github.com/objectstack-ai/objectui/pull/10822",
      "head": "11725ece913208630276727e5d4a1bc9914702af",
      "round": "PATCH ROUND (REWORK, round 1) — contract review 5858250761 (VERDICT FAIL on one changeset sentence), same branch, same claim 5857481990",
      "session": "session_01DuWo5bdP9SdVebamn99GGk — dev subagent (mode:subagent); the seat's session id",
      "premise_still_valid": true,
      "summary": "Fixed the one defect contract review 5858250761 found, in .changeset/6356-timeline-item-element.md only; no code change. (1) The @object-ui/plugin-timeline paragraph no longer says 'Anything that type-checked against it before still does.' It now states the break. The paragraph is labelled breaking at compile time, shipped as a minor. It says TimelineRenderer is a public export and its schema prop is now the renderer's input type: the two declared item shapes plus the composed keys color / group / meta / startDate / endDate / _data, with any string as a composed item's variant. An object literal passed to the prop whose item carries a key neither shape declares is now TS2353, with the example schema={{ type: 'timeline', items: [{ title: 'T', date: 'x' }] }} and the fix (a feed item's date is time). It lists what still type-checks: any value typed TimelineSchema, a non-literal items array (e.g. string-keyed records in a variable), empty-object items, and the composed keys. (2) The supersession paragraph now scopes 'a bar still accepts keys it does not declare' to the tolerant TimelineSchema and says the strict authoring schema refuses them by name. The minor bump on both packages is unchanged. Every claim in the new paragraph was measured with a tsc probe on this head before it was committed (see tests). origin/main 9a5f99880 was merged first with a clean text merge, no conflicts. Main had touched @object-ui/types (field-types.ts, form.ts, index.ts, select-option.ts, objectql.zod.ts, one new test), so the types type-check and tests were re-run and are green. The PR body does not repeat the false sentence; its only TimelineRenderer mention is accurate, so no body replacement is needed. The body's heading 'Verification — final head 42ef5763f' now names the pre-merge head. The seat may retitle it; this round's readings are in the gates field.",
      "tests": "(1) CLAIM PROBE, BORN-FALSE guard, run under os-verify-lock on the head sources. A scratch tsc project (strict, bundler resolution, @object-ui/types path-mapped to head packages/types/src/data-display.ts) declares prop(schema: TimelineRenderSchema), the type TimelineRenderer's schema prop is declared with. Under @ts-expect-error: a fresh literal with an item { title, date } and a gantt bar { title, progress }. Plain lines that must compile: a value typed TimelineSchema; items as a Record-of-string-to-unknown array variable; items [{}]; a feed item carrying variant 'todo', color, group, meta chips, startDate, endDate and _data; a gantt bar with startDate 0 and endDate new Date(). Also a base-control line: the base element type restated verbatim accepts { title, date }. Result: tsc exit 0, so both directives fired (an unfired one is TS2578) and every compile case compiled. Positive control: the same file with the two directives removed gives tsc exit 2 with exactly two errors, 'TS2353 ... 'date' does not exist in ty…' and 'TS2353 ... 'progress' does not exist', and nothing else. The first probe attempt excluded @types/react and gave exit 2 on three 'Cannot find namespace React' errors inside data-display.ts with zero errors in the probe file; I re-ran with React types for the clean reading above and did not count the first attempt as a result. (2) pnpm --filter @object-ui/types type-check (tsc + examples + test program) -> exit 0, zero error TS lines. (3) pnpm exec vitest run packages/types/ packages/plugin-timeline/ --maxWorkers=2 -> 'Test Files 281 passed (281)', 'Tests 5729 passed (5729)', exit 0. Both ran under os-verify-lock, 'VERDICT command-exit 0 · held the lock 133s (2m13s) · waited 80s (1m20s)', on head 11725ece9 with git status clean before and after.",
      "gates": [
        "node scripts/check-changeset-presence.mjs -> '✅  12 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s): .changeset/6356-timeline-item-element.md.' -> exit 0 @ 11725ece9",
        "node scripts/check-changeset-no-major.mjs -> '✅  No changeset declares a `major` bump.' -> exit 0 @ 11725ece9",
        "pnpm check:changeset-claims -> born-false PASS rows; '✅  Every package declared across those 1 body(ies) is either not negated'; report-only went-false list unchanged in kind (32 pending changesets name files this change touches) -> exit 0 @ 11725ece9",
        "pnpm check:pending-changeset-literals -> '✅  No test source names a pending changeset.' -> exit 0 @ 11725ece9",
        "pnpm check:control-bytes -> '✅  check-control-bytes: OK (scanned 9020 tracked text file(s); skipped 85 binary).' -> exit 0 @ 11725ece9; plus grep -naP control-byte self-scan of the changeset: 0 lines",
        "pnpm --filter @object-ui/types type-check -> exit 0 (under lock: 'os-verify-lock: VERDICT command-exit 0 · held the lock 133s (2m13s) · waited 80s (1m20s)') @ 11725ece9",
        "pnpm exec vitest run packages/types/ packages/plugin-timeline/ --maxWorkers=2 -> 'Test Files 281 passed (281)' / 'Tests 5729 passed (5729)' -> exit 0 @ 11725ece9",
        "tsc claim probe (scratch project over head sources) -> main exit 0; directive-removed control exit 2 with exactly the two expected TS2353 errors (under lock, 'VERDICT batch-last-exit 0', parts' exits recorded separately: main-exit=0, control-exit=2)",
        "NOT MEASURED this round: @object-ui/plugin-timeline type-check, reason: the merge did not touch plugin-timeline and this round changed no code; its build closure was not rebuilt in the fresh worktree. Last green reading: exit 0 @ 42ef5763f (round 1). CI on 11725ece9: not read, not waited on."
      ],
      "line_budget": "n/a — no skills/** or other governed file touched",
      "deviations": [
        "The merge commit 2854a0c32 is a --no-ff merge of the pinned sha 9a5f99880, not a fast-forward, because the branch had its own commits. It carries the harness's model-named Co-Authored-By trailer, as do the fix commit 11725ece9 and the four round-1 commits. Reported, not rewritten (no force-push).",
        "The first tsc probe attempt excluded @types/react, so it ran with errors outside the probe file (exit 2). It was re-run with React types and the clean run is the one reported; the first attempt is not counted as a reading.",
        "I edited only the two sentences the review named. The PR body was not edited, as instructed. Suggested follow-up for the seat, optional and not a defect: the body's 'Verification — final head `42ef5763f`' heading now names the pre-merge head (the new head adds merge 2854a0c32 and changeset-only commit 11725ece9), and the body's 'What changes for an author' table has no row for the TimelineRenderer prop. If the seat wants one, its exact text is: '| a fresh literal passed to `TimelineRenderer`'s `schema` prop whose item carries an undeclared key (e.g. `date`) | compiles | TS2353 — the prop is the renderer's handoff type; values typed `TimelineSchema` and the composed keys still type-check |'."
      ],
      "files_changed": [
        ".changeset/6356-timeline-item-element.md"
      ],
      "mcp_calls": "0 — no MCP GitHub tool called",
      "api_writes": "1 relay write as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectui/issues/6356/comments (this report, via post-stamped.mjs). Plus 1 git push (42ef5763f..11725ece9, fast-forward, not REST). Zero label writes, zero PR-body writes. Reads: REST GET issues/comments/5858250761 (the review), issues/6356/comments, pulls/10822.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  10. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    ACCEPT — PR objectui#10822 · domain:ui seat #1 · 2026-09-27T18:19Z

    session_01DuWo5bdP9SdVebamn99GGk (os-steve), the dispatching seat and reviewer of record. Checked against GitHub and origin/main, ⛔ not against the reports.

    Checklist

    • Shape: draft → main; body opens Fixes #6356, the only closing keyword; Clause-②: yes at line start.
    • Scope: 14 files, +979 / −124, inside the surface the ruling's re-dispatch named: the TimelineSchema region of packages/types/src/data-display.ts and its zod mirror (closed TimelineFeedItem / TimelineGanttItem arms, title / label required, the node-level superRefine that judges each element against the arm variant selects), packages/plugin-timeline/src (the internal handoff type in renderHandoff.ts, the renderer prop, ObjectTimeline.test.tsx re-spelled off the authored date), the docs page, pins re-decided (7164, 7365, mirror parity), not deleted, a new pin for this card, and one changeset (@object-ui/types minor, @object-ui/plugin-timeline minor).
    • Ruling fit (5857468846, Q1 = A, Q2 = C): the authored feed element declares the documented seven; the five composed keys live on the renderer-internal handoff type, which is not exported by name; the published TimelineSchema stays an interface and a ZodObject.
    • Governed-surface predicate on the 14 paths: NOT governed (AGENTS.md lit control: exit 3). Checks on head 11725ece9: 43 check-runs, 40 success, 3 skipped, 0 failure. mergeable_state clean at this read.
    • Contract review: record 5858250761 (head 42ef5763f) FAILED on one changeset sentence, a compile break at TimelineRenderer's prop the text denied; patch round, dev report 5858397783. Delta record 5858491252 — Served-tier: CONTRACT_REVIEW_TIER, head 11725ece913208630276727e5d4a1bc9914702af (the current head), independence pair present, PASS. Every sentence of the rewritten paragraphs measured true; the merge of origin/main touched none of the PR's timeline surface, and no round-1 judgment moves.
    • Deviation 1 (the zod arms stay .passthrough() on the tolerant face; unknown keys are refused by name only on the strict face) was judged to conform to the ruling's letter. ⇒ safeValidateSchema still accepts { title, date } today; the by-name refusal lands at objectui validate when objectui#5250 wires the strict face. The changeset says so.

    Out-of-scope findings, one line each

    • packages/plugin-timeline/README.md spells the node component: 'timeline', which validate refuses — already filed as objectui#10824.
    • The renderer prop is slightly wider than the prose's summary (a handoff feed item may omit title, and time is unknown); relative to the base it is still a narrowing, and the renderHandoff.ts docblock states it. No change.
    • Tenant-stored timeline items carrying meta / group keys: NOT MEASURED by anyone; the box's confidence gap says so.

    Landing: not governed, green, review PASS ⇒ pr_ready then automerge_enable through the relay, in this act. On merge the seat files the objectstack follow-up for object-timeline.items (Q3: objectui first, then the spec), Blocked-by: none, and objectui#6170's serial constraint on this PR clears.

    domain:ui seat #1 · ACCEPT · 2026-09-27T18:19Z


    Generated by Claude Code

  11. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    Contributor

    Landed — PR objectui#10822 · domain:ui seat #1 · 2026-09-27T18:36Z

    session_01DuWo5bdP9SdVebamn99GGk (os-steve). Merged through the queue as 3a5817f07, verified by content on origin/main against the pre-merge parent:

    reading pre-merge parent origin/main
    export interface TimelineFeedItem / TimelineGanttItem in packages/types/src/data-display.ts 0 2
    timelineItemsFitVariant in packages/types/src/zod/data-display.zod.ts — 4
    packages/plugin-timeline/src/renderHandoff.ts and .changeset/6356-timeline-item-element.md absent present

    domain:ui seat #1 · landed · 2026-09-27T18:36Z


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions