Uh oh!
There was an error while loading. Please reload this page.
feat(plugin-timeline)!: retire the timeScale alias — scale is the only axis spelling - #6656
Merged
Merged
Conversation
…n red first The pin precedes its fix on purpose: on this tree `timeScale` is still `TimelineScaleSchema.optional()`, so the timeScale-only document parses green (vitest: 1 failed | 4 passed) and the compile-time half reports TS2578 on the unused directive. Both legs are the red the retirement turns green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
… only axis spelling Maintainer ruling objectui#6355 (2026-08-27): immediate retirement, no phased window, while the project is at startup stage. `timeScale` was this renderer's pre-spec spelling of the gantt axis bucket; `scale` is canonical (@objectstack/spec ui/TimelineConfig.json, and the key the renderer already preferred). The alias is tombstoned on BOTH halves in lockstep — `?: never` on `TimelineSchema` and `z.never().optional()` on the Zod twin — rather than deleted. BaseSchema is .passthrough() with a `[key: string]: any` index signature, so deleting the key would let the retired spelling parse and type-check green while the renderer no longer read it: the gantt axis would silently fall back to the month default and nothing would error. The tombstone is what makes the removal audible. Also: resolveTimelineScale drops the `?? schema.timeScale` fallback; the designer drops its deprecated timeScale input; ObjectTimeline now composes `scale` (it wrote the alias, which would have silently reverted every object-bound gantt); both in-repo authors and the docs are migrated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
…nonical `scale` Covers the schema-catalog fixture, the registration's examples.gantt, the designer inputs, and ObjectTimeline's composed schema — the last of which no grep of authored metadata would find, and the one that would have silently reverted every object-bound gantt to the month default. Each assertion goes through the real resolveTimelineScale rather than reading the key, so a rename that left the value unreachable cannot pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
…sts__ packages/plugin-timeline's test tsconfig carries no node types, so the fs-reading assertions belonged where the repo's other catalog test lives. The registry and designer assertions stay in the package. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
…lidator meet scripts/__tests__ cannot resolve @object-ui/types/zod (scripts/ is not a workspace package, so there is no dependency edge); plugin-timeline's test tsconfig has no node types. packages/types has both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
The existing leg reads the member's type; this one writes a TimelineSchema document, which is what an author or a metadata-generating AI actually produces, and is the leg that proves the tombstone survives BaseSchema's index signature. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
Excess-property checking reports at the member, so the directive one line above the const suppressed nothing: it went unused (TS2578) while the real TS2322 stood. Caught by the ablation's restore leg. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This was referenced Aug 28, 2026
os-elon
approved these changes
Aug 28, 2026
os-elon
left a comment
Collaborator
There was a problem hiding this comment.
Contract review: PASS — director seat, session session_016SG9S6V15MqeAgkehDcTwk, at the contract-review tier (machine-verified this shift). Reviewed on the maintainer's direct summons (live PM chat, 2026-08-28, verbatim: 「#6656 帮我审核 合并」), which is also the landing authorization.
What was verified, against head 82f289e1:
- Ruling fidelity — executes ui#6355's full remaining scope under the 2026-08-27 immediate-retirement ruling: fallback read dropped (
resolveTimelineScalenarrows to{ scale?: unknown }), designer input dropped, docs row replaced with a retirement callout, in-repo authors migrated (catalog fixture +examples.gantt), tombstone per the #5474 lockstep convention. - The failure the change is shaped around is actually closed —
?: never(TS) +z.never().optional()(Zod) on both halves defeats[key: string]: any/.passthrough()silent acceptance; an old-spelling document is refused loudly at the authoring boundary instead of silently re-bucketing tomonth. The envelope-level pin (invalid_type / expectednever/ pathtimeScale) cannot green on an unrelated failure. - The unlisted third author is caught —
ObjectTimeline's composed-schema write moved toscale, without which every object-bound gantt would have reverted silently; the precedence note (timelineConfig.scalenow actually beats a flatschema.scale) is correct by JS spread ordering and pinned. - Independent residue sweep — my own
git grep timeScaleover the PR tree (FETCH_HEAD =82f289e1), non-test sources: every remaining occurrence is the tombstone itself or retirement prose; zero live reads, zero live writes. - Evidence discipline — pin committed red-first with both legs shown failing on the pre-fix tree; ablation/restore with blob hashes on both tombstone halves; CI fully green on this head (4 test shards, Type Check, Lint, changeset policy).
- Versioning —
minorwith the break spelled out, per AGENTS.md fixed-group policy;Changeset Bump Policygreen. #6655 correctly fenced as pre-existing (verified byte-identical halves) and stays open.
Proceeding per 清标即落地: ready → merge, same action.
Generated by Claude Code
os-elon
marked this pull request as ready for review
August 28, 2026 13:16
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#6355
Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
Maintainer ruling (2026-08-27), verbatim: 「6335 和以后这种类似的,都立即退休。项目在创业阶段,用户也很少,短期不考虑渐进。」 — Option A, immediate retirement, no phased window. The staged window was declined and the silent-reversion cost for externally-stored old documents is accepted by the ruling.
Draft on purpose — governed, human merge. Clause-② per this repo's convention: the diff changes a published type. Not enqueued, no auto-merge.
The failure this change is shaped around
Dropping
resolveTimelineScale's?? schema.timeScalefallback is the whole behavioural change, and on its own it is the wrong shape. A stored document spelling onlytimeScalewould stop being read and fall through to the renderer's historicalmonthdefault: the chart changes bucket and nothing errors. That is the silent axis breakage #2942 closed, running in the other direction.So the deliverable is not "
scaleworks". It is that an old-spelling document is refused, loudly, at the authoring boundary.Why a tombstone rather than deleting the key
BaseSchemais.passthrough()on the Zod side and carries[key: string]: anyon the TS side, so an undeclared key is accepted unvalidated by both halves. DeletingtimeScaleoutright would have handed the retired spelling exactly the silent no-op this card exists to prevent — it would parse green, type-check green, and do nothing.Keeping it declared as
?: never/z.never().optional()is what makes the removal audible. Both halves or neither, per the #5474 lockstep convention: either half alone leaves the other surface silently accepting the retired spelling.absentstays valid on both, so a document that never wrote the alias is untouched.The pin, shown red before the fix
The pin was committed before its fix (
ab88df74), on the pre-fix tree, and run there.Runtime leg —
packages/types/src/__tests__/timeline-timescale-retired.test.ts, on the pre-fix tree:The four counter-probes passed, so the file failed on the refusal assertion specifically, not because it was broken. After the fix:
Tests 5 passed (5).Compile-time leg — same tree,
tsc -p tsconfig.test.jsonexit 2:The pin asserts the rejection envelope, not merely
success: false—code: invalid_type,expected: 'never', on thetimeScalepath — so it cannot be satisfied by a document failing for an unrelated reason.Ablation, with the mutation proven on disk and the restore proven by observation
Reverting the Zod tombstone on the committed tree (
data-display.zod.ts):cc5fb86d→5f59f6881 failed | 4 passed5f59f688→cc5fb86d(= HEAD blob),git diff HEADclean5 passedReverting the TS tombstone (
data-display.ts,never→TimelineScale) reddens both compile-time directives (TS2578×2, at the member-type leg and the document leg); restore returns blob5365e337andgit diff HEADclean, tsc exit 0.That restore leg earned its keep: it caught a misplaced
@ts-expect-errorin my own pin — sitting above theconstrather than above the property excess-property checking actually reports on, so it suppressed nothing while the realTS2322stood. Fixed in82f289e1.No dist rebuild leg is claimed or needed: the pins import
../zod/data-display.zod.jsrelatively, and@object-ui/types/zodis aliased to source invitest.config.mts— there is noexports/dist hop on these pairs, so no stale-dist false green is possible.Scope, as the ruling enumerates it
timeScale—?: neveronTimelineSchemaplus the Zod mirror twin (z.never().optional()).scalecontrol already offers all six buckets.resolveTimelineScale's parameter narrows to{ scale?: unknown }.packages/components/.../TIMELINE.md's Gantt table now documentsscalewith the full six-value vocabulary it has accepted since Tier 2 (#2901): spec values that validate and then render nothing #2942 — its row still claimed three.gantt-style-timeline.jsonfixture and the registration's ownexamples.ganttblock.A third in-repo author the card did not list
ObjectTimelinecomposes a schema forTimelineRendererand wrote the resolved axis under the alias. It is invisible to a grep of authored metadata — no author ever sees that object — and it would have sent every object-bound gantt back to themonthdefault the moment the fallback read went, silently, since a composed schema never crosses the authoring boundary the tombstone guards. Migrating it is not a drive-by: dropping the read while leaving this write is simply a broken implementation of the same scope item.Writing
scaleafter the spread also restores the precedence the surrounding code already intends.resolvedScaleistimelineConfig?.scale ?? schema.scale, but the spread carried a flatschema.scalethrough untouched, and the resolver'sscale ?? timeScaleordering then preferred it — sotimelineConfig.scalelost to a flatscale, inverted. The ablation shows this directly: with the alias restored, the pin failsexpected 'day' to be 'year'.Verification
Union re-run on the final commit
82f289e1(git rev-parse --short HEADfrom that run), from the repo root:vitest run(touched suites)Test Files 31 passed (31)·Tests 226 passed (226)turbo run type-check(types, plugin-timeline, components)Tasks: 13 successful, 13 totaltype-check:scriptsturbo run lintTasks: 4 successful, 4 totallint: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-snippetsEvery 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·check-changeset-presence✅ No changeset declares a major bump.·✅ 8 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)check:doc-types·check:doc-fences·docs:check-links·check:designer-field-key-paritycheck:doc-snippetsandcheck:readme-exportsfirst reported PRECONDITION NOT MET / population collapsed — both say so in their own words and name the build they need. Those runs were recorded as not measured, never as red; both were re-run after building what they ask for, and the verdicts above are from the real runs.Version bump
minor, notmajor, per AGENTS.md §版本号策略: objectui's major tracks the@objectstackmajor and all 39 publishable packages share onefixedgroup, so amajorwould drag the whole group off that cadence. The break is spelled out in the changeset body instead.check-changeset-no-major.mjsenforces this; nothing was re-scored to get it green.Out of scope — filed separately
Issue #6655 records an object-bound timeline with
variant: 'gantt'throwingRangeError: Invalid time value:calculateDateRangereads the gantt row shape whileObjectTimelinecomposes feed items. Pre-existing — both halves are byte-identical to the merge base, verified withgit diff— and out of scope here, so#6655 remains open. This PR's composition pin rendersvariant: 'vertical'deliberately so it does not couple to that defect.