You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Retire the timeScale alias on the timeline node, now that scale is canonical on all three surfaces #6355
#6170's maintainer ruling (2026-08-25): "scale is canonical (it is spec ui/TimelineConfig.json's axis key AND the renderer's preferred read); timeScale goes the alias-retirement route, not a silent second spelling."
PR #6352 made scale canonical on the two surfaces that were missing it — the exported TimelineSchema (which declared neither key) and the designer inputs (which offered only timeScale, and only 3 of its 6 legal values). timeScale is now declared, @deprecated, marked advanced, and still read. That is the alias made honest, not retired.
What is left
resolveTimelineScale (packages/plugin-timeline/src/renderer.tsx) still resolves schema.scale ?? schema.timeScale. Retiring the alias means deciding:
Whether a migration runs, and over what (examples/schema-catalog/src/schemas/plugin-timeline/gantt-style-timeline.json authors timeScale today; so does the registration's own examples.gantt block in renderer.tsx).
Note this key is not dead — unlike events/orientation/position in #6354, timeScale has a live read point and live authors. It cannot go the same route on the same evidence.
Refs: #6170, PR #6352, #2942 (why the fallback read exists), #6354 (the sibling ADR-0049 removal), #5474 (tombstone convention).
Maintainer ruling recorded — Option A: immediate retirement, no transition window — and a STANDING class rule
Provenance: maintainer, 2026-08-27, PM chat decision-inbox batch 6 (adjudication session session_01DKWDdUJ2XNRESVVWUvcpnh), verbatim: 「6335 和以后这种类似的,都立即退休。项目在创业阶段,用户也很少,短期不考虑渐进。」 (6355 by number; 6335 is a typo for this card).
Ruled: A — retire timeScale now. Tombstone (?: never + mirror twin per the #5474 convention), drop the designer input, drop the resolveTimelineScale fallback read, drop the docs row — and migrate the two in-repo authors in the same PR (examples/schema-catalog/.../gantt-style-timeline.json and the renderer's own examples.gantt block). The silent-reversion cost for externally-stored old documents is accepted by the ruling: startup stage, very few users. The staged window (option B) is declined.
⭐ Standing class rule, quoted for future seats: for this class — a deprecated alias/spelling with the canonical form already ruled — the default is IMMEDIATE retirement, no phased windows, while the project is at startup stage. Recorded into the decision framework via objectstack-ai/objectstack#12668. Sibling #6354's own路线 is unaffected (already immediate).
Dispatch notes: Clause-② per this repo's convention (published-type change ⇒ governed draft, human merge); pin: the old spelling is refused loudly post-change (red-before-fix on a timeScale-only document), not silently defaulted.
The maintainer ruling (2026-08-27) is the order. Four things on top of it.
⛔ This PR is NOT mine to enqueue — governed draft, human merge
The ruling's dispatch note is explicit: Clause-② per this repo's convention (published-type change ⇒ governed draft, human merge).
So: open it draft and leave it draft. ⛔ Do not flip ready, do not enable auto-merge, do not enqueue. I will not either — I will report it as ready for a human merge. This differs from every other card this seat has dispatched today, and it differs for a reason: the diff changes a published type.
The pin is the whole deliverable, and it has a specific shape
pin: the old spelling is refused loudly post-change (red-before-fix on a timeScale-only document), not silently defaulted.
The failure this card exists to prevent is a timeScale-only document silently reverting to the default month granularity — the chart changes and nothing errors. So a test that merely proves scale works is not the pin. What must be pinned is that an old-spelling document is refused, and it must be shown red before the fix.
⛔ Do not let the tombstone's presence stand in for that. Show the refusal failing on the pre-fix tree, then passing.
Scope, as the ruling enumerates it — all in ONE PR
Migrate the two in-repo authors in the same PR: examples/schema-catalog/.../gantt-style-timeline.json and the renderer's own examples.gantt block.
Re-verify all five on the current origin/main before editing — line and path references in this card predate several merges today.
On the bump — read this before you reach for major
This retires a published alias, so the honest-declaration question arises. The convention here is minor plus the break spelled out in the changeset body (AGENTS.md §版本号策略): objectui's major is pinned to the @objectstack major, and all publishable packages share one fixed group. A second dev reached the same reading independently on #6490 today, unprompted.
⛔ If you nonetheless conclude an honest declaration requires major, stop and report — do not fight the gate, and do not re-score to get green. That exact standoff is live and unresolved on #6366, whose PR is deliberately parked draft-and-red; a second instance of it helps nobody. Reporting is the correct outcome.
Standing class rule from this ruling, quoted because it governs future cards too
for this class — a deprecated alias/spelling with the canonical form already ruled — the default is IMMEDIATE retirement, no phased windows, while the project is at startup stage.
The staged-window option was declined, and the silent-reversion cost for externally-stored documents is accepted by the ruling. So do not re-litigate it, and do not soften the removal into a deprecation warning.
{
"issue": 6355,
"status": "done",
"branch": "claude/issue-6355-retire-timescale-alias",
"pr": "https://github.com/objectstack-ai/objectui/pull/6656",
"premise_still_valid": true,
"summary": "Retired the `timeScale` alias per the 2026-08-27 ruling (Option A, immediate, no window). All five enumerated scope items landed in one PR: tombstoned `timeScale` on BOTH halves in lockstep (`?: never` on TimelineSchema plus the Zod twin's `z.never().optional()`), dropped the designer input, dropped `resolveTimelineScale`'s `?? schema.timeScale` fallback read, dropped the docs row, and migrated the two named in-repo authors. All five were re-verified present on the current origin/main before editing. The tombstone is load-bearing rather than ceremonial: BaseSchema is `.passthrough()` with a `[key: string]: any` index signature, so DELETING the key would have let the retired spelling parse and type-check green while the renderer no longer read it — a silent revert to the month default, which is the exact failure this card exists to prevent. I found and migrated a THIRD in-repo author the card did not list: ObjectTimeline composes a schema for TimelineRenderer and wrote the resolved axis under the alias; invisible to any grep of authored metadata, and it would have silently reverted EVERY object-bound gantt the moment the fallback read went. Writing `scale` after the spread also fixes a latent precedence inversion — `timelineConfig.scale` was losing to a flat `schema.scale` because the resolver read `scale ?? timeScale`. Bump is `minor` with the break spelled out in the changeset body, per AGENTS.md §版本号策略; no major standoff arose. PR is DRAFT and stays draft (Clause-②, published-type change, human merge) — not enqueued, no auto-merge, `draft: true` and `auto_merge: null` confirmed by readback.",
"tests": "All heavy runs serialized through scripts/pm/os-verify-lock.sh; every exit code captured before any pipe (redirect-then-capture), and each verdict quoted from the gate's own printed line.\n\nRED BEFORE FIX (the deliverable). The pin was committed BEFORE its fix (commit ab88df74) and run on that pre-fix tree. Runtime leg, packages/types/src/__tests__/timeline-timescale-retired.test.ts: `Test Files 1 failed (1)` / `Tests 1 failed | 4 passed (5)`, failing on `AssertionError: a timeScale-only document was ACCEPTED — it will silently revert to the month default: expected true to be false`. The 4 counter-probes passed, so the file failed on the refusal assertion specifically. Compile-time leg on the same tree: `tsc -p tsconfig.test.json` exit 2, `src/__tests__/timeline-timescale-retired.test.ts(116,5): error TS2578: Unused '@ts-expect-error' directive.` After the fix both legs green: `Tests 5 passed (5)`, tsc exit 0. The pin asserts the rejection ENVELOPE (code `invalid_type`, `expected: 'never'`, path `timeScale`), not merely success:false.\n\nABLATIONS (2), each with the mutation proven on disk and the restore proven by observation, never by exit code. (1) Zod tombstone reverted in data-display.zod.ts: anchored grep counts injected=1 / removed=0, blob cc5fb86d -> 5f59f688; ablated vitest exit 1 (`1 failed | 4 passed`); restore via `git checkout HEAD -- <abs path>` returned blob cc5fb86d = the HEAD blob with `git diff HEAD` clean; restored vitest exit 0 (`5 passed`). (2) TS tombstone reverted in data-display.ts (`never` -> `TimelineScale`): injected=1 / removed=0, blob 5365e337 -> 19922e94; ablated tsc exit 2 with TS2578 x2 (both compile-time legs); restore returned 5365e337 with `git diff HEAD` clean; restored tsc exit 0. Both scripts carried `trap '<restore>' EXIT INT TERM` with ABSOLUTE paths from `git rev-parse --show-toplevel`. NO REBUILD LEG is claimed or needed and this is measured, not assumed: the pins import `../zod/data-display.zod.js` relatively and `@object-ui/types/zod` is aliased to SOURCE in vitest.config.mts, so there is no exports/dist hop on these pairs and no stale-dist false green is possible. A third ablation reverted ObjectTimeline's `scale` write (blob d0604c86 -> 1884ac56, restored to d0604c86, diff clean): 3 failed ablated / 4 passed restored, one failure being `expected 'day' to be 'year'` — direct evidence for the precedence-inversion claim.\n\nThe restore leg earned its keep: it caught a misplaced `@ts-expect-error` in my own new pin (above the `const` instead of above the property that excess-property checking reports on), which suppressed nothing while a real TS2322 stood. Fixed in 82f289e1 and re-ablated: TS2578 x2 red, restore clean, restored exit 0.\n\nUNION on the final commit `82f289e1` (`git rev-parse --short HEAD` taken from that run), all from the repo root — 15 steps, every one exit 0: vitest touched suites `Test Files 31 passed (31)` / `Tests 226 passed (226)`; `turbo run type-check` (types, plugin-timeline, components) `Tasks: 13 successful, 13 total`; `type-check:scripts` 0; `turbo run lint` `Tasks: 4 successful, 4 total`; `lint:coverage` `lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).`; `check:control-bytes` `OK (scanned 5496 tracked text file(s); skipped 85 binary).`; `check:vi-mock-specifiers` `OK (... 729 relative specifier(s) resolved ...)`; `check:doc-snippets` `Every covered documentation snippet compiles against the built types.` (267/267 judged, 0 failed); `check:readme-exports` `OK (... 378 real, 0 wrong-path, 0 fabricated; 0 unbuilt)`; `changeset:check` `No changeset declares a major bump.`; `check-changeset-presence` `8 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)`; plus check:doc-types, check:doc-fences, docs:check-links, check:designer-field-key-parity.\n\nNOT MEASURED, then measured: check:doc-snippets first exited 2 with `PRECONDITION NOT MET (exit 2) — The snippet program was NOT run`, and check:readme-exports exited 1 with `the population COLLAPSED -- this run proves nothing` (packagesRead 11, floor 25). Both were recorded as NOT MEASURED, never as red; both were re-run after building what they name, and the green verdicts above are from those real runs.\n\nMigrated authors confirmed rendering canonically, not just renamed: every assertion runs through the real `resolveTimelineScale` and the published Zod validator, and the catalog fixture's pre-migration form is asserted REFUSED as a counter-probe.\n\nGate list derived manually from package.json + .github/workflows: objectui has no scripts/pm/dispatch-gates.mjs, and objectstack's copy answers only about its own tree, so it was deliberately not used here.",
"mcp_calls": "6 — issue_read(get), issue_read(get_comments), issue_write(create #6655), create_pull_request, update_pull_request, add_issue_comment. Issue dedup and all PR/body readbacks went through the zero-quota REST channel (probed 200 first; 92 open issues scanned, control word #6355 HIT).",
"open_questions": [],
"out_of_scope_findings": [
"filed as #6655: an object-bound timeline with `variant: gantt` throws RangeError: Invalid time value — calculateDateRange reads the gantt ROW shape while ObjectTimeline composes FEED items, so allDates is empty and Math.min() yields Infinity. Pre-existing: both halves proven byte-identical to the merge base with git diff. Related to but not fixed by #6356 (which is the declaration half of the same two-shapes fact). This PR's composition pin renders variant vertical deliberately so it does not couple to that defect."
]
}
Notes for the PM, outside the JSON:
The PR is draft and must stay draft — Clause-②, published-type change, human merge. Readback confirms draft: true, auto_merge: null. I did not flip, enqueue, or request review.
mergeable_state is behind (main moved during the run). Left as-is deliberately — updating the branch is not mine to do on a governed draft.
The PR body's _Generated by_ footer was removed by GitHub's body sanitizer on the one PATCH edit I made (that edit was needed: the heading Out of scope, filed not fixed put the word fixed immediately before #6655, which GitHub's closing-keyword parser can match across the blank line and would have silently closed that card on merge). Per convention I did not loop re-posting the footer; the session URL lives in the body prose as the durable copy, and a post-edit keyword rescan confirms Fixes #6355 is now the only closing-keyword match in the body.
ACCEPT — PR #6656. ⛔ Stays a DRAFT: this one is HUMAN MERGE.
Reviewed by the domain:ui execution seat. Per this card's ruling (published-type change, Clause-②), the PR is not flipped ready, not enqueued, and has no auto-merge. It is finished work waiting on the maintainer.
Entry qualification
Every check on head 82f289e1, not the required subset: 29 of 29 returned against total_count — 26 success, 3 skipped (dependabot, Test (coverage), Test (coverage shard …)), zero failures, zero still running. Type Check is completed: success, and for this change that is not one green among many: TS2578 makes an unused @ts-expect-error an error, so a green type-check is itself the proof the tombstone bites. A green test run would have proved nothing — the directive is type-erased.
⭐ The finding that justifies the whole card: deleting the key would have been the bug
The obvious implementation of "retire the alias" is to delete timeScale. Measured, that would have been a silent regression:
BaseSchema is .passthrough() with a [key: string]: any index signature — so a deleted key still parses and still type-checks green, while the renderer no longer reads it. An author who kept writing timeScale would get no error anywhere and a silent revert to the month default.
That is why the retirement is a tombstone in lockstep on both halves — ?: never on TimelineSchema and z.never().optional() on the Zod twin — rather than a deletion. This is the same rule this lane has now met three times, in its third face: under an index signature there is no non-membership left to enforce with, so the refusal has to be declared rather than inherited from absence.
⭐ It found a third author the card did not list — and it was ungreppable
The card named two in-repo authors. There is a third:
ObjectTimeline composes a schema for TimelineRenderer and wrote the resolved axis under the alias.
That is invisible to any grep of authored metadata, because it is not authored — it is code writing the key at runtime. Had the fallback read been dropped without it, every object-bound gantt would have silently reverted, and nothing in the corpus would have shown it. Finding it required reading the composition path rather than searching for the spelling.
Writing scale after the spread also corrected a latent precedence inversion: timelineConfig.scale had been losing to a flat schema.scale because the resolver read scale ?? timeScale. Ablation 3 produced expected 'day' to be 'year' — direct evidence, not an inference.
Shown able to fail, in the strict order
The pin was committed before its fix (ab88df74) and run on that pre-fix tree — so "red before green" is a property of the history, not a claim. Runtime leg failed on the refusal assertion specifically (a timeScale-only document was ACCEPTED — it will silently revert to the month default), with its 4 counter-probes passing, so the file failed for the right reason rather than failing wholesale. Compile leg: tsc exit 2 with TS2578. Both green after.
The pin asserts the rejection envelope (invalid_type, expected: 'never', path timeScale) rather than merely success: false — so it cannot be satisfied by any old refusal.
Three ablations, each with mutation proven on disk (anchored counts on injected and removed text, plus a blob-hash delta) and restoration proven by observation — blob equal to the HEAD blob andgit diff HEAD clean — never by an exit code. The no-rebuild claim was measured, not assumed: the pins import the mirror relatively and @object-ui/types/zod is aliased to source in vitest.config.mts, so no dist hop exists on these pairs.
⭐ The restore leg caught a defect in the dev's own new pin
Worth keeping, because it is the failure mode that survives a green suite:
a misplaced @ts-expect-error — above the const instead of above the property that excess-property checking actually reports on — suppressed nothing while a real TS2322 stood.
A directive that guards the wrong line is indistinguishable from a working one until something forces the question. Here the ablation did, and it was fixed and re-ablated rather than noted.
Two gates reported NOT MEASURED, and were treated as such
check:doc-snippets first exited 2 with "PRECONDITION NOT MET — the snippet program was NOT run", and check:readme-exports exited 1 with "the population COLLAPSED — this run proves nothing" (packagesRead 11 against a floor of 25). Both were recorded as NOT MEASURED, never as red, and re-run after building what they name. The green verdicts in the union are from those real runs. A precondition failure read as a pass — or as a failure — would both have been wrong.
The gate set was derived manually from package.json + .github/workflows, with the reason stated: objectui has no scripts/pm/dispatch-gates.mjs, and objectstack's copy answers only about its own tree. Declaring that beats silently running the wrong tool.
Scope
All five ruled scope items landed in one PR, each re-verified present on the current origin/main before editing. Bump is minor with the break spelled out in the changeset body — no major standoff arose. One out-of-scope finding filed as #6655 (an object-bound timeline with variant: gantt throws RangeError: Invalid time value), proven pre-existing by a byte-identical diff against the merge base rather than assumed to be.
⛔ What happens next is the maintainer's
This PR retires a published spelling, so under this card's ruling the merge is a human act and the draft state is the review record. It is green, complete, and waiting. Nothing further is owed by this seat, and this seat will not flip, enqueue, or auto-merge it.
Activity
timelinerenderer reads seven keysTimelineSchemadoes not declare, and declares three it never reads #6170os-zhuang commentedon Aug 27, 2026
Maintainer ruling recorded — Option A: immediate retirement, no transition window — and a STANDING class rule
Provenance: maintainer, 2026-08-27, PM chat decision-inbox batch 6 (adjudication session
session_01DKWDdUJ2XNRESVVWUvcpnh), verbatim: 「6335 和以后这种类似的,都立即退休。项目在创业阶段,用户也很少,短期不考虑渐进。」 (6355 by number; 6335 is a typo for this card).Ruled: A — retire
timeScalenow. Tombstone (?: never+ mirror twin per the #5474 convention), drop the designer input, drop theresolveTimelineScalefallback read, drop the docs row — and migrate the two in-repo authors in the same PR (examples/schema-catalog/.../gantt-style-timeline.jsonand the renderer's ownexamples.ganttblock). The silent-reversion cost for externally-stored old documents is accepted by the ruling: startup stage, very few users. The staged window (option B) is declined.⭐ Standing class rule, quoted for future seats: for this class — a deprecated alias/spelling with the canonical form already ruled — the default is IMMEDIATE retirement, no phased windows, while the project is at startup stage. Recorded into the decision framework via objectstack-ai/objectstack#12668. Sibling #6354's own路线 is unaffected (already immediate).
Dispatch notes: Clause-② per this repo's convention (published-type change ⇒ governed draft, human merge); pin: the old spelling is refused loudly post-change (red-before-fix on a
timeScale-only document), not silently defaulted.State:
needs-user-decision→pm:queue.Generated by Claude Code
os-sales commentedon Aug 28, 2026
Claim:
domain:uiexecution seat, session8ca04858-ea8e-5b85-9182-de59aa49e00c, branchclaude/issue-6355-retire-timescale-alias.The maintainer ruling (2026-08-27) is the order. Four things on top of it.
⛔ This PR is NOT mine to enqueue — governed draft, human merge
The ruling's dispatch note is explicit: Clause-② per this repo's convention (published-type change ⇒ governed draft, human merge).
So: open it draft and leave it draft. ⛔ Do not flip ready, do not enable auto-merge, do not enqueue. I will not either — I will report it as ready for a human merge. This differs from every other card this seat has dispatched today, and it differs for a reason: the diff changes a published type.
The pin is the whole deliverable, and it has a specific shape
The failure this card exists to prevent is a
timeScale-only document silently reverting to the default month granularity — the chart changes and nothing errors. So a test that merely provesscaleworks is not the pin. What must be pinned is that an old-spelling document is refused, and it must be shown red before the fix.⛔ Do not let the tombstone's presence stand in for that. Show the refusal failing on the pre-fix tree, then passing.
Scope, as the ruling enumerates it — all in ONE PR
timeScale(?: neverplus the mirror twin, per the finding(components): the statictablerenderer reads 9 of the 20 keysTableSchema/TableColumndeclare —hoverable,striped,align,sortable,celland 6 more are inert, and the reference page documents two of them as working #5474 lockstep convention — both halves or neither).resolveTimelineScalefallback read.examples/schema-catalog/.../gantt-style-timeline.jsonand the renderer's ownexamples.ganttblock.Re-verify all five on the current
origin/mainbefore editing — line and path references in this card predate several merges today.On the bump — read this before you reach for
majorThis retires a published alias, so the honest-declaration question arises. The convention here is
minorplus the break spelled out in the changeset body (AGENTS.md §版本号策略): objectui's major is pinned to the@objectstackmajor, and all publishable packages share onefixedgroup. A second dev reached the same reading independently on #6490 today, unprompted.⛔ If you nonetheless conclude an honest declaration requires
major, stop and report — do not fight the gate, and do not re-score to get green. That exact standoff is live and unresolved on #6366, whose PR is deliberately parked draft-and-red; a second instance of it helps nobody. Reporting is the correct outcome.Standing class rule from this ruling, quoted because it governs future cards too
The staged-window option was declined, and the silent-reversion cost for externally-stored documents is accepted by the ruling. So do not re-litigate it, and do not soften the removal into a deprecation warning.
Draft PR,
Fixes #6355, and leave it draft.Generated by Claude Code
os-sales commentedon Aug 28, 2026
os-dev-report
{ "issue": 6355, "status": "done", "branch": "claude/issue-6355-retire-timescale-alias", "pr": "https://github.com/objectstack-ai/objectui/pull/6656", "premise_still_valid": true, "summary": "Retired the `timeScale` alias per the 2026-08-27 ruling (Option A, immediate, no window). All five enumerated scope items landed in one PR: tombstoned `timeScale` on BOTH halves in lockstep (`?: never` on TimelineSchema plus the Zod twin's `z.never().optional()`), dropped the designer input, dropped `resolveTimelineScale`'s `?? schema.timeScale` fallback read, dropped the docs row, and migrated the two named in-repo authors. All five were re-verified present on the current origin/main before editing. The tombstone is load-bearing rather than ceremonial: BaseSchema is `.passthrough()` with a `[key: string]: any` index signature, so DELETING the key would have let the retired spelling parse and type-check green while the renderer no longer read it — a silent revert to the month default, which is the exact failure this card exists to prevent. I found and migrated a THIRD in-repo author the card did not list: ObjectTimeline composes a schema for TimelineRenderer and wrote the resolved axis under the alias; invisible to any grep of authored metadata, and it would have silently reverted EVERY object-bound gantt the moment the fallback read went. Writing `scale` after the spread also fixes a latent precedence inversion — `timelineConfig.scale` was losing to a flat `schema.scale` because the resolver read `scale ?? timeScale`. Bump is `minor` with the break spelled out in the changeset body, per AGENTS.md §版本号策略; no major standoff arose. PR is DRAFT and stays draft (Clause-②, published-type change, human merge) — not enqueued, no auto-merge, `draft: true` and `auto_merge: null` confirmed by readback.", "tests": "All heavy runs serialized through scripts/pm/os-verify-lock.sh; every exit code captured before any pipe (redirect-then-capture), and each verdict quoted from the gate's own printed line.\n\nRED BEFORE FIX (the deliverable). The pin was committed BEFORE its fix (commit ab88df74) and run on that pre-fix tree. Runtime leg, packages/types/src/__tests__/timeline-timescale-retired.test.ts: `Test Files 1 failed (1)` / `Tests 1 failed | 4 passed (5)`, failing on `AssertionError: a timeScale-only document was ACCEPTED — it will silently revert to the month default: expected true to be false`. The 4 counter-probes passed, so the file failed on the refusal assertion specifically. Compile-time leg on the same tree: `tsc -p tsconfig.test.json` exit 2, `src/__tests__/timeline-timescale-retired.test.ts(116,5): error TS2578: Unused '@ts-expect-error' directive.` After the fix both legs green: `Tests 5 passed (5)`, tsc exit 0. The pin asserts the rejection ENVELOPE (code `invalid_type`, `expected: 'never'`, path `timeScale`), not merely success:false.\n\nABLATIONS (2), each with the mutation proven on disk and the restore proven by observation, never by exit code. (1) Zod tombstone reverted in data-display.zod.ts: anchored grep counts injected=1 / removed=0, blob cc5fb86d -> 5f59f688; ablated vitest exit 1 (`1 failed | 4 passed`); restore via `git checkout HEAD -- <abs path>` returned blob cc5fb86d = the HEAD blob with `git diff HEAD` clean; restored vitest exit 0 (`5 passed`). (2) TS tombstone reverted in data-display.ts (`never` -> `TimelineScale`): injected=1 / removed=0, blob 5365e337 -> 19922e94; ablated tsc exit 2 with TS2578 x2 (both compile-time legs); restore returned 5365e337 with `git diff HEAD` clean; restored tsc exit 0. Both scripts carried `trap '<restore>' EXIT INT TERM` with ABSOLUTE paths from `git rev-parse --show-toplevel`. NO REBUILD LEG is claimed or needed and this is measured, not assumed: the pins import `../zod/data-display.zod.js` relatively and `@object-ui/types/zod` is aliased to SOURCE in vitest.config.mts, so there is no exports/dist hop on these pairs and no stale-dist false green is possible. A third ablation reverted ObjectTimeline's `scale` write (blob d0604c86 -> 1884ac56, restored to d0604c86, diff clean): 3 failed ablated / 4 passed restored, one failure being `expected 'day' to be 'year'` — direct evidence for the precedence-inversion claim.\n\nThe restore leg earned its keep: it caught a misplaced `@ts-expect-error` in my own new pin (above the `const` instead of above the property that excess-property checking reports on), which suppressed nothing while a real TS2322 stood. Fixed in 82f289e1 and re-ablated: TS2578 x2 red, restore clean, restored exit 0.\n\nUNION on the final commit `82f289e1` (`git rev-parse --short HEAD` taken from that run), all from the repo root — 15 steps, every one exit 0: vitest touched suites `Test Files 31 passed (31)` / `Tests 226 passed (226)`; `turbo run type-check` (types, plugin-timeline, components) `Tasks: 13 successful, 13 total`; `type-check:scripts` 0; `turbo run lint` `Tasks: 4 successful, 4 total`; `lint:coverage` `lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).`; `check:control-bytes` `OK (scanned 5496 tracked text file(s); skipped 85 binary).`; `check:vi-mock-specifiers` `OK (... 729 relative specifier(s) resolved ...)`; `check:doc-snippets` `Every covered documentation snippet compiles against the built types.` (267/267 judged, 0 failed); `check:readme-exports` `OK (... 378 real, 0 wrong-path, 0 fabricated; 0 unbuilt)`; `changeset:check` `No changeset declares a major bump.`; `check-changeset-presence` `8 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)`; plus check:doc-types, check:doc-fences, docs:check-links, check:designer-field-key-parity.\n\nNOT MEASURED, then measured: check:doc-snippets first exited 2 with `PRECONDITION NOT MET (exit 2) — The snippet program was NOT run`, and check:readme-exports exited 1 with `the population COLLAPSED -- this run proves nothing` (packagesRead 11, floor 25). Both were recorded as NOT MEASURED, never as red; both were re-run after building what they name, and the green verdicts above are from those real runs.\n\nMigrated authors confirmed rendering canonically, not just renamed: every assertion runs through the real `resolveTimelineScale` and the published Zod validator, and the catalog fixture's pre-migration form is asserted REFUSED as a counter-probe.\n\nGate list derived manually from package.json + .github/workflows: objectui has no scripts/pm/dispatch-gates.mjs, and objectstack's copy answers only about its own tree, so it was deliberately not used here.", "mcp_calls": "6 — issue_read(get), issue_read(get_comments), issue_write(create #6655), create_pull_request, update_pull_request, add_issue_comment. Issue dedup and all PR/body readbacks went through the zero-quota REST channel (probed 200 first; 92 open issues scanned, control word #6355 HIT).", "open_questions": [], "out_of_scope_findings": [ "filed as #6655: an object-bound timeline with `variant: gantt` throws RangeError: Invalid time value — calculateDateRange reads the gantt ROW shape while ObjectTimeline composes FEED items, so allDates is empty and Math.min() yields Infinity. Pre-existing: both halves proven byte-identical to the merge base with git diff. Related to but not fixed by #6356 (which is the declaration half of the same two-shapes fact). This PR's composition pin renders variant vertical deliberately so it does not couple to that defect." ] }Notes for the PM, outside the JSON:
draft: true,auto_merge: null. I did not flip, enqueue, or request review.mergeable_stateisbehind(main moved during the run). Left as-is deliberately — updating the branch is not mine to do on a governed draft._Generated by_footer was removed by GitHub's body sanitizer on the one PATCH edit I made (that edit was needed: the headingOut of scope, filed not fixedput the wordfixedimmediately before#6655, which GitHub's closing-keyword parser can match across the blank line and would have silently closed that card on merge). Per convention I did not loop re-posting the footer; the session URL lives in the body prose as the durable copy, and a post-edit keyword rescan confirmsFixes #6355is now the only closing-keyword match in the body.Generated by Claude Code
os-sales commentedon Aug 28, 2026
ACCEPT — PR #6656. ⛔ Stays a DRAFT: this one is HUMAN MERGE.
Reviewed by the
domain:uiexecution seat. Per this card's ruling (published-type change, Clause-②), the PR is not flipped ready, not enqueued, and has no auto-merge. It is finished work waiting on the maintainer.Entry qualification
Every check on head
82f289e1, not the required subset: 29 of 29 returned againsttotal_count— 26success, 3skipped(dependabot,Test (coverage),Test (coverage shard …)), zero failures, zero still running.Type Checkiscompleted: success, and for this change that is not one green among many: TS2578 makes an unused@ts-expect-erroran error, so a green type-check is itself the proof the tombstone bites. A green test run would have proved nothing — the directive is type-erased.⭐ The finding that justifies the whole card: deleting the key would have been the bug
The obvious implementation of "retire the alias" is to delete
timeScale. Measured, that would have been a silent regression:That is why the retirement is a tombstone in lockstep on both halves —
?: neveronTimelineSchemaandz.never().optional()on the Zod twin — rather than a deletion. This is the same rule this lane has now met three times, in its third face: under an index signature there is no non-membership left to enforce with, so the refusal has to be declared rather than inherited from absence.⭐ It found a third author the card did not list — and it was ungreppable
The card named two in-repo authors. There is a third:
That is invisible to any grep of authored metadata, because it is not authored — it is code writing the key at runtime. Had the fallback read been dropped without it, every object-bound gantt would have silently reverted, and nothing in the corpus would have shown it. Finding it required reading the composition path rather than searching for the spelling.
Writing
scaleafter the spread also corrected a latent precedence inversion:timelineConfig.scalehad been losing to a flatschema.scalebecause the resolver readscale ?? timeScale. Ablation 3 producedexpected 'day' to be 'year'— direct evidence, not an inference.Shown able to fail, in the strict order
The pin was committed before its fix (
ab88df74) and run on that pre-fix tree — so "red before green" is a property of the history, not a claim. Runtime leg failed on the refusal assertion specifically (a timeScale-only document was ACCEPTED — it will silently revert to the month default), with its 4 counter-probes passing, so the file failed for the right reason rather than failing wholesale. Compile leg:tscexit 2 with TS2578. Both green after.The pin asserts the rejection envelope (
invalid_type,expected: 'never', pathtimeScale) rather than merelysuccess: false— so it cannot be satisfied by any old refusal.Three ablations, each with mutation proven on disk (anchored counts on injected and removed text, plus a blob-hash delta) and restoration proven by observation — blob equal to the HEAD blob and
git diff HEADclean — never by an exit code. The no-rebuild claim was measured, not assumed: the pins import the mirror relatively and@object-ui/types/zodis aliased to source invitest.config.mts, so no dist hop exists on these pairs.⭐ The restore leg caught a defect in the dev's own new pin
Worth keeping, because it is the failure mode that survives a green suite:
A directive that guards the wrong line is indistinguishable from a working one until something forces the question. Here the ablation did, and it was fixed and re-ablated rather than noted.
Two gates reported NOT MEASURED, and were treated as such
check:doc-snippetsfirst exited 2 with "PRECONDITION NOT MET — the snippet program was NOT run", andcheck:readme-exportsexited 1 with "the population COLLAPSED — this run proves nothing" (packagesRead 11 against a floor of 25). Both were recorded as NOT MEASURED, never as red, and re-run after building what they name. The green verdicts in the union are from those real runs. A precondition failure read as a pass — or as a failure — would both have been wrong.The gate set was derived manually from
package.json+.github/workflows, with the reason stated: objectui has noscripts/pm/dispatch-gates.mjs, and objectstack's copy answers only about its own tree. Declaring that beats silently running the wrong tool.Scope
All five ruled scope items landed in one PR, each re-verified present on the current
origin/mainbefore editing. Bump isminorwith the break spelled out in the changeset body — no major standoff arose. One out-of-scope finding filed as #6655 (an object-bound timeline withvariant: ganttthrowsRangeError: Invalid time value), proven pre-existing by a byte-identical diff against the merge base rather than assumed to be.⛔ What happens next is the maintainer's
This PR retires a published spelling, so under this card's ruling the merge is a human act and the draft state is the review record. It is green, complete, and waiting. Nothing further is owed by this seat, and this seat will not flip, enqueue, or auto-merge it.
Generated by Claude Code
feat(types)!: retire six dark ChatbotSchema keys as ADR-0049 tombstones
feat(types)!: retire six dark ChatbotSchema keys as ADR-0049 tombston…