Skip to content

fix(plugin-timeline,plugin-charts,i18n): a stored date-only day reads as that day in every zone on the timeline, a chart's date axis and the i18n date helpers (objectui#10866, slice 2) - #10901

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-10866-timeline-charts-i18n-date-only
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-10866-timeline-charts-i18n-date-only

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Part of #10866
Clause-②: no

Slice 2 of the date-only zone family's carrier card: site 3 (plugin-timeline), site 4 (plugin-charts) and site 6 (the @object-ui/i18n date helpers), per triage 5861646819, seat 2's release note 5863305580 and claim 5863746430. Site 5 (core FormulaFunctions) is measured, not edited: the measurement and a recommendation are below. The card stays open.

Why Clause-②: no: display reads of stored date-only values stop shifting a day west of UTC. No authored key, accepted document or public type moves.

What changed

Every read below now takes the shared step, toDisplayDate from @object-ui/core (the objectui#10183 convention). It tells the two shapes apart by the VALUE, never by a declared field type: a date-only YYYY-MM-DD string is rebuilt at local midnight of the day it names, and a value with a time part keeps its instant (the engine parse).

  • Site 3, plugin-timeline.
    • ObjectTimeline: the date bucket (startOfDay(toDisplayDate(raw))) and the sort (a sortKey over toDisplayDate) now read alike. The vertical renderer groups ADJACENT items, so a sort that disagreed with the buckets ordered "Today" above "Overdue" for a mix of shapes. A value the shared step refuses (unparsable, or a day its month does not have) sorts with the dateless items, where it already bucketed ("No date").
    • The renderer's formatDate: the short and long faces read the local-midnight value. The iso face prints a date-only value from local getters (core toDateInputValue), because toISOString() of a local midnight names the day before east of UTC; an instant keeps its UTC day. A refused value prints as written on iso, where toISOString() threw RangeError: Invalid time value during render.
  • Site 4, plugin-charts. AdvancedChartImpl's formatTick reads the category through toDisplayDate. Its fallback for a locale Intl refuses prints a date-only category from local getters and keeps an instant's UTC day (isRealCalendarDate on the category is the split). The span that picks MMM D or MMM YYYY keeps the engine parse on purpose: it is a duration between two categories, a whole number of days for two UTC midnights, where two local midnights are an hour off across a DST change and a 62-day span would flip faces. A refused category falls through to the raw category.
  • Site 6, the i18n helpers. formatDate, formatDateTime, formatRelativeTime (utils/formatting.ts) and formatDateSpec (utils/spec-formatters.ts) parse through toDisplayDate.
    • formatDateSpec applies timeZone to an instant only (isRealCalendarDate on the string is the split, the same test the shared step makes). A date-only value is formatted in the local zone, the only zone where the shared step's local midnight reads back as its day; toDisplayDate's own contract says to read its result with local getters or local-zone Intl only.
    • formatRelativeTime returns an unparsable value as its string, the face the other three already give, instead of throwing. It is needed here: the shared step refuses 2026-02-30, which the engine used to roll.
    • Dependency direction, checked before editing: @object-ui/i18n already depends on @object-ui/core (it re-exports number-display from there) and core imports nothing from i18n. No cycle, and no copy of the helper.
  • Pins. Four files registered in PINS of scripts/__tests__/date-only-zone-pins-10183.test.ts (plus one header sentence): ObjectTimeline.dateOnlyZone-10866, TimelineRenderer.dateOnlyZone-10866, AdvancedChartImpl.dateOnlyZone-10866 and formatting.dateOnlyZone-10866. Each runs under America/Los_Angeles and Asia/Shanghai, each zone opens with a rig case and a fixture-validity case, and each carries an instant control. Suite-zone cases run in the normal run too.
  • packages/plugin-timeline/tsconfig.test.json names node types for the pins' process.env (TypeScript 6 includes no @types by default; packages/plugin-calendar/tsconfig.test.json names it for the same pins).
  • Console chunking (fix round 2, contract review 5864479592 item 1; claim amended at 5864501516). apps/console/vite.config.ts: the i18n-runtime manual-chunk group takes includeDependenciesRecursively: false. This slice gives packages/i18n its first runtime import of @object-ui/core, i18n-runtime outranks framework (83 over 80), and rolldown's default recursive capture followed the import and wrote framework's declared members into i18n-runtime, which turned Bundle Analysis's membership half red. The flag is the objectui#9345 repair the config's data-adapter paragraph documents; the group's new comment records the circular-chunk re-check that paragraph asks for (readings below). scripts/__tests__/check-eager-closure-budget.test.ts pins it: the data-adapter options case is now one case per group, data-adapter and i18n-runtime, each asserting it outranks framework and declares the flag. toDisplayDate keeps its one home in core; nothing is copied into i18n.
  • Docs: one paragraph in content/docs/plugins/plugin-timeline.mdx ("Date Formatting") and one in packages/i18n/README.md ("Formatting Utilities").
  • Changeset .changeset/10866-timeline-charts-i18n-date-only.md: patch for @object-ui/plugin-timeline, @object-ui/plugin-charts and @object-ui/i18n. Fix round 2 extends its gantt sentence (review item ③a): the variant's axis headers, extent and bar positions are unchanged, so west of UTC its bar tooltip now names the stored day while the axis above it still reads the day before, until a later slice. The console chunking change moves no published file, so it needs no declaration (check-changeset-presence counts the same 9 source files of 3 released packages).

Readings, base b8e09415c against head, in both zones

Measured by a throwaway harness on the forks pool with the pins' own fixtures, once over the base sources and once over head (the harness file was deleted; the pins hold the same values).

Timeline, clock 2026-10-06T12:00Z ("today" is October 6th in both zones), en-US:

reading LA base LA head Shanghai base Shanghai head
feed of 2026-10-06 and 2026-10-07 Overdue: Ship (10/5/2026) · Today: Review (10/6/2026) Today: Ship (10/6/2026) · Tomorrow: Review (10/7/2026) Today: Ship (10/6/2026) · Tomorrow: Review (10/7/2026) same
feed of day 2026-10-06 and instant 2026-10-06T04:00Z Overdue: Day (10/5/2026), Instant (10/5/2026) Overdue: Instant (10/5/2026) · Today: Day (10/6/2026) Today: Day (10/6/2026), Instant (10/6/2026) same
item short / long, 2026-10-06 10/5/2026 / October 5, 2026 10/6/2026 / October 6, 2026 10/6/2026 / October 6, 2026 same
item iso, 2026-10-06 2026-10-06 2026-10-06 2026-10-06 2026-10-06
item iso, 2026-02-30 2026-03-02 2026-02-30 2026-03-02 2026-02-30

Chart x-axis ticks, en-US:

categories LA base LA head Shanghai base Shanghai head
2026-09-01, 2026-09-02 Aug 31, Sep 1 Sep 1, Sep 2 Sep 1, Sep 2 Sep 1, Sep 2
2026-09-01, 2026-12-01 Aug 2026, Nov 2026 Sep 2026, Dec 2026 Sep 2026, Dec 2026 same
days, refused locale (fallback) 2026-09-01, 2026-09-02 same same same
instants 2026-09-02T03:00Z, 2026-09-03T03:00Z Sep 1, Sep 2 same Sep 2, Sep 3 same
instants, refused locale (fallback) 2026-09-02, 2026-09-03 same same same
2026-02-28, 2026-02-30 Feb 27, Mar 1 Feb 28, 2026-02-30 Feb 28, Mar 2 Feb 28, 2026-02-30

The two fallback rows are green on base by construction: the old parse was UTC, so its toISOString() was right. They are the half a parse-only repair breaks east of UTC; see the second ablation below.

i18n helpers, clock 10:00 on August 30th local in each zone, en-US:

call LA base LA head Shanghai base Shanghai head
formatDate('2026-09-01') Aug 31, 2026 Sep 1, 2026 Sep 1, 2026 Sep 1, 2026
formatDateTime('2026-09-01') Aug 31, 2026, 5:00 PM Sep 1, 2026, 12:00 AM Sep 1, 2026, 8:00 AM Sep 1, 2026, 12:00 AM
formatRelativeTime('2026-09-01') tomorrow in 2 days in 2 days in 2 days
formatDateSpec('2026-09-01', medium) Aug 31, 2026 Sep 1, 2026 Sep 1, 2026 Sep 1, 2026
same with timeZone: 'America/New_York' Aug 31, 2026 Sep 1, 2026 Aug 31, 2026 Sep 1, 2026
formatDate('2026-09-02T03:00:00.000Z') (instant) Sep 1, 2026 same Sep 2, 2026 same
formatDate('2026-02-30') Mar 1, 2026 2026-02-30 Mar 2, 2026 2026-02-30
formatRelativeTime('not a date') throws RangeError not a date throws RangeError not a date

The Shanghai formatDateTime row moves on the time only (8:00 AM to 12:00 AM): the day was already right there, and a date-only value is now midnight of its day in every zone.

Tests and gates

Fix round 2, at head 52f4d10d0

  • CI on 52f4d10d0: all 43 check-runs completed, 40 success, 3 skipped, 0 failed. Bundle Analysis success (run 36386759427); its budget comment 5864690342 reads PASS, eager closure 3095.7 KB of 3104.5 KB.
  • Bundle Analysis reproduced locally first. Console build under os-verify-lock (cd apps/console && pnpm exec vite build; the gate reads only that step's reports, so the package tsc and build:plugin steps were not run), then node scripts/check-eager-closure-budget.mjs:
    • at 8559187c4, before the flag: exit 1, byte halves green (3096.3 KB of 3104.5 KB), membership red: "packages/core is declared in framework but 54 in i18n-runtime (27 of its 81 modules landed in framework)" and "packages/types ... packages/types/src 2 in i18n-runtime". The reviewer's mechanism holds; the capture is 54 modules of core's barrel slice, not two.
    • at 52f4d10d0: exit 0, every half green: "Console eager closure is 3095.5 KB gzipped across 329 of 2445 chunks (budget: 3104.5 KB, headroom: 9.0 KB)"; membership packages/core 81 modules in framework, packages/types 13 in framework and its src/zod subtree 9 in types-zod.
    • control, a build of main 9f0c84a44 in a separate worktree: exit 0, 3095.6 KB across 329 of 2445 chunks.
  • Circular-chunk caveat, re-checked (the data-adapter paragraph asks a new taker of the flag to): on both builds of this branch framework and i18n-runtime import each other, flag or no flag; on main i18n-runtime imports no framework chunk, and data-adapter and framework import each other there already. So the cycle comes with this slice's i18n-to-core import, not with the flag, and it has the shape data-adapter already has. The flagged build's JS chunk names match main's one for one (2445 each, empty diff). The built i18n-runtime chunk imports one framework binding and reads it only inside the formatRelativeTime function body, never at module evaluation. Browser smoke: vite preview of the flagged build and of the main build, each loaded in Chromium (Playwright): the sign-in page rendered with 0 page errors, and each logged 12 console errors; the eight the probe printed for each build are the same failed resource loads with no backend running (502, one 404). Both preview servers were stopped by their recorded PIDs.
  • The pin, ablated (objectstack scripts/ablation-replace.mjs, wrap mode, the i18n-runtime anchor priority: 83, includeDependenciesRecursively: false }, replaced by priority: 83 },, counted 1 to 0, blob 3e9a3b88d4c6 to df2d1a47a29f): exactly the new i18n-runtime row red, Tests 1 failed | 177 passed (178); restored, blob equal to HEAD, git diff HEAD empty.
  • Suites (os-verify-lock): the 17 scripts/__tests__ suites whose corpus holds apps/console/vite.config.ts, run by name (check-changeset-presence, check-eager-closure-budget, console-vite-alias-closure-4925, cross-file-line-citation-census, scripts-type-check, side-effects-declaration-consistency, turbo-build-inputs, turbo-lint-inputs, turbo-task-guard-coverage, turbo-test-inputs, vite-declared-lazy-views, vite-ineffective-dynamic-imports, vite-objectstack-client-dist, vite-objectstack-spec-dist, vite-types-zod-lazy, vitest-config-alias-targets-3944, vitest-config-type-check): Test Files 17 passed (17), Tests 945 passed (945). Every scripts/__tests__ suite: Test Files 177 passed | 2 skipped (179), Tests 5327 passed | 2 skipped (5329). pnpm exec vitest run packages/plugin-timeline/ packages/plugin-charts/ packages/i18n/ scripts/__tests__/date-only-zone-pins-10183.test.ts: Test Files 193 passed (193), Tests 2576 passed | 40 skipped (2616). Each VERDICT command-exit 0.
  • Type-check: closure rebuilt; the three packages' type-check echo tsc --noEmit && tsc -p tsconfig.test.json and Done; pnpm type-check:scripts and pnpm type-check:vitest-config exit 0 (VERDICT command-exit 0).
  • Gates, exit 0: check-changeset-presence (the same 9 source files of 3 released packages, 1 changeset), -no-major, -fixed, -overwrite, check-pending-changeset-literals, check-control-bytes, check-new-cross-file-line-citations (0 new citations), check-governed-queue-guard --test over 16 paths (NOT GOVERNED). check-changeset-claims (report-only) adds 6681-declared-lazy-marketplace-routes, which names apps/console/vite.config.ts for its marketplace co-tenant groups; this change does not touch them, and it stays true. On the local console build, check-eager-locale-catalogues (NOT MEASURED in round 1) and check-sdui-registration-pins exit 0.
  • ESLint, narrowed to the two files this round adds to the diff: apps/console/vite.config.ts and check-eager-closure-budget.test.ts, 0 errors and 0 warnings each, as at 8559187c4.

