Repository navigation
finding(plugin-timeline): the timeline renderer reads seven keys TimelineSchema does not declare, and declares three it never reads #6170
Description
Activity
- addeddomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repoobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
on Aug 24, 2026 yinlianghui-tw commented
on Aug 24, 2026 CollaboratorAuthorMore actionsPM triage — ⛔ escalated as
needs-user-decision, not dispatched. And it completes a family of four.Triaged by the
domain:devx@ objectui execution seat (#5748), PM sessionsession_019b5UBNMtTzKbVtZZGvFuxe. ⛔ Not claimed.Why there is no dispatchable half here, unlike its siblings
I split #6172 and #6169 tonight because each had a piece that was simply false and needed no ruling — a wrong closure claim, a row naming a key that reaches nothing. I looked for the equivalent here and there isn't one.
⭐ The docs table is not the thing that's wrong. It agrees with the renderer and with the registration's own
inputsmetadata —variant/items/dateFormat/timeScaleare what authors write, what the designer offers, and whatTimelineRendererreads. It is the exported type that disagrees with all three. So "fix the docs" would make the page wrong, and "fix the type" is the contract decision. There is no cheap half.The decision, stated so it can be made once
Three vocabularies are live for one node:
source keys docs table + registry inputs+ renderer readsvariant,items,dateFormat,timeScale,rowLabel,minDate,maxDate,classNameTimelineSchema(data-display.ts:1204)type,events(required),orientation,position@objectstack/specui/TimelineConfig.jsonaxis key is scale⭐ And the renderer already reads
schema.scale ?? schema.timeScale— so it is quietly bridging vocabularies 1 and 3 at runtime while being annotated against vocabulary 2. Whetherscale/timeScaleis an alias to keep, a migration to finish, or a bug depends on the same ruling. ⛔ Not guessable.TimelineSchema'sevents,orientationandpositionhave no read point inpackages/plugin-timelineat all.⭐ The mechanism, which is the fourth sighting tonight
The reason none of this reddens anything:
BaseSchemacarries an index signature (base.ts:318), so every key reads asanyandschema: TimelineSchemaconstrains nothing.tsccannot see the divergence.That exact fact has now decided four separate matters in this lane today:
- It falsified my own option-2 ruling on finding(docs): component reference pages declare their own
interface, so the doc-snippet gate can never see the props they invent — 4 measured on the 5 pages #6122 PR 2 touched alone #6143 — I claimed annotating against an exported type makes a page structurally incapable of teaching a key the type lacks. It does not, because 73 of 87 component types are open. - I re-confirmed it on
FormFieldthis hour while reviewing finding(types): three more type names are declared twice with disagreeing shapes —FormField,MarkdownSchema,KanbanSchema— and one doc assertsFormFieldis declared once #6172's PR: 23 named members plus an index signature. - It is why No gate judges prose that restates a declared surface — three docs pages measured wrong about
PageHeaderPropsin one day #6086 could not name the shape on several property tables. - It is why this card's annotation is decorative.
⚠️ So "annotate it against the exported type" is not available as a remedy anywhere in this family, and anyone reaching for it should be pointed here first.The family — four cards, one decision, and they should be ruled together
All four are the authoring surface and the exported type disagree, with nothing mechanical able to notice:
card shape of the disagreement status #6172 one name, several shapes ( FormField23 vs 29,MarkdownSchema,KanbanSchema)needs-user-decision; docs half landed as PR #6185#6155 KanbanCard/KanbanColumndeclared four times, published copies disagreepm:blockedbehind #6172#6169 a shape with no name at all — 11 keys in an anonymous inline intersection at the registration site needs-user-decision; docs half dispatched#6170 (this) a named type that matches neither the authors nor the renderer, plus a third spec vocabulary needs-user-decision⚠️ They are not duplicates — one name/many shapes, no name at all, and a name that fits nothing are different failures — but the remedy space is shared: rename, re-export one copy from the other, lift the real shape into the exported type, or gate against a second declaration. Ruling them one at a time is how the tree ends up with four conventions, which is the outcome I have been sequencing #6155 behind #6172 to avoid.⛔ And one option is already ruled out for the whole family by measurement, so nobody should spend time on it: a package qualifier does not disambiguate, because three of the four
KanbanCarddeclarations are inside@object-ui/plugin-kanbanitself.Labelling
needs-user-decisionand cross-linking the four. ⛔ Do not dispatch any of the contract halves until the family is ruled.
Generated by Claude Code
- It falsified my own option-2 ruling on finding(docs): component reference pages declare their own
Triage(决策箱勤务两笔):① 摘
finding—— 与needs-user-decision是矛盾态(finding = 待首次定级,而本卡已定级入箱);② 补标准四棱卡面块:① 项目长远合理性:三套词汇一物三名(exported type / docs+registry+renderer / spec
TimelineConfig.json的scale)。收敛到 renderer+registry+docs 已经一致的那套、退役 type 的死键(events/orientation/position零读点)是缩小特例;继续三名并存则每次语义裁决成本 ×3。
② 实际业务拉动:作者照 docs 写今天能用(renderer 靠 index signature 读到),损伤在于 exported type 教的三个键写了白写,且tsc对整个分歧失明 —— 下一个 #6086 类缺陷没有机械裁判。
③ 防 AI 写代码犯错:index signature 吞掉唯一的类型裁判;把 type 对齐 renderer 实读集后,声明面重新可判。
④ 创业阶段不扩散需求:对齐既有事实,不加新能力;scale已是 renderer 的优先拼法,timeScale按 alias 退役路线走。
推荐:TimelineSchema对齐 renderer 实读集(scale为准、timeScale走 alias 退役),events/orientation/position走 ADR-0049 enforce-or-remove;docs 表随之核对。
置信缺口:specui/TimelineConfig.json与 objectui type 之间谁是 producer(权威边界)未在本席测定 —— 若裁定 spec 侧为准,半边落domain:spec需拆卡。<!-- os-decision-facets -->
Generated by Claude Code
Maintainer ruling (2026-08-25, batch adjudication close-out, decision 甲/A1; verbatim: 「同意」): the family rule adopted on #6172 applies — the exported type aligns to the measured authored+read set. Per the facet-block recommendation on this card, now ruled:
TimelineSchemaaligns to what authors write andTimelineRendererreads (variant,items,dateFormat,rowLabel,minDate,maxDate+ base).scaleis canonical (it is specui/TimelineConfig.json's axis key AND the renderer's preferred read);timeScalegoes the alias-retirement route, not a silent second spelling.events/orientation/position(zero read points inpackages/plugin-timeline) go the ADR-0049 enforce-or-remove route.- Docs table re-verified against the converged type in the same stroke.
- Producer-boundary check (binding): measure whether spec's
TimelineConfig.jsonitself must move to stay consistent; if yes, that half forks to the spec lane (this repo does not edit@objectstack/spec) — do not smuggle it.
State transition:
needs-user-decision→pm:queue. Reroutedomain:devx→domain:ui(fix lands inpackages/types+packages/plugin-timeline). Clause-②: yes — published type surface changes, contract-review tier at dispatch. Serial with the #6172/#6169 family claims if shared files emerge.
Generated by Claude Code
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removeddomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repoobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
on Aug 25, 2026 os-support-ai commented
on Aug 25, 2026 CollaboratorMore actionsClaim —
domain:ui@ objectui execution seatPM session
session_011SfZeFWrhGLHmfq61xbz4q(os-support-ai). Branchclaude/issue-6170-timeline-schema-drift. Labelspm:queue→pm:dispatchedin the same stroke as the assign.Shadow check run BEFORE dispatch (the step I skipped on #5993 this round and am not skipping again): all 247 open objectui issues enumerated and filtered locally — this is the only open card naming timeline. No sibling to converge with.
Premise confirmed on
origin/main@79ebf30d1TimelineSchema(packages/types/src/data-display.ts:1204) declares, beyondtype, exactly three keys:events,orientation,position. Aschema.*census acrosspackages/plugin-timeline/srcreturns ~21 distinct reads — and none of them is any of those three. So the card's "declares three it never reads" is exact, and the undeclared-read half is real.⭐ A root cause the card does not name — stated as a HYPOTHESIS for the dev to test, not a ruling
There appear to be two different timelines in this repo, and the renderer may simply be typed against the wrong one:
shape keys presentational TimelineSchema,types/src/data-display.ts:1204events/orientation/positionobject-bound ListViewTimelineConfig=TimelineConfigfrom@objectstack/spec/ui& { dateField?, [key: string]: any },types/src/objectql.ts:116startDateField,titleField, …renderer.tsx:250typesschemaasTimelineSchemawhile reading object-bound keys, andObjectTimeline.tsx:52patches over the gap with a localTimelineSchema & { … }intersection that re-declares the missing keys by hand.If that hypothesis holds, the honest fix is re-pointing the type at the shape the code already consumes — mechanical — rather than widening a published presentational type to absorb object-bound keys. ⛔ But it is a hypothesis: I measured the shapes, not the intent, and the dev is instructed to test it and report rather than assume it.
⛔ Two boundaries
packages/specis off limits.TimelineConfigis the spec's, andpackages/specbelongs to thedomain:specseat alone. Consuming it is fine; changing it is a different lane — report, do not touch.- The
[key: string]: anyonListViewTimelineConfigis NOT this card's. That index-signature collapse is the same class already tracked by finding(types): BaseSchema's[key: string]: anyleaves every component schema open, so a "declare the surface" fix can never reject a misspelled TOP-LEVEL key #5155 and finding(types): ObjectViewSchema'stableandformslots declare ZERO properties — the same Omit-under-index-signature collapse as #6151, in property position #6269. ⛔ Do not fix it here.
If the fix turns out to require widening or narrowing a published type's accept set, that is Clause-② and a fork to report, not a call to make mid-run.
packages/plugin-timeline(+ possiblypackages/types) — disjoint from the four concurrent claims: #5034plugin-form, #6275core, #5977plugin-map, #5993components.Type
Task; dispatched under the lane's Bug-first order with the reason logged: the gradedpm:queueBug tier is drained.
Generated by Claude Code
17 remaining items
- added and removed
on Sep 27, 2026 objectstack-fleet commented
on Sep 27, 2026 ContributorMore actionsClaim: PM loop round 1
Session:session_01DuWo5bdP9SdVebamn99GGk
Account:os-steve(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-6170-timeline-dead-keys-retire
Worktree:objectui-issue-6170
Domain:domain:ui
Seat:domain:ui#1
File surface:TimelineSchema'sevents/orientation/positionmembers inpackages/types/src/data-display.tsand their zod twin inpackages/types/src/zod/data-display.zod.ts— retired as?: nevertombstones in lockstep, in the house form oftimeScale— plus the rows ofcontent/docs/plugins/plugin-timeline.mdxthat name them, the pins that assert them (the mirror-parity ratchet included), and one@object-ui/typesminorchangeset with the break spelled out (stop on breach; explain in the report)
Container & model:S,mode:subagent,model: opus(TIER_DEFAULT, objectstackscripts/pm/dispatch-gates.mjs:12534at0d3ec47137)
Clause-②: yes
Thread-read: 5857169230
Serial constraints cleared: read 2026-09-27T18:50Z at objectuiorigin/mainfda49e557, which carries objectui#10822 (theTimelineSchemaelement change that held this card). Of the 12 open PRs, one touches these files: objectui#10714 (seat #2, objectui#9590) editsListSchema/ListItem(data-display.ts:326-460, zod:167-210). This card editsTimelineSchema(:2861-3001, zod:1262-1304) ⇒ region-disjoint; mergemainif it lands first. objectui#10803 (not dispatched) listsdata-display.tsamong its sites and now also waits on this card.Why
Clause-②: yes: the maintainer's ruling5406604598puts it there (「published type surface changes」). Three declared members of a published type become refused, so the change narrows only.One triage reading corrected by this seat, on
origin/mainfda49e557. Triage's decision5857169230saidTimelineSchema「no longer declaresorientation/position」, which left one key. It still declares both, as live optional members (data-display.ts:2991orientation?: 'vertical' | 'horizontal',:3001position?: 'left' | 'right' | 'alternate'), each@deprecatedwith 「ZERO read points」. Neither is deleted or tombstoned; the zod docblock at:1262says all three 「stay declared and stay mirrored」. ⇒ The remainder is three keys, not one, exactly the three the ruling names (「events/orientation/position… go the ADR-0049 enforce-or-remove route」). No scope beyond the ruling. Triage's execution notes 1, 2 and 4 stand; note 3 resolves to "neither, so tombstone them withevents".domain:uiseat #1 · claim · 2026-09-27T18:50Z
Generated by Claude Code
objectstack-fleet commented
on Sep 27, 2026 ContributorMore actionsos-dev-report
{
"issue": 6170,
"status": "done",
"branch": "claude/issue-6170-timeline-dead-keys-retire",
"pr": "#10840",
"head_sha": "b3f4ecc9313303ef9d07e1b72c3fdeb63b253d2d",
"session": "session_01DuWo5bdP9SdVebamn99GGk — the dispatching seat's id (mode:subagent, so the parent's); the relay stamped every write with it",
"premise_still_valid": true,
"summary": "ADR-0049 stage 2 landed as draft PR #10840 (Fixes #6170, Clause-②: yes). TimelineSchema events / orientation / position are retired as?: neveron the TS face and retirementTombstone() on the zod twin, in lockstep. Each refusal message (== its .describe()) names the replacement: events -> items, orientation -> variant; position has no successor, and its message says the vertical rail is always drawn on the left. TimelineEvent / TimelineEventSchema stay exported and are now orphaned (docblocks say so; not deleted, per Zone 2 #3). PREMISE, re-measured on origin/main fda49e5: the seat's claim correction holds (orientation / position were live optional declarations, so three keys, not one). READERS: none, by three instruments with controls. (1) A type-checker findReferences over the repo-root tsconfig program (762 population files, 4010 in the program) found no reference outside packages/types; the rowLabel control found the renderer read. (2) A runtime probe (real SchemaRenderer + registry, one key varied) found byte-identical HTML on baretimeline(view:timeline) in all three variants, with the variant / item-title controls firing. Onplugin-timeline:timelinethe keys only land as inert stringified root-element attributes through TimelineRenderer's leftover-props spread. (3) Zero identifier reads in plugin-timeline/src. PRODUCERS: none that expects them to render. objectstack origin/main 3f86dc52f (examples, showcase, docs, spec, skills) had zero, with population and matcher controls. objectui had two echoes of the dead declaration, both migrated here: the unpublished packages/types/examples/data-display-examples.json timeline node (all three keys; objectui#6354's stage-2 checklist had already scheduled its replacement), and two{type:'timeline', events: []}snippets in content/docs/api/schema-reference.md. I judged these fixture/docs echoes, not producers, and did not stop — the PM may second-guess that call. TIMING NOTE: npm says the latest @object-ui/types is 17.6.0 (published 2026-08-24T14:44Z, before stage 1 merged), so objectui#6354's restart-when predicate never fired. I proceeded on the binding triage decision 5857169230. The changeset tells consumers that events goes from required straight to refused if their install predates stage 1.",
"tests": "All at head b3f4ecc (the only commit). Seegatesfor each verdict line and exit code. PINS: new packages/types/src/tests/timeline-dead-keys-retired-6170.test.ts (35 tests with timeline-declared-keys). timeline-declared-keys.test.ts was re-decided, not deleted (the still-declared and absent-stays-valid assertions are kept; the vocabulary-refusal test and the orientation / position ts-expect-error lines moved to the new pin).tsc -p tsconfig.test.json --listFilescounts the new pin as compiled = 1. ABLATION, with the prediction stated before the run (13 vitest red in the new pin, timeline-declared-keys green, exactly 6 TS2578 in the new pin, no parity error): I committed first, then restored the OLD declarations of all three keys on BOTH faces via objectstack scripts/ablation-replace.mjs --hold. Every anchor hit x1 -> x0 and every blob changed; on-disk counts were old-events=1, old-orientation=1, old-position=1, zod tombstones-6170=0. A trap restored from HEAD on absolute paths. Observed: vitestTests 13 failed | 22 passed (35)(exit 1), all 13 in the new pin; tsc exit 2 with exactly 6TS2578: Unused '@ts-expect-error' directive, all in the new pin. Restore proven: ts blob c01f3c914c19 == HEAD, zod blob 3e52a35011e1 == HEAD, git diff HEAD empty. No build leg was needed: both instruments resolve ../data-display.js and ../zod/*.js to SOURCE by relative import. REVERSE CHECK on the consumer: I rebuilt types dist (grep shows 3?: nevermembers in dist/data-display.d.ts, 0 old). A temporary TimelineSchema literal with orientation:'horizontal' in plugin-timeline/src then gaveTS2322: Type 'string' is not assignable to type 'undefined'; the probe was removed and git status is clean.",
"gates": [
{
"command": "pnpm --filter @object-ui/types type-check (tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json)",
"verdict": "script name echoed, no diagnostics; os-verify-lock VERDICT command-exit 0",
"exit": 0
},
{
"command": "pnpm exec vitest run --maxWorkers=2 packages/types/ packages/plugin-timeline/ (repo root)",
"verdict": "Test Files 283 passed (283) · Tests 5767 passed (5767)",
"exit": 0
},
{
"command": "pnpm exec vitest run --maxWorkers=2 (73 scripts/tests files that git grep names a touched path, .changeset, content/docs or packages/types/src/tests)",
"verdict": "Test Files 73 passed (73) · Tests 3232 passed (3232)",
"exit": 0
},
{
"command": "pnpm --filter @object-ui/plugin-timeline type-check (after turbo build --filter='@object-ui/plugin-timeline^...', 10/10 tasks)",
"verdict": "no diagnostics",
"exit": 0
},
{
"command": "node scripts/check-changeset-presence.mjs",
"verdict": "✅ 4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/6170-retire-timeline-dead-keys.md.",
"exit": 0
},
{
"command": "node scripts/check-changeset-no-major.mjs",
"verdict": "✅ No changeset declares amajorbump.",
"exit": 0
},
{
"command": "node scripts/check-changeset-fixed.mjs",
"verdict": "✅ All workspace packages are in the changeset fixed group.",
"exit": 0
},
{
"command": "node scripts/check-changeset-claims.mjs",
"verdict": "report-only: self-contradiction reading ✅ (1 changeset, 1 declaring a package); 26 pending changesets name a touched file — the timeline / data-display-examples ones were read, none is falsified (6939-tree-view names the tree-view node only)",
"exit": 0
},
{
"command": "node scripts/check-pending-changeset-literals.mjs",
"verdict": "✅ No test source names a pending changeset.",
"exit": 0
},
{
"command": "node scripts/check-spec-symbol-derivation.mjs",
"verdict": "✅ spec symbol derivation: 1412 files scanned against 5085 spec export names; 18 declared dialects, 0 untriaged collisions in 0 packages.",
"exit": 0
},
{
"command": "node scripts/check-control-bytes.mjs",
"verdict": "✅ check-control-bytes: OK (scanned 9047 tracked text file(s); skipped 85 binary).",
"exit": 0
},
{
"command": "node scripts/check-new-cross-file-line-citations.mjs",
"verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0",
"exit": 0
},
{
"command": "node scripts/check-lucide-icon-record-names.mjs",
"verdict": "OK lucide icon names: 184 authored/declared names reaching 1 record-reading resolver are liveiconskeys",
"exit": 0
},
{
"command": "node scripts/check-doc-component-types.mjs",
"verdict": "✅ Every documented component type is registered.",
"exit": 0
},
{
"command": "node scripts/check-doc-snippet-types.mjs (after the scoped --build-filter build, 35/35 tasks)",
"verdict": "Semantic phase: 678 of 678 block(s) judged, 0 failed.",
"exit": 0
},
{
"command": "node scripts/check-doc-example-types.mjs (same built closure)",
"verdict": "Every covered@examplecompiles, or fails exactly as its ledger row declares.",
"exit": 0
},
{
"command": "node scripts/check-doc-fence-languages.mjs",
"verdict": "✅ check:doc-fences — every TypeScript block in 227 document(s) is fenced ts/tsx/typescript …",
"exit": 0
},
{
"command": "node scripts/check-doc-links.mjs",
"verdict": "Links are valid across 17 scan roots.",
"exit": 0
},
{
"command": "node scripts/check-handler-key-read-sites.mjs",
"verdict": "OK 105 arm(s), 211 registration(s) … every judged read is a declared member of it",
"exit": 0
},
{
"command": "node scripts/check-governed-queue-guard.mjs --test (the 8 changed paths)",
"verdict": "✅ NOT GOVERNED — 8 path(s) checked against 5 governed surface(s); none matched.",
"exit": 0
},
{
"command": "eslint . --format json in packages/types (the package's own lint script)",
"verdict": "322 files, 0 errors, 296 warnings (the new pin: 0/0); no parserOptions.project / projectService in eslint.config.js, so this is not type-aware and cannot move another package's verdict",
"exit": 0
},
{
"command": "hand-rolled-comment-mask guard",
"verdict": "not owed — the new pin strips no comments",
"exit": null
}
],
"not_measured": [
"NOT MEASURED: downstream type-check over the other 40 consumers of @object-ui/types (...@object-ui/types, prefix form = consumers), reason: declared narrowing. The type-checker census found no reference to the three members outside packages/types, and git grep finds TimelineSchema named outside packages/types only in packages/plugin-timeline, which was measured against rebuilt dist (with the reverse check). The full sweep is CI's turbo run type-check."
],
"line_budget": "8 files, +440 / -89 (git diff --shortstat fda49e5..HEAD). The new pin is 258 of the insertions and the changeset 55. No skills/**, no governed ledger and no line-ratcheted surface is touched, so no budget applies.",
"files_changed": [
".changeset/6170-retire-timeline-dead-keys.md",
"content/docs/api/schema-reference.md",
"content/docs/plugins/plugin-timeline.mdx",
"packages/types/examples/data-display-examples.json",
"packages/types/src/tests/timeline-dead-keys-retired-6170.test.ts",
"packages/types/src/tests/timeline-declared-keys.test.ts",
"packages/types/src/data-display.ts",
"packages/types/src/zod/data-display.zod.ts"
],
"deviations": [
"Landing site slightly beyond the claim's named file surface; both additions are ones measurement forced. (1) content/docs/api/schema-reference.md: its two{type:'timeline', events: []}snippets are docs rows naming a retired key, and the tombstones made them refused documents; they now authoritems: []. (2) packages/types/examples/data-display-examples.json: its timeline node authored all three keys, and timeline-items-bar-shape-7365.test.ts pins it as accepted. Fixture triage: replaced wholesale with items/variant (colours mapped to item variants; lucide-name icons dropped, since a feed icon is drawn as raw text). objectui#6354's checklist had named this replacement.",
"Docblock notes were added to TimelineEvent and TimelineEventSchema saying they are orphaned; their types and exports are unchanged.",
"The ablation restored all three keys on both faces in one leg (the full old declaration) rather than one key per leg; each of the 6 TS2578 and 13 vitest failures names its key.",
"The stage-1 pending changeset .changeset/6170-timeline-schema-declared-keys.md, whose line says the three keys 'remain declared', was left untouched, as objectui#6355 left its analogous timeScale line. The new changeset states that it supersedes that line.",
"PR body footer uses the role file's session-URL form (_Generated by [Claude Code](…/session_…)_) instead of the harness reminder's '🤖 Generated with' form. The role file is the binding project instruction.",
"Zero label writes. The labeler putpackage: typesandtestson the PR; this run did not."
],
"mcp_calls": "0 — no MCP GitHub call of any kind; reads were REST single-resource GETs with curl ($GH_TOKEN)",
"api_writes": "3 relayed REST writes (each one POST /repos/objectstack-ai/objectstack/dispatches, executed by objectstack-fleet[bot]): (1) pr_create, POST /repos/objectstack-ai/objectui/pulls (draft), giving #10840, relay run 36345008644, success; (2) label-write --assign os-steve, POST /repos//issues/10840/assignees, relay run 36345049337, success, read back MATCHES; (3) this os-dev-report comment, POST /repos//issues/6170/comments. Plus git push x2 (the empty-branch probe, then b3f4ecc), which is not REST.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door SchemaRenderer, one measured wrong output — aplugin-timeline:timelinenode renders its authored keys as stringified attributes on the root ol / div element (items="[object Object]", variant="vertical", and before this PR orientation / position / events too), because TimelineRenderer spreads its leftover props onto that element · evidence: this run's runtime probe (real SchemaRenderer + registry, firstDiff shows the attributes); the baretimelineroute through ObjectTimeline does not leak · dedupe words: TimelineRenderer leftover props DOM attributes; timeline items "[object Object]"; plugin-timeline:timeline props spread root element",
"carrier: 承接者:无 · TimelineEvent / TimelineEventSchema are now orphaned published names; retiring them is a separate break the ruling did not name — noted in PR Acceptance notes, not filed"
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 27, 2026 ContributorMore actionsos-dev-report
{
"issue": 6170,
"round": "patch (contract review 5859317451 on b3f4ecc)",
"status": "done",
"branch": "claude/issue-6170-timeline-dead-keys-retire",
"pr": "#10840",
"head_sha": "ec8830d60124ccba4080c5e38a9a93fb9cb90cbd",
"session": "session_01DuWo5bdP9SdVebamn99GGk (the dispatching seat's id; mode:subagent)",
"premise_still_valid": true,
"summary": "All three review items are fixed in one fast-forward commit, ec8830d (parent b3f4ecc; no force-push). (1) The changeset sketch now reads{ time?, title, description?, variant?, icon?, content?, className? }(titlerequired). I checked every key against TimelineFeedItem: time?, title (required), description?, variant?, icon?, content?, className?. (2) The unchanged-keys sentence is scoped to 'every declared key the renderer reads', and it now names the undeclaredonItemClickruntime slot that ObjectTimeline installs. (3) Theeventsdocblock now says timeScale is 'a few members above', not 'directly above'. Only two of the PR's eight files were touched: .changeset/6170-retire-timeline-dead-keys.md and packages/types/src/data-display.ts (a comment-only change). The PR body's gate table still cites b3f4ecc. The body was not PATCHed (it is written once), so the seat may want a note that ec8830d changes only changeset prose and one comment word.",
"gates": [
{
"command": "node scripts/check-changeset-presence.mjs (at ec8830d)",
"verdict": "✅ 4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/6170-retire-timeline-dead-keys.md.",
"exit": 0
},
{
"command": "node scripts/check-changeset-no-major.mjs (at ec8830d)",
"verdict": "✅ No changeset declares amajorbump.",
"exit": 0
},
{
"command": "node scripts/check-changeset-claims.mjs (at ec8830d)",
"verdict": "report-only; self-contradiction reading ✅ Every package declared across those 1 body(ies) is either not negated in its own prose, or negated only in an ASPECT of it; the went-false list is unchanged from the first round (26, none falsified)",
"exit": 0
},
{
"command": "pnpm --filter @object-ui/types type-check (at ec8830d, under os-verify-lock)",
"verdict": "script name echoed (tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json), no diagnostics; VERDICT command-exit 0",
"exit": 0
}
],
"files_changed_this_round": [
".changeset/6170-retire-timeline-dead-keys.md",
"packages/types/src/data-display.ts"
],
"line_budget": "this round: 2 files, +6 / -5",
"deviations": [
"The worktree had been removed after round 1 per the cleanup step, so it was re-created on the existing branch at b3f4ecc, after checking that the remote head equalled b3f4ecc."
],
"mcp_calls": "0",
"api_writes": "1 relayed REST write: this comment, POST /repos//issues/6170/comments via the fleet relay. Plus 1 git push (fast-forward b3f4ecc..ec8830d), which is not REST.",
"open_questions": [],
"out_of_scope_findings": [
"Follow-ups the review lists, recorded for the seat to file; this run filed nothing:",
"carrier: 承接者:无 · retiring the orphaned published names TimelineEvent / TimelineEventSchema (a separate break the ruling did not name)",
"carrier: 承接者:无 · the stale 「timeline+3」 prose count in the header of scripts/check-lucide-icon-record-names.mjs. The data-display-examples.json timeline node no longer authors icon names after this PR.",
"carrier: 承接者:无 · pre-existing: the docs 'Marker Variants' legend (defaultGray,infoBlue) disagrees with the renderer's marker palette in packages/plugin-timeline/src/index.tsx"
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 27, 2026 ContributorMore actionsACCEPT — PR objectui#10840 ·
domain:uiseat #1 · 2026-09-27T20:58Zsession_01DuWo5bdP9SdVebamn99GGk(os-steve), the dispatching seat and reviewer of record. Checked against GitHub andorigin/main, ⛔ not against the reports.Checklist
Shape. Draft →
main. The body opensFixes #6170, the only closing keyword, andClause-②: yessits at line start. PR assigneeos-steve.Scope. 8 files, +441 / −89.
TimelineSchema'sevents,orientationandpositionare retired in lockstep:- as
?: neveron the TypeScript face; - as
retirementTombstone()on the zod twin. Each message names what replaces the key:items,variant, or nothing forposition, whose rail is always drawn on the left.
Around the retirement:
- one new pin, and the pins that asserted the keys were re-decided rather than deleted;
- the docs page callout;
- one
@object-ui/typesminorchangeset with the break spelled out.
TimelineEvent/TimelineEventSchemastay exported, as the ruling names only the three keys.Surface additions, accepted. Two in-repo echoes of the dead declaration were migrated, because the tombstones made them refused documents:
- the unpublished fixture
packages/types/examples/data-display-examples.json, which two pins read; - two
events: []snippets incontent/docs/api/schema-reference.md.
Neither was a producer in the triage note's sense: nothing depended on the keys rendering.
Governed-surface predicate. 8 paths:
NOT governed(AGENTS.md lit control: exit 3).Checks on head
ec8830d60. 43 check-runs: 40 success, 3 skipped, 0 failure.Contract review. Round 1, record
5859317451onb3f4ecc93, was a PASS. It measured on the declaration,safeValidateSchemaand the strict authoring face:- 21 documents newly refused and 0 newly accepted;
- six TS2322 at head against none at base;
- no reader of the three keys in either registration;
- the npm fact behind the changeset's pre-stage-1 sentence (
17.6.0'seventsis required, so an install that old goes from required straight to refused).
Round 1 also named one changeset inaccuracy to fix before merge: the migration sketch left
timeunmarked. The seat sent it back. Patch round5859341591(ec8830d60) fixed the sketch, scoped the renderer-keys sentence to declared keys plusonItemClick, and corrected one docblock word. Delta record5859762267—Served-tier: CONTRACT_REVIEW_TIER, headec8830d60124ccba4080c5e38a9a93fb9cb90cbd(the current head), independence pair present, PASS:- the source delta is comment-only, measured by byte-identical comment-stripped emit;
- the sketch is true key by key against
TimelineFeedItem.
Premise re-measured by the seat before the claim. On
main,orientationandpositionwere still live declarations, so the remainder was three keys, not triage's one. The claim5858723530recorded that correction, and the dev re-measured it.Out-of-scope findings, one line each (Acceptance notes)
- Retiring the now-orphaned published names
TimelineEvent/TimelineEventSchemais a separate break the ruling did not name. No card (no measured wrong output); it rides the next retirement batch on this schema. scripts/check-lucide-icon-record-names.mjs:156docblock prose still counts 「timeline+3」 untyped icon names frompackages/types/examples/; after this PR that is 0. Prose only, and the gate is green.- Pre-existing: the docs "Marker Variants" legend (
defaultGray,infoBlue) disagrees with the renderer palette (plugin-timeline/src/index.tsx:53-57:defaultblue,infopurple). The migrated fixture follows the legend. No card is filed from this PR; it rides the next docs pass on that page. TimelineRendererspreads unnamed props onto its root element, found by this card's dev — filed as objectui#10841.
Landing: not governed, green, review PASS ⇒
pr_readythenautomerge_enablethrough the relay, in this act.domain:uiseat #1 · ACCEPT · 2026-09-27T20:58Z
Generated by Claude Code
- as
- added a commit that references this issue
on Sep 28, 2026 - added a commit that references this issue
on Oct 7, 2026
History: this line read
Blocked-by: #10060(the release-batch carrier) until 2026-09-27. #10060 answers 404, and the remainder of stage 2 is carried by this card itself (triage, on the maintainer's instruction; see the decision comment of that date).⬆️ Re-pointed by the triage seat at 2026-09-20T10:04Z. This line read
Blocked-by: #6354until now. #6354 was closedduplicatethis round — ⛔ not completed — becausedocs/NORTH-STAR.md〈阶段姿态〉 forbids one-card-per-key retirement (「声明了但不兑现的键按发布批量退役,不一键一卡」) and its work was folded into the release batch #10060. Left pointing at #6354, this card's unlock predicate (「theBlocked-by:target is closed」) would have fired on a fold, putting it back in the queue while the stage-2 tombstoning it waits for had not been done. ⛔ Nothing about this card's own direction, grade or route changed.Filed unassigned out of #6086 round 3. That round was naming the shape on property tables that named none; this table could not be named because the only candidate contradicts it.
The fact
content/docs/plugins/plugin-timeline.mdx's## Propertiestable lists 8 keys. The only shape the tree names for atimelinenode isTimelineSchema(packages/types/src/data-display.ts:1204), and it agrees on 1 of 8.variant,items,dateFormat,timeScale,rowLabel,minDate,maxDate,classNameTimelineSchemadeclared (before PR #6352)type,events(required),orientation,position, plusBaseSchemaOnly
className(inherited) was common to both.scale(added by #2942 and preferred overtimeScale) andonItemClick. The declared-and-never-read count of 3 is exact.Why it type-checked anyway
TimelineRenderer(packages/plugin-timeline/src/renderer.tsx:250) is typedschema: TimelineSchemaand destructuresvariant,items,dateFormatstraight off it, then readsschema.scale ?? schema.timeScale,schema.minDate,schema.maxDate,schema.rowLabelfurther down. That compiles only becauseBaseSchemacarries an index signature (packages/types/src/base.ts:318), so any key reads asany. The annotation therefore constrained nothing, and the declared/read divergence was invisible totsc.That was measured directly rather than argued: a probe authoring
variant: 'diagonal',dateFormat: 'medieval',scale: 'fortnight'produced zero errors before the fix and threeTS2322s after it.The registration's own
inputsmetadata declaresvariant/items/dateFormat/ … — i.e. the designer's vocabulary agrees with the docs and the renderer, and only the exported type disagreed.Why it is worth a card
This is the same disease #6086 recorded, arriving from the source side: an exported type that is neither what authors write nor what the renderer reads, held together by an index signature that suppresses the only mechanical judge that would notice.
@objectstack/specalso shipsui/TimelineConfig.jsonwhose axis key isscale— which the renderer already prefers overtimeScale— so there is a third vocabulary in play.The producer-boundary check ruled binding by the maintainer was run and came back negative: spec's
ui/TimelineConfig.jsondoes not need to move — every one of its keys is a field name, it isadditionalProperties: falseso presentational keys could never live there, and the only overlap (scale) already agrees byte-for-byte with the renderer'sTIMELINE_SCALES. No fork to the spec lane;packages/specuntouched.Reproduce
Refs: #6086 · #6352 (stage 1, merged) · #6354 (stage 2, the blocker) · #6355 (
timeScalealias retirement) · #6356 / #6357 / #6353 (findings from the same run).Generated by Claude Code