Skip to content

fix(plugin-calendar): align the calendar.allDay default to the pack and drop its dead ternary - #7574

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7454-calendar-allday-spelling
Sep 4, 2026
Merged

os-project-manager merged 1 commit into
mainfrom
claude/issue-7454-calendar-allday-spelling

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #7454

Clause-②: no — determined from this diff, not adopted. The diff moves no
declaration and changes no published surface: the only changed line in shipped
code is a value inside the module-private const DEFAULT_TRANSLATIONS, plus one
JSX expression. No export line is added, removed or moved (the sole
export/type token anywhere in the diff is a type import in the new
test file, which files: ["dist", ...] never publishes).

Scope is the triage ruling R+129, not the card title: the title still
describes two halves and only the instance half is here. The class half —
value-comparing factory defaults tables in scripts/check-i18n-call-site-keys.mjs
— is objectui#7567 and is untouched, as are packages/i18n/src/locales/*.

What changed

Two lines in packages/plugin-calendar/src/CalendarView.tsx:

  • the DEFAULT_TRANSLATIONS entry 'calendar.allDay': 'all-day' becomes 'All Day'
  • the lane header's ternary t('calendar.allDay') === 'calendar.allDay' ? 'all-day' : t('calendar.allDay') becomes t('calendar.allDay')

Both sites were re-derived by content on this base rather than taken from
R+129's line numbers: they are still at 84 and 1271, so nothing drifted
from the e304a4e measurement.

R+129's three grounds, re-measured

  1. The packs say All Day. Confirmed, and stronger than stated: all ten
    packs define calendar.allDay (en.ts:671 is allDay: 'All Day'), so no
    language depends on the local table.
  2. Sibling entries match their en value. Confirmed, with one arithmetic
    correction: the table holds seven entries, so there are six siblings,
    not seven — and 6 of 6 match en exactly. The direction of the ground
    holds and is unanimous; only the count in the ruling's phrasing was off by
    one.
  3. The lowercase branch is unreachable. Confirmed by reading
    createSafeTranslation (packages/i18n/src/useSafeTranslation.ts) rather
    than taking triage's word. Its provider-less arm is
    defaults[key] || inlineDefault || key, and defaults carries a non-empty
    calendar.allDay, so the || key arm cannot be reached for this key. The
    provider path is i18next's own t, and createI18n lays the built-in packs
    under any caller resources, so it too carries the key.

Stop-condition: swept, and it does not fire

Nothing marks the lowercase spelling as deliberate.

  • No pin. Repo-wide, the only two occurrences of the literal all-day that
    can reach a user are the two lines this PR changes. Every other hit is prose
    about the concept, a spec describe() string, or a test fixture's id.
  • No width constraint. The gutter is w-14 (56px) with pr-1 and no
    truncate, and the two candidate strings are both seven characters, so
    there is no width argument that distinguishes them.
  • No design note — and evidence the other way. The spelling entered in
    20ae38aff (2026-05-11), whose message says only "Adds calendar.allDay
    translation key with English fallback 'all-day'", with no rationale. en.ts
    had said All Day since bd3deb60f (2026-02-08) — three months earlier,
    and an ancestor of that commit. That same message records the author verifying
    in a Chinese locale ("campaigns show as horizontal bars in 全天 row"), so the
    lowercase fallback was never seen rendering. That is the oversight shape, not
    the objectui#7443 shape.