Round 1, at 8559187c4

  • Red first, base sources plus the four pins (os-verify-lock, pnpm exec vitest run scripts/__tests__/date-only-zone-pins-10183.test.ts): Tests 5 failed | 13 passed (18), VERDICT command-exit 1. The 5 are the four new pin rows and the child-exit row; the 13 existing pins stayed green.
  • Green: driver plus the four pins in the suite zone, Test Files 5 passed (5), Tests 28 passed | 40 skipped (68), VERDICT command-exit 0 (the 40 skipped are the zone cases, which run inside the driver's forks child).
  • Reverse validation after commit (the five source files checked out from base, driver run, restored with git checkout HEAD -- on absolute paths): the base blobs proven on disk before the run, Tests 5 failed | 13 passed (18), then each restored blob equal to its HEAD blob and git diff HEAD empty.
  • Ablation of the ISO arms (objectstack scripts/ablation-replace.mjs, wrap mode, both anchors counted 1 to 0 with changed blobs): the timeline iso local-getter line deleted and the chart fallback reduced to toISOString(). Measured direction: exactly the two Shanghai rows went red (expected '2026-10-05' to be '2026-10-06'; expected [ '2026-08-31', '2026-09-01' ] to deeply equal [ '2026-09-01', '2026-09-02' ]), Tests 3 failed | 15 passed (18); every LA row stayed green. Both files restored, blob equal to HEAD, git diff HEAD empty.
  • Package suites (os-verify-lock): pnpm exec vitest run packages/plugin-timeline/ packages/plugin-charts/ packages/i18n/ scripts/__tests__/date-only-zone-pins-10183.test.ts, Test Files 193 passed (193), Tests 2576 passed | 40 skipped (2616), VERDICT command-exit 0. The i18n suite includes the objectui#9909 machine-locale census.
  • Every scripts/__tests__ gate (os-verify-lock, pnpm exec vitest run scripts/__tests__/ --maxWorkers=2, at head 8559187c4): Test Files 177 passed | 2 skipped (179), Tests 5326 passed | 2 skipped (5328), VERDICT command-exit 0.
  • Type-check: dependency closure built (--filter 'PKG^...' for each of the three packages, VERDICT command-exit 0); pnpm --workspace-concurrency=2 --filter @object-ui/plugin-timeline --filter @object-ui/plugin-charts --filter @object-ui/i18n run type-check echoes tsc --noEmit && tsc -p tsconfig.test.json and Done for each; pnpm type-check:scripts exit 0. --listFiles on each test program lists the new pins (2, 1, 1).
  • Gates, exit 0: check-changeset-presence (9 source files of 3 released packages, 1 changeset), -no-major, -fixed, -overwrite, check-pending-changeset-literals, check-new-cross-file-line-citations (VERDICT new-cross-file-line-citations: 0 new citation(s)), check-control-bytes, check-unreferenced-sources, check-test-path-roots, check-vi-mock-specifiers / -inherit / -override-shape, check-phantom-dependencies, check-unused-dependencies, check-i18n-call-site-keys, check-i18n-en-drift, check-i18n-dead-keys, check-doc-fence-languages, check-doc-links, check-package-self-import, check-governed-queue-guard --test over the 14 paths (NOT GOVERNED). A control-byte self-scan over the changed files: no hits.
  • check-changeset-claims (report-only) names three pending changesets that cite a touched file: 7546-chart-series-keys-declared (series keys in AdvancedChartImpl.tsx), 8632-malformed-picklist-option-loud and 9615-locale-count-ten (both cite packages/i18n/README.md sections this change does not touch). Each read; none becomes false.
  • ESLint, narrowed: root eslint.config.js with --no-inline-config --format json over the 10 changed code files (10 results in the JSON): 0 errors; the three modified sources keep their base warning counts, measured with --stdin on the base blobs (AdvancedChartImpl.tsx 44, ObjectTimeline.tsx 28, renderer.tsx 14); the new files and the driver 0. The config sets no parserOptions.project or projectService, so the rules are not type-aware and this diff cannot move any untouched file's verdict.
  • NOT MEASURED: check-readme-exports and check-doc-snippet-types (prerequisite: every package built; this change adds prose only, no fenced block, to the README and the doc); check-eager-locale-catalogues (prerequisite: a console build; measured in fix round 2, above). CI builds and runs them.

Site 5, FormulaFunctions: measurement only, no edit

Measured with @objectstack/formula 17.4.0, the version installed here, and read against objectstack packages/formula/src/stdlib.ts and ADR-0053 D1; process zone America/Los_Angeles and Asia/Shanghai, now pinned at 2026-09-28T05:30Z and 2026-09-28T20:00Z.

  • Server (CEL stdlib). There is no TODAY() or DATEADD by those names; the equivalents are today(), daysFromNow(n), addDays(d, n), addMonths(d, n) and daysBetween(a, b). A calendar day is a Date at UTC midnight of that day (ADR-0053 D1), a date-only string hydrates to UTC midnight, arithmetic runs in UTC, addMonths clamps to the month's last day, and today() is the calendar day in EvalContext.timezone (default UTC) as UTC midnight. No result depends on the process zone. For a CEL default on a date field the engine stores such a result as its UTC day (objectql normalizeExpressionDefault through resolveNowDefault).
    • today() at 05:30Z: 2026-09-28T00:00Z with the zone unset or Shanghai, 2026-09-27T00:00Z with Los Angeles; at 20:00Z, 2026-09-29T00:00Z with Shanghai.
    • addDays('2026-09-01', 1): 2026-09-02T00:00Z everywhere; addDays('2026-11-01', 1): 2026-11-02T00:00Z; addMonths('2026-01-31', 1): 2026-02-28T00:00Z.
  • Core FormulaFunctions. TODAY() is the UTC day as YYYY-MM-DD (2026-09-28 in both zones at both clocks), so it agrees with the server only while the reference zone is UTC. DATEADD parses the UTC midnight, moves it with LOCAL setters and returns an instant string:
    • DATEADD('2026-09-01', 1, 'day') gives 2026-09-02T00:00:00.000Z in both zones (agrees);
    • DATEADD('2026-11-01', 1, 'day') in LA gives 2026-11-02T01:00:00.000Z, and DATEADD('2026-03-08', 1, 'day') in LA gives 2026-03-08T23:00:00.000Z (the server: 2026-11-02 and 2026-03-09 at midnight UTC);
    • DATEADD('2026-09-01', 1, 'month') in LA gives 2026-10-02T00:00:00.000Z (Shanghai and the server: October 1st);
    • DATEADD('2026-01-31', 1, 'month') gives 2026-03-03T00:00:00.000Z in every zone, UTC included (the server clamps to February 28th);
    • DATEFORMAT('2026-09-01', 'YYYY-MM-DD') gives 2026-08-31 in LA; DATEDIFF('2025-12-31', '2026-01-01', 'year') gives 0 in LA and 1 in Shanghai and UTC.
    • Output shape: a date-only argument comes back as an instant string, which the display path (toDisplayDate) then reads as an instant, a day early west of UTC.
  • Recommendation. The day arithmetic can agree with the server without a new statement, because ADR-0053 D1 already states the model: read a date-only argument at UTC midnight, add days and months with UTC setters (months clamped as addMonths does), format with UTC getters, and hand a date-only argument back as YYYY-MM-DD (the spec's calendar-day stored form) and an instant as an instant. ONE thing needs a statement, because the server takes it from a context the client does not have: the zone TODAY() uses in the browser. Proposed wording for the seat: "ADR-0053's compute-tz axis covers client-evaluated formulas: TODAY() in @object-ui/core names the calendar day in the same reference timezone the server's EvalContext.timezone resolves for that user, and UTC when none is available to the client." Recommended because ADR-0053 puts today() on the compute-tz axis (execution context), not the render-tz axis (the viewer), and a client formula that disagrees with the server's saved answer is the worse split; the cost is that TODAY() keeps disagreeing with the viewer's own "today" (the timeline's buckets) until that timezone reaches the client.

Acceptance notes

  • The timeline's gantt-variant axis is not changed (the card's site-3 line "the axis extent parses then prints toISOString()"). generateTimeScaleHeaders, calculateDateRange, calculateBarDimensions, the findUnusableGanttDate validity gate and the min-over-max inversion guard still use the engine parse, and emptyGanttDateRange (the empty plan's axis) still takes today as the UTC day (new Date().toISOString().split('T')[0]), not the viewer's. Measured on head: generateTimeScaleHeaders('day', '2026-10-05', '2026-10-07', 'en-US') reads Oct 4, Oct 5, Oct 6 in LA (Oct 5, Oct 6, Oct 7 in Shanghai); 'month' over 2026-10-01 to 2026-12-31 reads Sep 2026, Oct 2026, Nov 2026 in LA. The composed timeline refuses variant: 'gantt' (objectui#6655), so only authored rows reach it; examples/schema-catalog/src/schemas/plugin-timeline/gantt-style-timeline.json authors date-only rows. Left for a later slice of this card: routing it means the validity gate refuses a day its month does not have and the datetime axis labels move to the viewer's day, two decisions of their own. The bar tooltip on that variant uses formatDate, so it now reads the stored day while the headers and bar positions do not: west of UTC a header can read Oct 4 over a bar whose tooltip says 10/5/2026. The changeset says so (fix round 2).
  • Site 6 does have an in-repo caller. The card says none; packages/app-shell/src/utils/relativeTime.ts (timeAgo, for notification and activity rows) calls formatRelativeTime with epoch milliseconds from an instant, which never takes the date-only arm. Its behaviour does not move.
  • formatRelativeTime still counts from now to an instant, now local midnight for a date-only value: at 01:00 on August 31st, 2026-09-01 reads "in 23 hours" (derived, not measured), which is what a UTC viewer always read at 01:00 UTC. Calendar-day phrasing is a separate decision.
  • formatDateSpec with a timeStyle and a date-only value prints midnight in the viewer's zone (and, for long or full, that zone's name).
  • Remaining engine parses in the touched packages, each deliberate: the chart span (a duration, above); the "now" clocks; the gantt-axis reads (above).

Serial

  • PR objectui#10892 merged as b8e09415c at 04:59:24Z, two minutes after the claim. This branch was fast-forwarded onto it before any edit, and plugin-charts/src/ChartRenderer.tsx and plugin-timeline/src/useTimelineTranslation.ts are not touched, so nothing near its hunks had to be left.
  • PR objectui#10896 touches packages/i18n/src/locales/*; this PR touches no locale file.
  • git merge-tree --write-tree of head 52f4d10d0 against main 8522396c0: clean (exit 0); nothing main gained since the branch base touches these 16 paths.

Implemented by the dev agent for domain:ui seat #1, session https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk.


Generated by Claude Code

…as that day in every zone (objectui#10866, slice 2)

The timeline's date bucket, sort and item date, the chart's date-only
x-axis tick, and @object-ui/i18n's formatDate / formatDateTime /
formatRelativeTime / formatDateSpec parsed a YYYY-MM-DD value with the
engine's own Date parse (UTC midnight), so every viewer west of UTC read
the day before. Each now reads it through core's toDisplayDate; a value
with a time part keeps its instant. The ISO faces (timeline iso, chart
locale fallback) print a date-only value's day from local getters, since
toISOString of the local midnight names the day before east of UTC.
formatDateSpec applies its timeZone to an instant only.

Four zone pins registered in the date-only zone-pin driver, each under
America/Los_Angeles and Asia/Shanghai with an instant control.

Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
Co-authored-by: Claude <noreply@anthropic.com>
…ate-only zone pins (objectui#10866)

The pins set process.env.TZ in the forked child and read the driver's flag
off process.env; TypeScript 6 includes no @types by default, so the test
program named neither. packages/plugin-calendar/tsconfig.test.json names
node for the same pins.

Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
Co-authored-by: Claude <noreply@anthropic.com>
…e pin (objectui#10866)

Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation plugin plugin: charts tests package: i18n labels Sep 28, 2026
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 4 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/6681-declared-lazy-marketplace-routes.md

  • names apps/console/vite.config.ts → apps/console/vite.config.ts — edited by this change

    Both marketplace chunks were held by CHUNK CO-TENANCY, not by any import of the page: rolldown had parked components/SuggestedBindingsPanel.tsx (statically imported by the eager views/studio-design/StudioDesignSurface.tsx) in the first, and console/marketplace/InstalledListWidget.tsx (bare-imported by the package barrel for its SDUI registration) in the second. An app-shell-eager-leaves group in apps/console/vite.config.ts isolates those co-tenants so the three declared-lazy pages chunk by their own dynamic-only reachability. The console eager closure moves 3180.2 KB → 3171.5 KB gzipped (−8,888 bytes, 48 → 45 eager chunks) with the three per-chunk ceilings unmoved.

.changeset/7546-chart-series-keys-declared.md

  • names AdvancedChartImpl.tsx → packages/plugin-charts/src/AdvancedChartImpl.tsx — edited by this change

    Every one of the six is read by normalizeSeries (@object-ui/plugin-charts, normalizeChartSchema.ts:242-255) and does real work in AdvancedChartImpl.tsx — label names the legend entry, variant === 'comparison' selects the muted overlay, opacity / dashArray set stroke and fill, stack becomes Recharts' stackId, yAxis binds the secondary axis. Any consumer of the parse output — objectui check / objectui validate via safeValidateSchema, a JSON schema derived from the mirror, or any pipeline that keeps parse()'s result — lost them outright.

.changeset/8632-malformed-picklist-option-loud.md

  • names packages/i18n/README.md → packages/i18n/README.md — edited by this change

    Two new strings land in the designer's own en / zh tables — the metadata-admin console owns its strings in views/metadata-admin/i18n.ts and is deliberately outside the ten locale packs (packages/i18n/README.md, "Scope — the engine.* carve-out").

.changeset/9615-locale-count-ten.md

  • names packages/i18n/README.md → packages/i18n/README.md — edited by this change

    Correcting the numeral to ten was weighed and refused. A restated count is the construct that permitted the error, and this repository has now watched it recur three times over the same fact — objectui#3351 in a changeset file, objectui#7989 in packages/i18n/README.md, and this one in published source. So the number is gone rather than corrected: the sentence now claims only what it needs (the key is translated in every locale the package ships) and names all-locales-key-parity.test.ts as the instrument that re-derives it. That test asserts the stronger invariant — every pack defines every en key — over whatever the pack set is on the day it runs, so an eleventh pack makes the sentence more true rather than false, and there is no figure left to rot (AGENTS.md commandment 完善设计器的每一个细节 #9).

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 9f0c84a44 (merge-base with origin/main): 15 file(s) changed outside .changeset/, read against 1667 pending declaration(s) that publish a body (2261 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3096.5 KB 3104.5 KB
Main entry chunk (gzip) 148.4 KB 350 KB
Entry file index-B-HbkCKq.js —
Status FAIL —

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.

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ✅ pass
Per-chunk ceilings ✅ pass
Per-chunk membership (declared packages) ❌ over its ceiling
Ceiling sensitivity (headroom) ✅ pass
Ceiling freshness (checkout vs. base branch) ✅ pass

📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.58KB 6.17KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 557.59KB 133.60KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 226.44KB 63.00KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 197.67KB 46.90KB
plugin-dashboard (index.js) 136.93KB 36.48KB
plugin-designer (index.js) 215.03KB 44.21KB
plugin-detail (index.js) 233.48KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.15KB 9.14KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 88.55KB 22.21KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.27KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 8559187c4a1e12eabf5afa3d28ca90aec94d4fa3

Read: card objectui#10866 (body, triage 5861646819, seat 2's release note 5863305580, claim 5863746430, dev report 5864266391), PR objectui#10886 and its record 5862881358, PR objectui#10901 (body, diff, 14 files, 43 check-runs on the head), the objectui tree at head and at origin/main 9f0c84a44, objectstack ADR-0053 and packages/formula/src/stdlib.ts. Re-measured in a private worktree at head, on the forks pool with OBJECTUI_DATE_ZONE_CHILD=1: the four pins over head, over the base blobs of the five sources, and over an ablation of the two ISO arms; plus a throwaway probe of core FormulaFunctions, generateTimeScaleHeaders and the i18n helpers under America/Los_Angeles, Asia/Shanghai and UTC. The dev report was read as a claim and checked, not adopted.

① Derived judgments

Blocking

  1. CI is red on the head, and this diff causes it. Bundle Analysis (run 36383523471, step Check console performance budget, comment 5864278471) fails on the eager-closure gate's membership half: "Per-chunk membership (declared packages)" objects while every byte ceiling passes. The merge base origin/main 9f0c84a44 passed the same workflow three minutes earlier and PR objectui#10902 passed it five minutes later, so the diff is the variable. Mechanism, read from apps/console/vite.config.ts and scripts/check-eager-closure-budget.mjs (⛔ not rebuilt here; the job log sits behind a host this session cannot reach): the i18n-runtime group (priority 83) carries no includeDependenciesRecursively: false, so it captures its members' imports recursively at rolldown's default, and it outranks framework (priority 80), whose declared packages core, react and types must land in framework wholly (PER_CHUNK_MEMBERSHIP, an exact claim). On base no packages/i18n module retains a runtime import of @object-ui/core (utils/number-display.ts is a re-export nothing consumes at runtime; builtinAggregateLabels.ts imports a type). This PR adds the first: utils/formatting.ts and utils/spec-formatters.ts import toDisplayDate and isRealCalendarDate, and formatting.ts is retained through app-shell's relativeTime.ts. So packages/core/src/index.ts and utils/date-display.ts are written into i18n-runtime, and core no longer lands wholly in framework. That is the objectui#9345 shape the config's own data-adapter paragraph records, with its repair: declare includeDependenciesRecursively: false on i18n-runtime, re-check the circular-chunk caveat that paragraph says a group taking the flag re-checks rather than inherits, and show Bundle Analysis green on the new head. check-eager-closure-budget.test.ts pins the flag on data-adapter and the carve-out groups and asserts only that framework has no options tail, so nothing there moves. apps/console/vite.config.ts is outside claim 5863746430's surface: the seat amends the claim. ⛔ Copying toDisplayDate into @object-ui/i18n is not a repair (one home, objectui#4576 and objectui#10110).

Per site, re-measured on head: 50 of 50 cases pass in both zones, every rig case among them.

  • Site 3 (ObjectTimeline bucket and sort, renderer.tsx formatDate). Los Angeles at clock 2026-10-06T12:00Z: the feed of 2026-10-06 and 2026-10-07 reads Today: Ship (10/6/2026), Tomorrow: Review (10/7/2026) (base: Overdue: Ship (10/5/2026), Today: Review (10/6/2026)); the mixed feed reads Overdue: Instant (10/5/2026), Today: Day (10/6/2026); 2026-10-06 prints 10/6/2026, October 6, 2026 and 2026-10-06. Shanghai: the same days; the instant 2026-10-06T04:00Z keeps its local day in each zone and its ISO face keeps 2026-10-06 in both. Bucket, sort and face read alike.
  • Site 4 (AdvancedChartImpl formatTick). Los Angeles: 2026-09-01, 2026-09-02 tick Sep 1, Sep 2 (base Aug 31, Sep 1); the month axis Sep 2026, Dec 2026 (base Aug 2026, Nov 2026); a refused locale falls back to the stored days in both zones; the instants 2026-09-02T03:00Z, 2026-09-03T03:00Z tick Sep 1, Sep 2 in Los Angeles and Sep 2, Sep 3 in Shanghai, and their fallback keeps the UTC day in both.
  • Site 6 (i18n). Los Angeles at 10:00 on August 30th: formatDate('2026-09-01') Sep 1, 2026 (base Aug 31, 2026); formatDateTime Sep 1, 2026, 12:00 AM (base Aug 31, 2026, 5:00 PM); formatRelativeTime in 2 days (base tomorrow); formatDateSpec with timeZone: 'America/New_York' Sep 1, 2026 (base Aug 31, 2026 in BOTH zones). Shanghai: the same faces; the instant 2026-09-02T03:00Z reads Sep 1 in Los Angeles and Sep 2 in Shanghai and keeps its timeZone day.
  • Base blobs of the five sources under the head pins: 16 of 50 cases red, every Los Angeles row of the four sites among them, plus the two Shanghai rows that move on the time or on timeZone, and the suite-zone rows for the refused day and the throws. The reds are the reasons the pins name (expected 'Aug 31, 2026' to be 'Sep 1, 2026', RangeError: Invalid time value, RangeError: Value need to be finite number).
  • Ablation of the two ISO arms (the renderer's if (isRealCalendarDate(dateString)) return toDateInputValue(date); deleted; the chart fallback reduced to return d.toISOString().slice(0, 10);): exactly the two Shanghai rows red (expected '2026-10-05' to be '2026-10-06'; expected [ '2026-08-31', '2026-09-01' ] to deeply equal [ '2026-09-01', '2026-09-02' ]), 48 of 50 green. The east-of-UTC rows catch a parse-only repair, as the dev says.

The behaviour beyond a parse swap. All three are right, pinned and named in the changeset: the iso face printing a refused or unparsable value as written (base threw RangeError: Invalid time value during render; pinned in TimelineRenderer.dateOnlyZone-10866); the sort sinking a refused value with the dateless items (its bucket is already "No date", and the vertical renderer groups ADJACENT items, so any other order splits a bucket; pinned through the mixed feed); formatRelativeTime returning an unparsable value as its string (base threw from Intl.RelativeTimeFormat.prototype.format; pinned). Each is needed because the shared step refuses 2026-02-30, which the engine rolled into March.

formatDateSpec applies timeZone to an instant only: right, and the only arm the binding text allows. The shared step's local midnight reads back as its day only in the local zone, so handing Intl another timeZone would name the day before in every zone west of the viewer's. No author who relied on the old behaviour loses the day: timeZone: 'UTC' or an east zone named the stored day before and still does (Sep 1, 2026 with timeZone: 'UTC' in all three zones, measured); a west zone named the day before, which is the defect. What moves is the time face of a date-only value under timeStyle: dateStyle: 'medium', timeStyle: 'long', timeZone: 'Asia/Tokyo' now prints Sep 1, 2026, 12:00:00 AM PDT in Los Angeles (measured), where base printed the UTC midnight as 9:00 AM Tokyo. The changeset names the rule ("applies its timeZone to an instant only, and formats a date-only value in the local zone") and the PR body names the timeStyle face; that is enough for a patch.

The chart span keeps the engine parse: sound. It is a duration between two categories of one axis. Two date-only values are UTC midnights a whole number of days apart in every zone; two local midnights differ by an hour across a DST change, so a span near the 62-day bound would flip faces. An instant pair is the same under either parse.

Site-3 census: four reads, as the dev says. Bucket, sort, formatDate, and the gantt-variant axis, which is left. Measured on head: generateTimeScaleHeaders('day', '2026-10-05', '2026-10-07', 'en-US') reads Oct 4, Oct 5, Oct 6 in Los Angeles and Oct 5, Oct 6, Oct 7 in Shanghai; the month scale over the fourth quarter reads Sep 2026, Oct 2026, Nov 2026 in Los Angeles. Nothing else in the two packages parses a date-only value with new Date or date-fns: the remaining engine parses are the gantt variant (generateTimeScaleHeaders, calculateDateRange, calculateBarDimensions, findUnusableGanttDate, the min-over-max guard, and emptyGanttDateRange's today, a UTC day the dev's list does not name), the "now" clocks, and the chart span. ③ names the disagreement this leaves inside the gantt variant.

Pins. Four PINS rows registered the slice-1 way plus one header sentence; each zone opens with a rig case and, west, a fixture-validity case; each pin carries an instant control; the suite-zone cases run in the normal run. Red on base, green on head in both zones, and the ablation shows the east rows earn their keep (above). Every Test (shard N/8) is green on the head, so the driver ran them there as well.

Site 5. No FormulaFunctions edit: the diff touches nothing under packages/core. The measurement reproduces under my probe: TODAY() is 2026-09-28 in every zone at both clocks (the UTC day); DATEADD('2026-11-01', 1, 'day') in Los Angeles 2026-11-02T01:00:00.000Z; DATEADD('2026-03-08', 1, 'day') in Los Angeles 2026-03-08T23:00:00.000Z; DATEADD('2026-09-01', 1, 'month') in Los Angeles 2026-10-02T00:00:00.000Z; DATEADD('2026-01-31', 1, 'month') 2026-03-03T00:00:00.000Z in all three zones; DATEFORMAT('2026-09-01', 'YYYY-MM-DD') 2026-08-31 in Los Angeles; DATEDIFF('2025-12-31', '2026-01-01', 'year') 0 in Los Angeles and 1 elsewhere. The conclusion is true: ADR-0053 D1 already states the arithmetic (a calendar day as a UTC-midnight Date, addDaysUtc, addMonthsUtc clamping to the month's last day), so DATEADD, DATEDIFF and DATEFORMAT can match stdlib.ts with no new statement; today() reads registerStdLib's timezone (the EvalContext zone, default UTC, resolved per request under Phase 2), which the browser does not hold, so TODAY()'s client zone needs the statement. The dev stopped there, as triage item 3 directs; the option is the seat's to post, not mine.

Clause-②: no is honest. No authored key, accepted document or public type moves; three published helpers change what they return for an existing input class, which is a bug fix.

② Semver level

patch for @object-ui/plugin-timeline, @object-ui/plugin-charts and @object-ui/i18n is right: bug fixes in released packages, no major, and Changeset Bump Policy, Changeset Declaration and Changeset Fixed Group Check are green. Every changeset sentence is true against the measurements above; ③ names one sentence that stops short.

③ Boundary flags

  • CI on head (final, 43 runs): 39 success, 3 skipped, 1 failure, Bundle Analysis (item 1).
  • Ride the same push. (a) Inside the gantt variant this PR now disagrees with itself west of UTC: TimelineGanttBar's title runs formatDate over startDate and endDate, so the bar tooltip prints the stored day while the headers and the bar positions still read the UTC-midnight parse (a header Oct 4 under a bar whose tooltip says 10/5/2026). The composed timeline refuses variant: 'gantt' (objectui#6655), so only authored rows reach it, and examples/schema-catalog/src/schemas/plugin-timeline/gantt-style-timeline.json authors date-only rows. The changeset's "The gantt variant's axis headers and extent are not changed here" stops short: add that the variant's bar tooltip now prints the stored day while its axis does not, until the later slice. (b) Add emptyGanttDateRange's UTC today to the remainder the card carries.
  • Surface outside claim 5863746430: packages/plugin-timeline/tsconfig.test.json (types: ["node"]; the calendar, charts and i18n test configs already name it, and the pins would not type-check without it under TypeScript 6.0.3) and two prose paragraphs (content/docs/plugins/plugin-timeline.mdx, packages/i18n/README.md), both true. With item 1's apps/console/vite.config.ts the seat owes one claim amendment, as slice 1 did.
  • Serial: open PRs objectui#10902, docs(changeset): date-note six pending entries that PRs #10793, #10802 and #10821 made false (objectui#10877) #10891, fix(app-shell): PageView refreshes a page action’s data in place on the invalidation bus instead of remounting the page (objectui#10519) #10777, feat(plugin-grid): one display page size read from the spec, and a distinct fetch batch (objectui#9853) #10278 and the release PR chore: release packages #5400 touch none of these 14 paths at their current heads; git merge-tree --write-tree against origin/main 9f0c84a44 is clean.
  • Out of scope, for the card: the gantt-axis remainder (measured above) and DATEADD's month overflow (2026-01-31 plus one month is 2026-03-03 in every zone, UTC included, where addMonthsUtc clamps to 2026-02-28), which is not a zone defect.
  • PR body: no line address, no angle bracket, no false statement found; its readings match mine row for row. Draft, Part of #10866 on line 1, Clause-②: no on line 2. NOT governed.

Implemented-by: claude/issue-10866-timeline-charts-i18n-date-only
Reviewed-by: session_01DuWo5bdP9SdVebamn99GGk

VERDICT: FAIL

Item 1 stops the landing; ③'s same-push items ride the fix; the rest is the card's.


Generated by Claude Code

…bjectui#10866)

packages/i18n now imports @object-ui/core at runtime (the date helpers
read through toDisplayDate), and i18n-runtime outranks framework, so
rolldown's default recursive capture wrote 54 of core's 81 modules and 2
of types/src into i18n-runtime; Bundle Analysis's membership half went
red. includeDependenciesRecursively: false is the objectui#9345 repair
the data-adapter paragraph documents; the circular-chunk caveat is
re-checked in the group's comment. The eager-closure pin now names
i18n-runtime beside data-adapter.

Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
Co-authored-by: Claude <noreply@anthropic.com>
…he stored day while its axis does not (objectui#10866)

Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the apps label Sep 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3095.7 KB 3104.5 KB
Main entry chunk (gzip) 148.5 KB 350 KB
Entry file index-qYp51RI1.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.58KB 6.17KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 557.59KB 133.60KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 226.44KB 63.00KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 197.67KB 46.90KB
plugin-dashboard (index.js) 136.93KB 36.48KB
plugin-designer (index.js) 215.03KB 44.21KB
plugin-detail (index.js) 233.48KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.15KB 9.14KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 88.55KB 22.21KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.27KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 52f4d10d0a508c593201ba58d46ad3caa5e018d3

Delta review of fix round 2 against record 5864479592 (FAIL on 8559187c4). The head is a fast-forward of two commits over the reviewed one, touching exactly three files: apps/console/vite.config.ts, scripts/__tests__/check-eager-closure-budget.test.ts and the changeset. Read: claim amendment 5864501516, the PR body as applied (r2), the diff 8559187c4..52f4d10d0, the 43 check-runs on the head, budget comment 5864690342, and the job steps of Bundle Analysis, Build & E2E and Live E2E. Re-measured in a private worktree at 52f4d10d0: the budget test with and without the flag; the four zone pins on the forks pool; and three console builds under the heavy-verify lock (the dependency closure built first): the head as is, the head with the flag removed, and the head unminified, each read through chunk-membership.json, scripts/check-eager-closure-budget.mjs and the emitted framework / i18n-runtime / data-adapter chunks' import lines. No dev report was posted for this round; the relayed claims were checked, not adopted.

① Derived judgments

Item 1 (the red Bundle Analysis): repaired, and the flag is the right repair.

  • CI on 52f4d10d0 is settled: 43 runs, 40 success, 3 skipped, 0 failed. Bundle Analysis (check-run 108813638903) is green through every step, and its comment reads PASS at 3095.7 KB of 3104.5 KB.
  • The membership claim, reproduced: the head's build puts packages/core 81 of 81 modules in framework, packages/types 13 in framework and its src/zod subtree 9 in types-zod, packages/react 67 in framework; the budget script exits 0 with "5 budgeted package(s) each landed exactly where declared", 3095.5 KB across 329 of 2445 chunks. With the flag removed and nothing else changed: packages/core 27 in framework and 54 in i18n-runtime, packages/types/src 2 in i18n-runtime, and the script exits 1 with the two messages the PR body quotes. So the group's test now decides its membership, which is what PER_CHUNK_MEMBERSHIP asserts, and the shape is the one data-adapter already carries for objectui#9345. The alternatives are worse: lifting framework above 84 re-runs objectui#7399 (the config says why), and copying toDisplayDate into i18n breaks the one-home rule.
  • The new framework / i18n-runtime chunk cycle is safe. Measured on the head's chunks: framework imports four bindings from i18n-runtime (useObjectTranslation, useObjectLabel, useSafeFieldLabel, pickLocalized) and i18n-runtime imports one from framework (toDisplayDate). In the unminified build every use of those five bindings is inside a function body; the only column-0 mentions are comment lines. At source, packages/react's runtime imports of @object-ui/i18n are hooks and pickLocalized called inside functions, plus barrel re-exports, and en comes from the separate i18n-locale-en chunk, which imports no framework; packages/i18n's runtime imports of @object-ui/core are formatting.ts and spec-formatters.ts, whose module scope holds only imports, interfaces and function declarations. A module-evaluation-order hazard needs a module-scope read of a binding across the cycle, and there is none in either direction; toDisplayDate is besides a function declaration, hoisted at instantiation. Both chunks are eager, so the cycle resolves once at boot, and the boot was exercised on this head by Build & E2E (Build Console for E2E, Run E2E tests in Chromium: success) and Live E2E (Build console, Serve console (vite preview), Run live E2E allowlist: success). The cycle predates the flag, as the config's new comment says: with the flag removed the two chunks still import each other (many bindings each way, because i18n-runtime then holds core's barrel slice), and data-adapter and framework import each other on the same build. The config's "same chunk population as 9f0c84a44" and the sign-in smoke are the dev's readings; I built no main, and the CI browser jobs above stand in for the smoke. The comment's own caveat is right and worth keeping: a future packages/i18n module reading a core value at module scope would need the order re-checked.
  • The pin catches the flag's removal. check-eager-closure-budget.test.ts at the head: 178 passed. With the i18n-runtime line reduced to priority: 83 },: exactly narrows i18n-runtime to its own regex, so it cannot absorb framework's members red, 1 failed | 177 passed, restored to a clean tree. The case also asserts the group outranks framework, so a priority change that made the flag moot would red too; the framework options-tail control is kept.

Item 2 (the changeset's gantt sentence): repaired. The Timeline bullet now says the variant's axis headers, extent and bar positions are unchanged, so west of UTC its bar tooltip, printed through the same item-date function, names the stored day while the axis above it still reads the day before, until a later slice. Every clause is true against the measurements in 5864479592: TimelineGanttBar's title runs formatDate, and generateTimeScaleHeaders / calculateDateRange / calculateBarDimensions are untouched. The frontmatter is byte-identical.

Item 3: emptyGanttDateRange's UTC today and the min-over-max guard are named in the PR body's Acceptance notes as the site-3 remainder. The card body is not edited; the PR body carries it, which is where the seat's release note will read it.

No regression from round 1. The five source files are byte-identical to 8559187c4; the four pins re-run at 52f4d10d0 on the forks pool: 50 of 50 cases pass in both zones, 8 rig cases among them; every Test (shard N/8) and Test (dist pins) is green on the head.

Every sentence checked is true. The changeset (above). The config's new comment: the 54 / 81 / 2 readings, the red-then-green membership half, "the flag adds no cycle", the function-body read — each reproduced here; its two main-anchored readings are marked historical, as the file's convention requires. The PR body's fix-round-2 block: its local numbers match mine to the digit (3095.5 KB across 329 of 2445 chunks; 54 in i18n-runtime, 27 of 81 in framework; 2 of packages/types/src), its pin ablation matches mine, and its CI line matches the head. Two readings to keep straight, neither false: the "What changed" bullet's "No cycle" is the PACKAGE dependency direction (core imports nothing from i18n), while the chunk cycle is a fact of the console build and is stated under fix round 2; and "the console chunking change moves no published file, so it needs no declaration" holds under the presence gate's population (published, executable source of a released package; a build config is not one), while the console's published dist does change chunk attribution — no declaration is owed because @object-ui/console rides the fixed group and bumps with the three patch entries regardless.

Clause-②: no holds. The chunk group is build configuration; no authored key, accepted document or public type moves.

② Semver level

patch for @object-ui/plugin-timeline, @object-ui/plugin-charts and @object-ui/i18n, unchanged and right; Changeset Declaration, Changeset Bump Policy, Changeset Overwrite Report and Changeset Fixed Group Check are green on the head. No major.

③ Boundary flags

Implemented-by: claude/issue-10866-timeline-charts-i18n-date-only
Reviewed-by: session_01DuWo5bdP9SdVebamn99GGk

VERDICT: PASS


Generated by Claude Code

Merged via the queue into main with commit eb97ce6 Sep 28, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10866-timeline-charts-i18n-date-only branch September 28, 2026 09:03
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 28, 2026
…ts name 23 objectui issues that answer 404, and re-qualify 19 bare objectstack numbers (objectui#10803, batch 6) (objectstack-ai#10914)

Part of objectstack-ai#10803
Clause-②: no

Dispatched implementation of the `domain:ui` seat objectstack-ai#1 claim (comment
`5864334310`) on objectui#10803, batch 6, session
`https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk`. Citations
only: no sentence's claim moves, and every edited pending changeset's
frontmatter is byte-identical. The one runtime string that moves is
`InputSchema.wrapperClass`'s zod `.describe()` text in
`@object-ui/types`, which loses its dead pointer and nothing else
(amendment `5860244997`, Q1 = A; `patch` changeset). No test pins any
changed text: the literal-anchor sweep below finds no specific anchor,
so no test file is edited.

This batch carries the release note `5863776648`'s three lists:
- the last **23 family numbers**;
- the **18 bare objectstack numbers above 10900** from PR
objectui#10892's C0 census, plus **#13086**, a 404 the C0 instrument
cannot see because it shares a token with #13337 (**Premise**);
- the live-but-wrong bare `objectstack-ai#3391` at the two `rowCrudAffordances.ts`
sites beside `objectstack#3720`.

## Why `Part of`, not a closing line

The order says `Part of`. With this PR the family list (all 115 numbers
of amendment `5860244997` Q2) and the C0 above-10900 list both read 0.
Whether the card now closes, or carries the dead `objectstack#N` class
measured in **Acceptance notes** 1, is the seat's call.

## Premise, re-measured on `origin/main` `9f0c84a44` (the branch point)

- **The 23 family numbers.** REST `GET
/repos/objectstack-ai/objectui/issues/N`: 23 of 23 answer 404, and a
second read of each answers 404 again. `GET .../pulls/N` answers 404 for
all 23. Lit controls: objectui#10533 and objectui#7714 answer 200.
- **No new family member.** The distinct `objectui#N` citations in the
two in-scope classes at the branch point, less those at batch 1's head
`6c3ad7c80`, are 24 numbers. Each was read once: 24 of 24 answer 200. So
the family list is still batch 5's 23.
- **#14026 was a sister-repo card written as objectui's.**
objectui#14026 answers 404 as an issue and as a pull. objectstack#14026
answers 301 to objectui#10102, the card it was transferred to. That
card's measurement-round claim (branch
`claude/issue-14026-import-mapping-selector-probe`) is the session of
`ecf14190e`, the commit that added `14026-list-import-mappings-pin.md`,
and six sibling in-scope lines already write `objectstack#14026` for the
same card.
- **C0, the bare-number census** (the instrument of PRs objectui#10875
and objectstack-ai#10892, at the branch point):

```
git grep -hoP '(?:^|(?<=[^\w#&/.\-]))#\d+(?![0-9A-Za-z_])' 9f0c84a -- '.changeset/*.md' 'packages/*/src/**' ':!**/__tests__/**' ':!**/*.test.*' ':!**/*.spec.*' ':!**/test/**' ':!**/tests/**' | sort -u | wc -l
```

- 984 distinct at `9f0c84a44`, against 998 at batch 5's `b2683a2c0`. The
14 that batch 5 re-pointed are gone, and none is new.
- Above 10900 there are 23: the same 5 CSS colours and the same 18
numbers. Each of the 18 answers 404 as an objectui issue and as an
objectui pull on two reads.
- In objectstack, 16 of the 18 answer 200 with the sentence's own
subject (the **C0 mapping** below). #13033 and #13413 answer 404 there
too, as issues and as pulls. Their squash commits exist in objectstack's
history (a full, not shallow, clone): `c459da6bc` "narrow the per-option
`default` key out of the form-view options vocabulary … (#13033)" and
`89448a52b` "remove the inert AUTH_SSO_PROVIDER_SCHEMA export (#13413)".
Both are ancestors of objectstack `main` (`git merge-base
--is-ancestor`, exit 0).
- **The C0 instrument's blind spot.** Its lookbehind refuses a `#`
preceded by `/`, `-`, `.` or `&`. That form (`#A/#B`, `-#N`) carries 109
distinct numbers at the branch point. The 17 that no earlier batch read
were each read once. 16 answer 200. **#13086 answers 404**, as an issue
and as a pull, in objectui and in objectstack. It sits in
`6985-wizard-card-r-alignment.md` as "the #13337/#13086 fence", in the
same token as #13337, so it rides here. objectstack's
`check-yaml-examples.ts` header, at its landing `2ebfe7e9f` (PR
objectstack#13267, which answers 200), reads "Check YAML Examples
(anti-drift for the AUTHORING format, #13086)" and "Ruled 2026-08-29
(#13086)".
- **objectstack-ai#3391.** objectui#3391 answers 200 with an unrelated subject (a
record-header api action placeholder). objectstack#3391 answers 200 as
the apiMethods whitelist contract card ("跟踪:UI 操作按钮与 apiMethods
白名单一致性契约落地"), and objectstack#3720's own title calls itself "objectstack-ai#3391
遗漏的第四个面".
- Every edited changeset is pending: it is present in `.changeset/` on
`main`. The checkout is not shallow.

## Census (the enumeration pin for this batch)

The instrument PR objectui#10854 printed and PRs objectui#10869 / objectstack-ai#10875
/ objectstack-ai#10892 reused, with this batch's 23 numbers substituted (REF = a
commit or tree):

```
git grep -nE '(objectui#|#|issues/)(8072|8127|8137|8204|8229|8248|8307|8408|9231|9241|9244|9365|9373|9375|9542|9553|9585|10117|10119|10120|10129|10132|14026)([^0-9]|$)' REF -- '.changeset/*.md' 'packages/*/src/**' ':!**/__tests__/**' ':!**/*.test.*' ':!**/*.spec.*' ':!**/test/**' ':!**/tests/**' | grep -v 'objectstack#' | wc -l
```

- REF = `9f0c84a44` (branch point): **104** lines.
- One more line is hidden by the `grep -v 'objectstack#'` filter:
`9943-viewtype-totals-page-row-retired.md` names objectui#8127 beside an
`objectstack#` citation.
- So the true population is **105** lines: 28 changeset lines in 26
files, and 77 src lines in 42 files.
- Unfiltered, REF reads 111. The other 6 lines are live
`objectstack#14026` lines.
- REF = `2b3d2061a` (this head): **0**. Unfiltered it reads 7, all live:
the six `objectstack#14026` lines and the re-qualified one.
- This head merged with a fresh `main` (`8522396c0`, `git merge-tree
--write-tree`, clean, tree `f19d17884`): **0**. REF = `8522396c0` alone:
104.
- **C0**, the 19 numbers, bare and not `objectstack#`-qualified: 28
lines in 23 files at the branch point; 0 at this head. At this head C0
reads 966 distinct numbers, and above 10900 only the five CSS colours
are left.
- Lit control, the printed instrument over live objectui#7714 at this
head: 17 lines. Hex-colour false positives (a number followed by a hex
letter) at the branch point: 0.
- **Out of scope, as it stands** (the 23 numbers, filtered, whole tree
at this head):
  - 94 test lines in 36 files;
  - 5 `.github` lines in 1 file (`ci.yml`, objectui#9241);
  - 2 `apps/console` lines in 1 file (`FormPage.tsx`, objectui#8408);
  - 1 line of the root `vitest.config.mts`;
  - 0 scripts, 0 governed, 0 `CHANGELOG.md`.
- For the 19 C0 numbers: 13 test lines in 10 files, and 1 line of
`packages/plugin-tree/README.md`.

## Citation form

- **Landing shas.** The 9-character backticked sha of the commit on
`main` that landed the change the sentence rests on, as in the earlier
batches. All 15 distinct objectui shas below are ancestors of `main`:
`git merge-base --is-ancestor`, exit 0 each. Control legs in the same
checkout: the head of PR objectui#10902 (`496c63c06`) answers exit 1,
and the known ancestor `5f789538d` answers exit 0. `git rev-parse
--short=9` returns the same 9 characters for each.
- **Own-card pointers** in a changeset are dropped, not replaced. The
configured changelog generator (`@changesets/cli/changelog`, per
`.changeset/config.json`) prefixes each released entry with the hash of
the commit that added the file, which is that landing.
- **The claim lived only on the card.** Where the sentence rests on
something that lived only on the dead card, the sha only locates it:
"the card behind SHA".
- **Nothing answers.** The pointer is dropped and the sentence names the
card by role (objectstack-ai#9553 only).
- **Sister-repo numbers.**
- A bare sister-repo number becomes `objectstack#N` after reading it
there.
- "objectstack PR #N" becomes "PR objectstack#N", since the qualifier
now carries the repository. "framework #N", "upstream #N" and "spec #N"
keep their word, as batch 4 kept "framework PR objectstack#6942".
- Where the objectstack pull request itself answers 404, the sentence
cites its commit in that repository as objectstack `SHA`, the spelling
the tree already uses for an objectstack commit ("Measured on
objectstack `9bd4344e4`").
- **Runtime text** carries no sha: see **Special cases** 6.

## Mapping, the 23 family numbers

Lines / files are the branch-point census for that number. "Method" is
how the landing was found; for every source site, `git log -S
'objectui#N' -- FILE` names the commit that wrote the citation, and it
is the landing below unless the row says otherwise.

| dead number | resolution | method | lines / files | why the commit
carries it |
|:--|:--|:--|:--|:--|
| objectstack-ai#8072 | `c974edf14`; own-card pointer dropped; runtime pointer dropped
| changeset's adding commit, subject "(objectui#8072)" | 5 / 3 | it
mirrors `wrapperClass` on `InputSchema`, which is what both comments and
the `7722` changeset say happened |
| objectstack-ai#8127 | `ca3942729`; "the card behind `ca3942729`" twice (**Special
cases** 2) | `git log -S` names `ca3942729` for the first citation in
all 10 source files; `05a49f2ee` (objectui#9880) and `8a7e09fa1` (the
`9943` changeset's landing) wrote three later sentences that cite the
same fix | 17 / 11 | it derives `ViewType` from `@objectstack/spec`
instead of re-declaring it, and its message records the
`page`-degrades-like-a-typo measurement the sentences cite |
| objectstack-ai#8137 | own-card pointer dropped (landing `0fa7a9c83`) | changeset's
adding commit | 1 / 1 | the only site is its own changeset's pointer |
| objectstack-ai#8204 | own-card pointer dropped (landing `580b0fdf4`) | changeset's
adding commit | 1 / 1 | the pointer opened a paragraph as its own
sentence |
| objectstack-ai#8229 | "a separate finding" (changeset); "the finding `8b7ea3945`
reconciled" (`flex.tsx`) | `8b7ea3945`'s message: "Reconciles the third
face objectui#8229 found" | 2 / 2 | the finding lived on the dead card;
`8b7ea3945` is the commit that reconciled it, and it is the `7735`
changeset's own landing |
| objectstack-ai#8248 | own-card label dropped from the second list item (landing
`d02942b0e`) | changeset's adding commit; its message covers
objectui#8458 and objectui#8248 | 1 / 1 | the changeset lists the two
cards its one landing closed |
| objectstack-ai#8307 | `5591f03bd`; own-card pointer dropped | changeset's adding
commit | 11 / 4 | it makes a lane header over a windowed fetch say
`77+`, and it wrote every comment that cites the card |
| objectstack-ai#8408 | own-card pointer dropped; "seam 2 of the card behind
`8241a4400`" | changeset's adding commit; `8241a4400`'s message names no
seams | 2 / 2 | the seam list lived on the dead card |
| objectstack-ai#9231 | `383502b23`; own-card pointer dropped | changeset's adding
commit | 3 / 2 | it gives the create dialog's confirm control its own
accessible name, the rule both `i18n.ts` comments state |
| objectstack-ai#9241 | own-card pointer dropped (landing `250429c8f`) | changeset's
adding commit | 1 / 1 | the only site is its own changeset's heading |
| objectstack-ai#9244 | `bd0995738` | `git log --grep`: its message names the card; it
wrote all three comments | 3 / 2 | it emits one col-span class per
breakpoint tier, not one for the widest |
| objectstack-ai#9365 | own-card pointer dropped (landing `0970a0e00`) | changeset's
adding commit | 1 / 1 | the only site is its own changeset's pointer |
| objectstack-ai#9373 | `c1006ed8e`; own-card pointer dropped | changeset's adding
commit | 2 / 2 | it resolves the inline locale map before the
interpolation options, the `ListView` comment's subject |
| objectstack-ai#9375 | own-card pointer dropped (landing `e427e9c00`) | changeset's
adding commit | 1 / 1 | the only site is its own changeset's pointer |
| objectstack-ai#9542 | `43c0d1710`; own-card pointer dropped; "filed as a separate
card" (**Special cases** 3) | changeset's adding commit; `git log -S`
names it for the `action-button` / `action-icon` comments too | 9 / 6 |
it accepts and resolves an inline `I18nLabel` on `resultDialog` and
makes `ResultDialogSpec` derive its label members |
| objectstack-ai#9553 | nothing answers: "a separate card carried that census" | `git
log --grep` finds only `40f34b4ba`, which points at the card; no commit
lands it | 1 / 1 | see **Special cases** 4 |
| objectstack-ai#9585 | "`ee70287e4`'s pin measures it NOT GATED"; in its own
changeset, "a pin in this change measures it" | no commit names it;
`ee70287e4` adds the test "NOT GATED: the renderer paints the very node
the mirror refuses, without parsing it" | 2 / 2 | see **Special cases**
5 |
| objectstack-ai#10117 | `4c6f549ef`; own-card pointer dropped | changeset's adding
commit | 3 / 2 | it resolves a lookup title candidate in `page:header`
and adds the record-key safety net both comments describe |
| objectstack-ai#10119 | `73a3c89af`; own-card pointer dropped | changeset's adding
commit | 4 / 2 | it makes a nav ancestor's gates reach its subtree and
stops a group outliving its children |
| objectstack-ai#10120 | `80c54122e`; own-card pointer dropped | changeset's adding
commit; for each file `git log -S` names it, or a later commit that
cites its gate: `e0f820246` (objectui#10563) or `b809375ac`
(objectui#10163) | 16 / 13 | it makes a form neither submit nor offer a
field the caller may read but not edit, the FLS half every site names |
| objectstack-ai#10129 | `6cc910b6d`; own-card pointer dropped | changeset's adding
commit | 10 / 5 | it routes a field-backed action param to its record
picker and refuses an unreadable one |
| objectstack-ai#10132 | `061f5e829`; own-card pointer dropped | changeset's adding
commit | 8 / 4 | it makes the two declared-translatable dashboard
surfaces resolve, including the axis `title` forward |
| #14026 | re-qualified `objectstack#14026` | see **Premise** | 1 / 1 |
it is the card the pin's measurement answered |

## Mapping, the 19 bare sister-repo numbers

| number | resolution | what objectstack says | lines / files |
|:--|:--|:--|:--|
| objectstack-ai#11289 | `objectstack#11289` ("upstream") | "record:details sections
cannot survive an empty record: `hideEmpty` / `collapsible` /
`showBorder` are honoured by the renderer but undeclared" | 2 / 2 |
| #11662 | PR `objectstack#11662` | "feat(spec): declare hideEmpty /
collapsible / showBorder on record:details sections" | 1 / 1 |
| #12616 | PR `objectstack#12616` | "feat(spec): declare record:details
section headerColor as a closed six-token enum" | 1 / 1 |
| #12718 | PR `objectstack#12718` | "feat(spec): retire preview mode —
the RuntimeMode 'preview' value and the whole PreviewModeConfig block" |
1 / 1 |
| #13033 | objectstack `c459da6bc` | the pull request answers 404; its
squash commit narrows the per-option `default` key out of the form-view
options vocabulary, the ruling `form-spec.ts` says was executed upstream
| 1 / 1 |
| #13337 | `objectstack#13337` | "docs(objectui): layout-dsl teaches
only shapes the live schemas accept" | 1 / 1 |
| #13086 | `objectstack#13267` | the card answers 404; PR
objectstack#13267 landed its ruled YAML-examples gate (see **Premise**)
| the same line |
| #13413 | objectstack `89448a52b` | the pull request answers 404; its
squash commit removes an inert schema export and leaves a note recording
the absence as a choice, the shape the `base.zod.ts` note cites as
precedent | 1 / 1 |
| #13632 | `objectstack#13632` | "[spec] `FieldSchema` accepts a
`lookup`/`master_detail` with no `reference` target …", the card
17.3.0's refinement answered | 3 / 3 |
| #13733 | PR `objectstack#13733` | "feat(spec): wizard view v1 —
declaration-and-refusal tightening of FormViewSchema type:'wizard' (Card
S)" | 1 / 1 |
| #13855 | `objectstack#13855` | the field-grouping decision card whose
option B is a section `group` reference, landed by `39404f3d9` (#13897)
"a layout section can reference a declared field group" | 1 / 1 |
| #13906 | `objectstack#13906` ("framework") | "two more
`computeExecCtx` seams read "failed" and "not wired" as one value …" | 6
/ 3 |
| #14274 | PR `objectstack#14274` | "fix(sdui-parser): refuse an
authored `type` attribute on the html tier …" | 1 / 1 |
| #14945 | `objectstack#14945` | "A flow cannot REFUSE with per-record
text …", honoured by `cca699149` "the flow `end` node honours `outcome:
'refused'`" | 1 / 1 |
| #15469 | `objectstack#15469` ("spec") | "`GanttConfigSchema` is
`strictObject(...).passthrough()` …", landed as `9c270bba0` "close the
gantt/tree config .passthrough() windows … (#15469)" | 2 / 2 |
| #15948 | `objectstack#15948` | "fix(plugin-auth)!: session payload
`positions[]` is the security axis, not the better-auth role scalar" | 1
/ 1 |
| #17493 | `objectstack#17493` | "three residues of #17322's node-door
refusal …", landed as `2c1011b01` "refuse a blank string in a flow
node's predicate slot" | 1 / 1 |
| #20051 | `objectstack#20051` | "judge a flattened view overlay's
top-level `options.KIND` …", whose door half is `6a4aec71d` | 1 / 1 |
| #20160 | PR `objectstack#20160` | "fix(spec): a joined report refuses
a top-level dataset / rows / columns / values, pointing each onto
blocks[]" | 2 / 2 |

The live-but-wrong number: "(`/me/permissions` `apiOperations`, objectstack-ai#3391)"
becomes "… `apiOperations`, objectstack#3391)" at the two
`rowCrudAffordances.ts` sites.

## Special cases (the judgement calls)

1. **#14026 is re-qualified, not dropped as an own-card pointer.** The
card behind `14026-list-import-mappings-pin.md` was never an objectui
card: it was objectstack#14026, now objectui#10102. Dropping the pointer
would lose which hypothesis the pin refuted. Re-qualifying it matches
the six sibling lines.
2. **objectstack-ai#8127, the card by role twice.**
- `CreateViewDialog.tsx`: "one of the sites the card records as
"drifted, …"" becomes "one of the sites the card behind `ca3942729`
records as …". That record lived on the card.
- `plugin-list` `ViewSwitcher.tsx`: "objectui#8127 was filed against the
two in `@object-ui/types`, and the maps below were described there as
total …" becomes "the card behind `ca3942729` was filed against …".
- `normalize-list-view.ts`: "The bug objectui#8127 records" becomes "The
bug `ca3942729` records". That commit's message carries the measurement:
`page` resolved "to exactly what it resolved a typo to".
3. **objectstack-ai#9542 in `8648-ui-action-four-undeclared-keys.md`.**
- The superseded paragraph keeps its pre-landing voice: "so it is filed
as a separate card and ⛔ not guessed at here".
- The superseding paragraph reads "`43c0d1710` landed the derivation".
- "(Noted here by the objectui#9542 seat, …)" becomes "(Noted here by
the seat that landed `43c0d1710`, …)".
4. **objectstack-ai#9553, nothing answers.** `base.zod.ts` said "No count of authored
`events` keys is stated here — objectui#9553 carries that census." No
commit landed that card. `40f34b4ba` only points at it, and the
`AGENTS.md` fix `7550728a6` names a different card. So the sentence now
reads "— a separate card carried that census". The census is not
restated, per AGENTS.md objectstack-ai#9.
5. **objectstack-ai#9585, the measurement's instrument.** The card measured the render
path NOT GATED, and no commit names it. But the commit that wrote both
citing sentences, `ee70287e4`, adds a pin that re-measures exactly that:
"NOT GATED: the renderer paints the very node the mirror refuses,
without parsing it".
- `disclosure.ts` now reads "(`ee70287e4`'s pin measures it NOT GATED)".
- Its own changeset, `8236-collapsible-open-intercept-retire.md`, whose
landing is `ee70287e4`, reads "(a pin in this change measures it NOT
GATED)".
6. **The runtime string.** Only the listed text moves.

| file | member | before | after |
|:--|:--|:--|:--|
| `types/src/zod/form.zod.ts` | `InputSchema.wrapperClass` `.describe()`
| "Classes on the wrapper div around the input and its label
(objectui#8072)" | "Classes on the wrapper div around the input and its
label" |

No test, doc or changeset quotes it: a whole-tree search for the old
string returns only the source line.
7. **objectstack-ai#8229 in `flex.tsx`.** "(objectui#8229, folded into objectui#7735's
ruling)" becomes "(the finding `8b7ea3945` reconciled, folded into
objectui#7735's ruling)". objectui#7735 answers 200 and stays.
8. **#13413's sentence.** "Precedent of the same shape:
objectstack#12009 / PR #13413." becomes "… objectstack#12009 /
objectstack `89448a52b`." objectstack#12009 answers 404 too, but it is a
qualified sister-repo number outside this batch's lists, so it is left
and listed (**Acceptance notes** 1).
9. **#20160 across a line break.** In
`10746-joined-report-clears-binding.md`, "(objectstack PR" ends one line
and "#20160: …" opens the next. They become "(PR" and
"objectstack#20160: …".

## The literal-anchor sweep (both test-pin classes, ruling `5861900779`)

- **Instrument.** Every string, template, numeric and regex literal in
all 4068 tracked test and script files (119016 distinct literals),
parsed with TypeScript.
- **Candidate filter.** A literal is a candidate only if it matches the
diff's removed lines with two lines of context, raw or
comment-flattened. That leaves 2839.
- **Test applied to each candidate.** Does its occurrence count DROP
between `9f0c84a44` and `2b3d2061a` in any of the 92 files this PR
changes, in raw text or in a comment-flattened form? 229 distinct
literals drop.
- **Every one is a generic token.** Digits and short numbers,
punctuation, single letters, `objectui#`, `objectui#1`, and whole-tree
scanners such as `/#\d+/` and `/objectui#\d+/`. The three `objectui#\d+`
matchers (`ActionRunner.disabledGate`, `registry-inputs-spec-parity`,
`catalog-gallery-render`) assert over their own test data and read no
changed file.
- **No specific anchor.** None is a batch number, a changed phrase, or
the changed `.describe()` string. So no test pins changed runtime text
(class one), and no source-reading test pins a changed comment or
docblock citation (class two).
- Test titles and comments that name these numbers are out of the card's
classes and stay (**Census**, out of scope).

## Held

**By the serial rule: nothing.** Re-mapped before the push, on 9 open
PRs: objectui#10910, objectstack-ai#10908, objectstack-ai#10907, objectstack-ai#10906, objectstack-ai#10901, objectstack-ai#10891, objectstack-ai#10777,
objectstack-ai#10278 and the release PR objectstack-ai#5400 (objectstack-ai#10902 and objectstack-ai#10904 had merged since the
claim).

Two of them share files with this PR. Their hunks were read against
their merge-bases; each file is identical at its merge-base and at
`9f0c84a44`, so the lines map directly.
- **objectui#10907, `types/src/zod/form.zod.ts`.** It inserts a refusal
constant before `InputSchema`, two tombstone members further down
`InputSchema`, and a comment in the `InputShorthandSchema` arm. This
PR's three edited lines (the `wrapperClass` comment and description, and
the arm's "shrank that row" comment) sit outside every one of its hunks
and their 3-line context.
- **objectui#10906, `metadata-admin/i18n.ts` and
`previews/ViewPreview.tsx`.** Its `i18n.ts` insertions are far from the
two `createDraft` comments, and its `ViewPreview.tsx` hunks are far from
the `options` fold docblock.

Trial merges with this head (`git merge-tree --write-tree`):
- clean for objectstack-ai#10910, objectstack-ai#10908, objectstack-ai#10907, objectstack-ai#10906, objectstack-ai#10901, objectstack-ai#10891 and objectstack-ai#10777;
- objectstack-ai#10278 (`eab4c8e52`) conflicts in `ObjectGrid.tsx`,
`plugin-grid/README.md` and `content/docs/plugins/plugin-grid.mdx`, and
conflicts identically against `9f0c84a44` alone;
- objectstack-ai#5400 (Version Packages) regenerates and is not a hold.

objectui#10891 shares no file with this PR.

## Changesets

- `.changeset/10803-dead-citation-sweep-sixth-batch.md`, EMPTY
frontmatter. It covers the comment-only edits in 15 released packages;
no published behaviour changes through them. It points at the second
file for `@object-ui/types`' runtime text.
- `.changeset/10803-sixth-batch-runtime-strings.md`,
`'@object-ui/types': patch`: the `wrapperClass` description loses its
pointer. No key, path, issue code, accept set, refusal or severity
moves.

## Proof of prose-only (C4), against `9f0c84a44`, on this head
`2b3d2061a`

- **Source.** Each of the 53 touched `.ts` / `.tsx` files was parsed at
`9f0c84a44` and at this head with TypeScript 6.0.3's `createSourceFile`,
and re-printed by `createPrinter({ removeComments: true })`.
  - 52 of 53 prints are identical.
- `form.zod.ts` is equal once the one listed substitution (**Special
cases** 6) is applied to the base print, matched once.
  - 0 parse diagnostics.
- Lit controls on the same instrument: editing a string literal moves
the print; re-spacing a comment does not.
- **Changesets.** The frontmatter block of every one of the 37 edited
changesets is byte-identical at `9f0c84a44` and this head (37 of 37, by
md5). The overwrite gate below agrees.
- **Scope of the diff:** 92 files, +175 / −140. That is 37 edited and 2
new changesets, and 53 non-test source files in 15 released packages. No
test file.

## Gates, on this head `2b3d2061a`

Each line is the gate's own verdict and exit code, captured by
redirect-then-`$?`.

- `node scripts/check-changeset-presence.mjs`, exit 0: "53 source
file(s) of 15 released package(s) changed, and this change declares 2
changeset(s): .changeset/10803-dead-citation-sweep-sixth-batch.md,
.changeset/10803-sixth-batch-runtime-strings.md."
- `pnpm changeset:check`, exit 0: "All workspace packages are in the
changeset fixed group." / "No changeset declares a `major` bump."
- `node scripts/check-changeset-overwrite.mjs` (report-only), exit 0: "2
changeset(s) added, 37 modified, 0 deleted". `declared at base` equals
`declares now` for 37 of 37.
- `pnpm check:changeset-claims` (report-only), exit 0:
- "Every one of those 1 address(es) either names the tree it was read
from, or points at a line this change does not move";
- "Every package declared across those 31 body(ies) is either not
negated …";
- the standing notice "75 pending changeset(s) describe a file this
change touches".
- Read against the diff: a pending changeset quoting a replaced pointer
would itself carry the dead number and sit in the census, which reads 0.
No pending changeset quotes the changed description.
- `pnpm check:control-bytes`, exit 0: "check-control-bytes: OK (scanned
9178 tracked text file(s); skipped 85 binary)."
- `pnpm check:new-line-citations`, exit 0: "VERDICT
new-cross-file-line-citations: 0 new citation(s), enforcement
report-only -> exit 0".
- `pnpm check:pending-changeset-literals`, exit 0: "No test source names
a pending changeset."
- Also run over the touched comments:
  - `pnpm check:installed-pin-claims`, exit 0 ("OK");
- `pnpm check:comment-mask-corpus`, exit 0 (1 disagreeing file, within
the ceiling objectui#7882 holds open);
- `pnpm check:handler-key-reads`, exit 0 ("every judged read is a
declared member of it").
- The governed-surface predicate over the 92 paths, exit 0: "NOT
GOVERNED — 92 path(s) checked against 5 governed surface(s); none
matched." Lit control `AGENTS.md`: exit 3.

**Tests**, through the shared verify lock, on `2b3d2061a`. Each is
`VERDICT command-exit 0`.
- `scripts/__tests__/`, the whole directory, whose whole-tree scanners
read the touched files: `Test Files 177 passed | 2 skipped (179)`,
`Tests 5322 passed | 2 skipped (5324)`. The two skipped files are the
network-escape fixtures that run only as a child.
- `packages/types/`, the whole package, whose runtime text moved: `Test
Files 264 passed (264)`, `Tests 5854 passed (5854)`. `pnpm --filter
@object-ui/types type-check`, exit 0.
- **The pins nearest the re-pointed text:**
- in `types`: `input-wrapper-class-mirrored-8072`, `zod-mirror-parity`,
`wrapper-class-declared-7722`, `zod-mirror-authors-no-defaults-7735`,
`collapsible-open-refusal-8236`, `tree-view-config-readers-8253` and
`layout-default-jsdoc-7361`;
- in `components`: `collapsible-open-intercept-8236`,
`registration-defaults-match-renderer-8229` and
`action-undeclared-keys-8648`;
- in `plugin-view`: `ViewSwitcher` and
`ViewSwitcher.viewTypeTotalsBothLegs-9943`;
  - in `plugin-kanban`: `laneCountHonesty-8307`;
  - in `plugin-grid`: `rowCrudAffordances` and `rowCrudEffectiveOps`.
  - Result: `Test Files  15 passed (15)`, `Tests  381 passed (381)`.
- No red leg: the sweep found no anchor to move, so there is no pin
whose old copy should fail.

**Declared narrowing.** NOT MEASURED locally:
- the full suites and type-check of the 14 other touched packages, and
eslint.
- Reason: the comment-stripped syntax tree of 52 of 53 touched source
files is identical to `main`, and the 53rd differs only by the listed
literal.
- CI runs the full farm.

## Acceptance notes

1. **A sister-repo class of the same defect: 30 `objectstack#N`
citations answer 404 in objectstack.**
- **Measurement.** Every distinct `objectstack#N` in the two in-scope
classes at the branch point is 359 numbers. Each was read once: 328
answer 200, 1 answers 301 (objectstack#14026 to objectui#10102), and 30
answer 404, each confirmed by a second read.
- **The 30:** objectstack-ai#5970 objectstack-ai#5976 objectstack-ai#6038 objectstack-ai#6124 objectstack-ai#6281 objectstack-ai#6331 objectstack-ai#6450 objectstack-ai#6483 objectstack-ai#6515
objectstack-ai#9933 objectstack-ai#9934 objectstack-ai#10354 objectstack-ai#10485 objectstack-ai#10695 objectstack-ai#11330 #11507 #11513 #11658 #11703
#11753 #11846 #12009 #12868 #13117 #13670 #16126 #17147 #17762 #17987
#18012.
- **Sites at this head:** 82 lines in 63 files, 26 of them changeset
lines.
- **Three of them sit in sentences this PR edits, and are left as they
are:**
- objectstack#11846, "(objectstack#11846, landed as PR
objectstack#12718)" in `6748-preview-mode-provenance-ratchet.md`;
     - objectstack#12009, in `base.zod.ts`'s precedent line;
- objectstack#12868, in `form-spec.ts`'s "RULED 2026-08-28
(objectui#6263 / objectstack#12868, …)".
- **Why left.** They were not in this batch's lists, and the class is
the seat's to scope. objectstack's own card for dead tracker citations
in its tree is objectstack#19123, whose landing `66e266c93` counts
#12868 among the dead numbers it measured. Triage item 3 would put a
dead number in these classes on this card. The fix shape measured here
for such a number is the objectstack commit, as with `c459da6bc`.
2. **The rest of the bare `objectstack-ai#3391` population.** Only the two sites the
claim names were re-qualified. 28 more in-scope lines in 10 files write
the apiMethods whitelist card as a bare `objectstack-ai#3391`, which resolves to the
unrelated objectui#3391:
   - `ObjectDataPage.tsx`, `ObjectView.tsx` (app-shell);
   - `managedBy.ts`;
- `MePermissionsProvider.tsx`, `PermissionContext.ts`,
`PermissionProvider.tsx`;
   - `fieldWriteGate.ts`;
   - `ImportWizard.tsx`, `ObjectGrid.tsx`;
   - `ListView.tsx`.

It is live, not a 404, so it is outside the family pin. Two of those
lines pair it with a bare 3546 that means objectstack#3546 ("detail/form
面的 edit/delete 按钮接入服务端 effective 操作集"), while objectui#3546 is an
unrelated i18n card. Carrier: none.
3. **A stale claim beside a re-pointed sentence.** The same
`base.zod.ts` paragraph says "AGENTS.md's abridged protocol sketch shows
`events?: Record…` and its action-system commandment authors one".
`7550728a6` removed both from `AGENTS.md`, so that sentence is now
false. It is not a citation, and this PR does not touch it. Carrier:
none.
4. **The C0 blind spot, for any later census.** The bare-number
instrument's lookbehind hides a number written after `/`, `-`, `.` or
`&` (`#13337/#13086`, `-objectstack-ai#2231`). This batch read the 17 such numbers no
batch had read, and #13086 was the only 404. A later census can narrow
the lookbehind to refuse only a word character, `#` or `&` before the
`#`, and drop URL fragments by hand.
5. **Filenames are not citations.** Pending changeset and test FILENAMES
carry several of these numbers. They stay, as in PRs objectui#10707,
objectstack-ai#10797, objectstack-ai#10854, objectstack-ai#10869, objectstack-ai#10875 and objectstack-ai#10892.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk)_

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants