Repository navigation
Commit eb97ce6
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)
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](https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 99e936b commit eb97ce6
16 files changed
Lines changed: 909 additions & 29 deletions
File tree
- .changeset
- apps/console
- content/docs/plugins
- packages
- i18n
- src
- __tests__
- utils
- plugin-charts/src
- __tests__
- plugin-timeline
- src
- __tests__
- scripts/__tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1059 | 1059 | | |
1060 | 1060 | | |
1061 | 1061 | | |
1062 | | - | |
| 1062 | + | |
| 1063 | + | |
| 1064 | + | |
| 1065 | + | |
| 1066 | + | |
| 1067 | + | |
| 1068 | + | |
| 1069 | + | |
| 1070 | + | |
| 1071 | + | |
| 1072 | + | |
| 1073 | + | |
| 1074 | + | |
| 1075 | + | |
| 1076 | + | |
| 1077 | + | |
| 1078 | + | |
| 1079 | + | |
| 1080 | + | |
| 1081 | + | |
| 1082 | + | |
| 1083 | + | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
1063 | 1094 | | |
1064 | 1095 | | |
1065 | 1096 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
326 | 326 | | |
327 | 327 | | |
328 | 328 | | |
| 329 | + | |
| 330 | + | |
329 | 331 | | |
330 | 332 | | |
331 | 333 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
163 | 163 | | |
164 | 164 | | |
165 | 165 | | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
166 | 172 | | |
167 | 173 | | |
168 | 174 | | |
| |||
Lines changed: 190 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
5 | 16 | | |
6 | 17 | | |
| 18 | + | |
| 19 | + | |
7 | 20 | | |
8 | 21 | | |
9 | 22 | | |
| |||
35 | 48 | | |
36 | 49 | | |
37 | 50 | | |
38 | | - | |
| 51 | + | |
39 | 52 | | |
40 | 53 | | |
41 | 54 | | |
| |||
63 | 76 | | |
64 | 77 | | |
65 | 78 | | |
66 | | - | |
| 79 | + | |
67 | 80 | | |
68 | 81 | | |
69 | 82 | | |
| |||
81 | 94 | | |
82 | 95 | | |
83 | 96 | | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
84 | 102 | | |
85 | 103 | | |
86 | 104 | | |
87 | 105 | | |
88 | 106 | | |
89 | | - | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
90 | 111 | | |
91 | 112 | | |
92 | 113 | | |
| |||
0 commit comments