The ternary landed in that same commit alongside the table entry, and it is the
only bare-key guard among the seven t('calendar. call sites in the
file — one label guarded, six not. That asymmetry is what makes it a leftover
rather than a policy.

Tests

New: packages/plugin-calendar/src/CalendarView.allDaySpelling-7454.test.tsx,
three cases — provider-less, provider-mounted, and the two compared directly.

It discriminates on the fact, never on the assertion: the expected value is
read from the pack itself (en.calendar.allDay), so a literal spelling never
appears in the assertion. The lane header is located by structure (first
child of the event bar's grandparent, with both structural assumptions asserted)
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().
createI18n installs a module-level i18next global via initReactI18next, so
without that guard a reordering that let the provider case run first would leave
the "provider-less" render quietly reading the pack and passing for the wrong
reason.

Discrimination, measured twice. On the pristine base before any edit, the
provider-less case rendered all-day and the provider case rendered All Day
— the card's exact split, with react-i18next's NO_I18NEXT_INSTANCE notice on
stderr confirming the fallback arm really was the one under test. Then, as a
formal ablation on the committed tree:

HEAD_BLOB=389b6e119adb4e0a88d1ccfcd8ec9519049a9730
injected-text count (want 1): 1
removed-text count  (want 0): 0
MUTATED_WORKTREE_BLOB=5d44b909620a43cedd1568e32219899946c97a21
MUTATION CONFIRMED ON DISK
MUTATED_TEST_EXIT=1        Tests  2 failed | 1 passed (3)
   AssertionError: expected 'all-day' to be 'All Day'
RESTORED_WORKTREE_BLOB=389b6e119adb4e0a88d1ccfcd8ec9519049a9730
git diff HEAD byte length (want 0): 0
RESTORE CONFIRMED BY STATE
RESTORED_TEST_EXIT=0       Tests  3 passed (3)

The mutation edited the table entry in CalendarView.tsx; the assertion was not
touched. Both legs ran under trap ... EXIT INT TERM with absolute paths,
restoring via git checkout HEAD -- ABSOLUTE_PATH; the restore is proven by
state (worktree blob equal to the HEAD blob and an empty git diff HEAD),
not by an exit code. Note the mutated run leaves the provider case green —
that one-of-three split is the defect itself, reproduced on demand.

Gate results, all at e9566e8d6

Run from the repo root (the package-scoped vitest form is refused here as a
known false-green), exit codes captured by redirect-then-read, never through a
pipe.

gate result
vitest run packages/plugin-calendar/ Test Files 23 passed (23) · Tests 134 passed (134)
turbo run type-check --filter=@object-ui/plugin-calendar Tasks: 15 successful, 15 total
pnpm check:i18n-keys exit 0
pnpm check:i18n-drift "No en value changed in this range."
pnpm check:control-bytes "OK (scanned 6240 tracked text file(s); skipped 85 binary)"
pnpm check:phantom-deps "Every in-scope import is declared by the package that publishes it."
pnpm check:vi-mock-specifiers / check:vi-mock-inherit OK
pnpm changeset:check "All workspace packages are in the changeset fixed group." · "No changeset declares a major bump."

The package's type-check is tsc --noEmit && tsc -p tsconfig.test.json, and
that second config's include names src/**/*.test.tsx — so the new test file
is genuinely type-checked, not merely adjacent to a green typecheck.

check:i18n-keys is not evidence for this change, and is reported as such.
It is green before the fix, after the fix, and in the ablation's mutated
state
(MUTATED_I18N_GATE_EXIT=0, full verdict line printed). Its own census
puts this entry under "1114 module-local table", which it never value-compares —
that blindness is precisely objectui#7567.

Narrowings, declared

  • Lint. Run over the two changed source files instead of turbo run lint
    across the repo. What the full run could have caught that a subset cannot is a
    verdict moving on a file this diff did not touch — and it cannot here: the
    root eslint.config.js enables no type-aware linting (no projectService,
    no project:, no parserOptions, no recommendedTypeChecked), so each
    file's verdict is a function of its own bytes plus the shared config, and this
    diff changes no config file. File count read from eslint's own --format json
    output: 2 files, 0 errors. The 24 warnings on CalendarView.tsx are
    pre-existing and sit on lines 123-1445, none on 84 or 1271.
  • Tests. packages/plugin-calendar/ rather than the whole suite. CI runs the
    full farm regardless; this narrowing covers the only package whose bytes moved.
  • Gates. The list above rather than every check:* script. Derived from this
    diff's actual paths (a plugin source file, a new test file, a changeset), not
    copied from the dispatch. CI runs the rest.

Filed, not fixed here

#7572 — createI18n's resources merge is one level deep, so a caller
overriding part of an existing group replaces the whole group and drops its
sibling keys. That is the single construction under which a provider-mounted
host could still see a bare calendar.allDay, and it is why the ternary's
deletion is safe rather than merely tidy: in that scenario the file's other six
calendar.* labels already render raw keys, so a guard on one of them was never
a defence. Fixing it belongs in packages/i18n, out of this fence.


Generated by Claude Code

…nd drop its dead ternary

`CalendarView`'s `DEFAULT_TRANSLATIONS` table — the `defaults` map behind its
`createSafeTranslation` factory — spelled `calendar.allDay` as `all-day` while
all ten locale packs carry the key and `en` spells it `All Day`. That table is
served only when no `I18nProvider` is mounted, so the same all-day lane header
rendered `all-day` in a standalone embed and `All Day` inside the console.

Also deletes the lane header's `t('calendar.allDay') === 'calendar.allDay'`
ternary. The factory's provider-less arm resolves `defaults[key]` before it
could ever return the bare key, and with a provider mounted `createI18n` lays
the built-in packs under the caller's resources, so both paths carry the key.
The one construction that can still strip it — a caller overriding part of the
`calendar` group, which replaces the whole group — is filed separately, and the
ternary was no defence against it anyway: it guarded one of the seven
`calendar.*` labels this file renders, so the other six already render raw keys
in exactly that case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3182.7 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-BntokXpf.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) 15.67KB 5.75KB
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) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
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.41KB 1.23KB
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.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
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) 516.21KB 117.82KB
core (index.js) 6.12KB 2.42KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 180.00KB 50.20KB
fields (index.js) 242.40KB 61.26KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
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.55KB 0.62KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useResponsiveConfig.js) 1.37KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
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) 5.12KB 1.74KB
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) 15.75KB 3.80KB
plugin-calendar (index.js) 48.09KB 13.34KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.43KB
plugin-dashboard (index.js) 132.89KB 34.68KB
plugin-designer (index.js) 212.87KB 43.19KB
plugin-detail (index.js) 250.93KB 64.09KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.44KB 41.05KB
plugin-grid (index.js) 209.25KB 56.71KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.55KB 6.80KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 43.57KB 11.96KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.38KB 3.22KB
plugin-view (index.js) 85.24KB 20.94KB
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.66KB 3.50KB
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) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
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 (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.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.58KB 2.23KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
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 (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants