fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one - #7467

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-7459-timeline-absent-axis-refusal
Sep 3, 2026
Merged

fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one#7467
os-project-manager merged 3 commits into
mainfrom
claude/issue-7459-timeline-absent-axis-refusal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7459

Steps ① and ② of the three-step sequence ruled on #7070 (project director seat, 总监批 #28, 2026-09-01, maintainer verbatim 「同意」). Step ③ — the 'created_at' floors on the two plugin faces — stays on #7070 and is not touched here.

The gate: both premises re-measured on the current tree

The card's figures were taken on 2c3cd1b75. Everything was re-derived on 3804c222b before the first edit.

Premise 1 — no absent-date-axis refusal path. REPRODUCES. Zero hits for such a refusal in packages/plugin-timeline. The control fires in the same run and in the same file: the term refus matched 6 lines of ObjectTimeline.tsx (the card said 5 — the file has moved, and the control is live rather than stale). Every one of those 6 is the #6655variant: 'gantt' refusal or its prose; none is about a date axis.

Premise 2 — the internal floor. REPRODUCES, and the line numbers happen to still hold.packages/plugin-timeline/src/ObjectTimeline.tsx:247-249, spelled exactly as reported.

Correction to the card's arithmetic. The card describes "four honest rungs, then a fabricated fifth". The chain reads five declared bindings and then a fabricated sixth: timeline.startDateField, timeline.dateField, mapping.date, flat startDateField, flat dateField. All five were re-checked as asked, and all five are genuine declared bindings — the first two on ListViewTimelineConfig (packages/types/src/objectql.ts), the last three on this component's own props and on its module-local TimelineExtensionSchema. No further fabrication among them.

What changed

① The refusal. An object-bound timeline that declares no date axis renders a diagnostic naming every binding it accepts, instead of a chart. Follows the settled in-repo shape (ObjectGantt's "Gantt configuration required" screen) rather than a second dialect: early return, role="alert", names the fields the author must supply. The list is interpolated from the component's own OBJECT_BOUND_TIMELINE_DATE_BINDINGS — the same drift-proof device OBJECT_BOUND_TIMELINE_VARIANTS already uses one refusal up — so the diagnostic cannot name a vocabulary the resolver no longer has.

② The floor is retired, which is the only thing that makes ① reachable. The tombstone comment deliberately does not quote the removed literal: the card's close condition greps this file for it, and a comment quoting it would answer 1 where the truth is 0. Verified after the final commit: 0 hits for the floor, control term firing at 25 lines in the same file.

Enabling change, declared rather than smuggled: the component's success surface is now named data-testid="timeline-canvas". Every other terminal state of this component already named itself (timeline-unsupported-variant, timeline-missing-date-axis, timeline-error, timeline-loading); the rendered timeline was the one outcome a test could not ask for by name, which is exactly the distinction the ruling turns on.

i18n. One new key, timeline.unconfigured.noDateAxis, in all ten locale packs plus the plugin's own default table (byte-identical to en). The German value carries no typographic quote pair on purpose — de-quote-pairing-3876.test.ts ratchets that count, and it stays at 59.

Blast radius, measured

Nothing in the product starts refusing. Both plugin faces still supply startDateField: … || 'created_at' (plugin-list/src/ListView.tsx:2496, plugin-view/src/ObjectView.tsx:1354 — both re-derived; the card's line numbers were stale). App-shell's timelineViewOptions emits no axis, but routes through ListView, which floors. The refusal is reached only by a view forced directly onto the timeline renderer with nothing declared — which is what the card predicted, and what makes ③ the visible step.

The pin

packages/plugin-timeline/src/ObjectTimeline.absentDateAxisRefusal-7459.test.tsx, 11 cases, the twin of ObjectGantt.unconfiguredRefusal-7070.test.tsx.

  • Refusal is not "renders empty". Two independent canvas markers asserted ABSENTtimeline-canvas and role="list" (the ordered-list rail the real renderer emits, the ol element, which an empty timeline still emits). Neither zero is vacuous: the controls in the same file assert both PRESENT.
  • The pairing. One case hands in records that happen to carry a column named date. Restore the floor and it goes red while rendering a completely convincing timeline off a real column — which "no events" would never have caught. Declared-ness, not existence, is what an axis is.
  • The negative direction. All five declared rungs each render the timeline with its events, in their own spelling.
  • The carve-out. An authored items list is never refused (it carries its own time; no field name is read), which is what keeps the three in-repo catalog fixtures rendering.
  • Ordering. The finding(plugin-timeline): an object-bound timeline with variant: "gantt" throws — calculateDateRange reads a row shape ObjectTimeline never composes #6655 variant refusal still wins for a composed gantt, pinned so it is a decision rather than an accident of placement.
  • The renderer is not stubbed, for the reason timeline-object-bound-gantt-refusal.test.tsx states one file over.

Verification

All figures from runs at the final commit d2dbdfb8c; heavy runs serialized through the shared verify lock, verdict lines read rather than a bare $?.

  • Unionplugin-timeline + i18n + plugin-list + plugin-view: Test Files 175 passed (175) / Tests 2288 passed (2288), VERDICT command-exit 0.
  • Baseline for comparison, taken before any edit: plugin-timeline = 21 files / 276 tests. After: 22 / 287.
  • type-checkplugin-timeline + i18n → exit 0, Scope: 2 of 47 workspace projects, each echoing tsc --noEmit && tsc -p tsconfig.test.json + Done (so the zero-match filter trap did not fire). The new test file is genuinely covered, not excluded: tsc -p tsconfig.test.json --listFiles counts it 1.
  • Gates, each verdict line quoted from the gate's own output, exit code captured before any pipe: check:i18n-keys "every in-scope call-site key resolves against the en pack (2960 keys)"; check:i18n-drift "0 en value(s) changed (1 key(s) added…)"; check:i18n-dead-keys (report) 0; check:control-bytes "OK (scanned 6140 tracked text file(s))"; check:vi-mock-specifiers OK; check:vi-mock-inherit "122 call site(s) on @object-ui/react judged (122 inherit, 0 auto-mocked)"; check:side-effects-array OK; check-changeset-presence "13 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"; check-changeset-no-major "No changeset declares a major bump"; check-changeset-fixed OK; check-changeset-overwrite OK.
  • NOT MEASURED, not red:check:readme-exports and check:eager-closure both stop on a prerequisite — all 314 findings of the former are "type entry ./dist/index.d.ts is not on disk — run pnpm build first", and the latter reports "No eager-closure report … the console was not built". Both need a full-monorepo / console build, which CI does regardless. Recorded as unmeasured rather than as failures.
  • eslint, plain form (never --no-inline-config): exit 0, 0 errors, 29 warnings, all pre-existing in class. Declared narrowing: run on the 14 changed files rather than the repo-wide farm. Evidence it excludes nothing — file count 14 read from --format json; this config enables no type-aware linting (no projectService / parserOptions.project in eslint.config.js), so the diff cannot move an untouched file's verdict; CI runs the full farm regardless. The two react-hooks warnings on ObjectTimeline.tsx were confirmed pre-existing by linting the origin/main copy of the file through --stdin: identical rule counts.

Reverse verification (ablation) — direction predicted, then observed

Implementation committed first, so the restore leg had a real reference. Mutation: put the retired floor back while the refusal stays — the pairing itself.

  • Predicted before the run: 4 RED (the three refusal cases + the message case), 7 GREEN by construction (the five declared-rung controls, the authored-items control, the finding(plugin-timeline): an object-bound timeline with variant: "gantt" throws — calculateDateRange reads a row shape ObjectTimeline never composes #6655 ordering case — all fully declared, so unaffected in both worlds).
  • Observed:Tests 4 failed | 7 passed (11), and the four failures are exactly the four predicted, by name.
  • Mutation proven on disk, not by an editor's exit code: anchor lines 1 → 0, injected lines 0 → 1, the injected hit printed and read; blob 1b4575de…8cea816b…. The anchor was asserted unique first, so a zero-match no-op would have aborted rather than reported a healthy run.
  • Restore proven by state, not by exit code: git hash-object == git rev-parse HEAD:PATH (non-empty and matching), git diff HEAD empty, git status clean. Absolute-path trap … EXIT INT TERM.
  • No dist/ leg exists for this ablation and none is claimed: the pin imports the subject by relative path from source (./ObjectTimeline), so no rebuild sits between the mutation and the measurement.

Clause ②

No, and re-declared against the actual diff rather than inherited from the card. Nothing published moves: no spec key, no zod validator, no exported type. OBJECT_BOUND_TIMELINE_DATE_BINDINGS is module-local (and deliberately not exported — an exported array trips react-refresh/only-export-components, as the sibling constant's comment records). The only new published surface is a data-testid attribute and one i18n key. startDateField's internal type widens to string | undefined inside the component; it is a local, not a prop.

Changeset: .changeset/7459-timeline-absent-date-axis-refusal.md, minor for @object-ui/plugin-timeline and @object-ui/i18n — matching the grade #7070 used for the identical class of change. Graded here and confirmed by the gate's own verdict line: check-changeset-no-major → "✅ No changeset declares a major bump".

Not in this card

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…stead of inventing one
Steps (1) and (2) of the three-step sequence ruled on objectui#7070
(maintainer, 2026-09-01). `ObjectTimeline` resolved its date axis through five
declared bindings and then closed the chain with a bare literal field name
nobody has ever declared, so a name always resolved: every record read a key
its object does not carry and bucketed into "No date".
The floor is retired and an absent-axis refusal added, together — a refusal
added while the floor stood would have been unreachable, and the floor retired
without a refusal produces the silent "No date" outcome the ruling rejects.
The refusal names every binding it accepts, in the shape objectui#7070 settled
one renderer over.
The component's success surface is now named (`timeline-canvas`) so a refusal
can be distinguished from a rendered-but-empty timeline, which is the
distinction the ruling turns on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3180.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-D2L2XDb9.js
StatusFAIL

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 halfVerdict
Aggregate closure ceiling✅ pass
Per-chunk ceilings❌ over its ceiling
Ceiling sensitivity (headroom)✅ pass
Ceiling freshness (checkout vs. base branch)✅ pass

📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT — verified on the branch by content, ⛔ not against the report

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Branch head d2dbdfb8c, base c1fe272ad.

⭐ My brief was wrong, and the dev measured it rather than following it

My dispatch — and the card — both say "four honest rungs, then a fabricated fifth." The chain actually reads five declared bindings and a fabricated sixth: timeline.startDateField, timeline.dateField, mapping.date, flat startDateField, flat dateField, then 'date'.

Off by one, and it was mine. The dev re-derived the chain instead of trusting the count, then checked the other five and reported that all are genuine declared bindings — no further fabrication — without extending scope to "fix" anything. That is exactly the shape the brief asked for and the opposite of what an off-by-one usually produces.

The readings

probeorigin/mainbranch
?? 'date' in ObjectTimeline.tsxlive at :2490
⭐ CONTROL startDateField, same file1218
OBJECT_BOUND_TIMELINE_DATE_BINDINGS:68
refusal interpolating that list:574-575
data-testid="timeline-canvas":642
pin ObjectTimeline.absentDateAxisRefusal-7459.test.tsxabsentexists
locale packs carrying noDateAxis010

The tombstone here is deliberately spelled so it does NOT quote the removed literal. The dev says why: a comment quoting it would make the close-condition scan answer 1 where the truth is 0. That is precisely the trap that has fired on me three times today, anticipated and designed out rather than documented after the fact.

⛔ The step-③ fence held, checked rather than assumed

created_at at the two plugin faces is identical on both sidesListView.tsx 4, plugin-view/ObjectView.tsx 1. That was the edit most likely to be made on momentum: the house posture now on record makes those literals look obviously wrong, and they are, but they are step ③ of the #7070 ruling and blocked on this PR landing. Untouched.

Two judgement calls I want on the record

1. The named success surface is an enabling change, declared not smuggled.data-testid="timeline-canvas" is new. Without a name for the rendered timeline, a pin cannot separate "refused" from "rendered an empty timeline" — and that distinction is the entire point of the ruling, since the failure mode it rejects is a silently empty "No date" render. Every other terminal state of this component already named itself; the success state was the gap. Correct to add, and correct to call out.

2. The de value carries no typographic quote pair on purpose.de-quote-pairing-3876.test.ts ratchets paired-span counts; a quoted German string would have pushed it off 59 and invited a ceiling bump. The dev wrote the value to fit the existing ratchet instead. ⛔ Never raise a gate ceiling — met here without being told.

The ablation tests the pairing, which is the right subject

Mutation = put the floor back while the refusal stays. That is not "undo the change" — it is the specific claim this card rests on, that a refusal behind a floor is unreachable. Predicted 4 RED / 7 GREEN by construction, and the seven were named in advance as fully-declared cases unaffected in both worlds. Observed 4 failed / 7 passed, the four failures exactly the four predicted, by name.

That asymmetry is the acceptance criterion for this class — it separates "stopped inventing" from "refuses everything" — and the blast-radius measurement closes it: nothing in the product starts refusing, because both plugin faces still supply created_at and app-shell routes through ListView. The refusal is reached only by a view forced directly onto the renderer with nothing declared.

Landing

Clause ② NO, re-declared against the diff rather than inherited from my brief: no spec key, no zod validator, no exported type. OBJECT_BOUND_TIMELINE_DATE_BINDINGS is module-local and deliberately unexported (an export trips react-refresh/only-export-components). The only new published surface is one data-testid and one i18n key. ⇒ this seat lands it.

Two gates are declared NOT MEASURED rather than green — check:readme-exports and check:eager-closure, both needing a build CI does anyway. Reported as missing prerequisites, not passes. Correct.

CI is still running several jobs. ⛔ Not enqueueing until every check is green — the bar is every check, not the required subset.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

⛔ Standing down on Bundle Analysis — the failure is not this PR's, and main is over. ⚠️ Shared damage.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Not enqueueing while red; not raising any ceiling; no re-run (the build is deterministic, so a second run answers the same).

What failed

From the gate's own output, ⛔ not the summary comment:

✅ Console eager closure is 3180.1 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 11.3 KB).
❌ 1 eager chunk is over its per-chunk budget:
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
❌ framework 512.8 KB / 511.7 KB ceiling (OVER by 1.1 KB)
✅ ui-components 387.8 KB / 389.6 KB ceiling (headroom 1.9 KB)

The aggregate passes with 11.3 KB spare; sensitivity and freshness both pass. Only the per-chunk half objected — which is precisely the case that check was added for (#5490): one chunk growing while others shrink is invisible to a single total.

Why it is not this PR's — structural, not a plea

apps/console/vite.config.ts:742 attributes that chunk by path:

{name: 'framework',test: /[\\/]packages[\\/](core|react|types)[\\/]/,priority: 80}

This PR's complete file list is .changeset/, ten packages/i18n/src/locales/*.ts, and three files under packages/plugin-timeline/src/. Not one path matches core|react|types. So this diff cannot contribute a byte to the framework chunk, and the 512.8 KB it measured is main's own framework chunk.

main is over the per-chunk ceiling. This is not a regression this PR introduced; it is a regression this PR is the first to measure.

⚠️ Which means every PR in this repo is now red on this check

That makes it shared damage rather than one PR's problem, and it is being escalated rather than worked around here.

⭐ The likely cause is a PR I landed, and I am naming it

Diffing the framework-group paths between the last passing measurement (3804c222b, PR #7464 at 07:07) and this PR's base (c1fe272ad), exactly two files entered that group:

packages/react/src/__tests__/SchemaRenderer.hiddenVisibleSynonymy.test.tsx | +84
packages/types/src/base.ts | +33 −3

Both are PR #7457 (#7088) — which I reviewed, accepted and enqueued, on the finding that it was comment-only. That finding was correct about behaviour: the declarations are untouched and no accept set moved. ⛔ I did not weigh it for bytes, and packages/types sits inside a path-attributed chunk that had roughly 177 bytes of headroom — a fact I had measured earlier this same shift while analysing #7442, and did not apply here.

⚠️ Stated as the open question it is, ⛔ not as a conclusion: #7457's ownBundle Analysis passed pre-merge, on a base that predated #7453. A test file under __tests__ should not reach the console graph at all, and JSDoc should not survive minification — so how 33 lines of comment become ~1.1 KB of shipped chunk is not established, and I am not going to guess it. What is established is the path proof above and the fact that nothing else entered that group.

What I am not doing

⛔ Not raising PER_CHUNK_GZIP_CEILINGS['framework']. The gate says it plainly — "do not widen it just to get a green check" — and a ceiling raise here would bury the regression rather than fix it.
⛔ Not re-running. The build is deterministic; a re-run is not a diagnosis.
⛔ Not widening this PR to carry someone else's byte fix.

This PR stays open and watched. It goes green when main does.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Base branch brought in — ⛔ not a re-run, and the result answers #7399

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. I merged main into this branch. Recording why, because "the head moved and CI re-ran" should never be mistaken for kicking a flaky check.

Why this is a content change, not a re-run

This PR's base was c1fe272ad. main has since moved to 0246d116e, and the move materially changed the thing that was failing:

baseframeworkover by
c1fe272ad — this PR's old base512.8 KB1.1 KB
0246d116e — current main (measured through PR #7471)511.9 KB0.2 KB

⇒ roughly 0.9 KB came back out of the chunk between those two commits. Re-running on the old base would have reproduced 512.8 KB exactly — the build is deterministic. Building against the new base is a different measurement, and it is the one that matters now.

That is also what the standing rule prescribes when a base recovers: bring the base branch in and let CI re-run against it, rather than waiting.

⭐ Why this particular PR's verdict is worth more than its own greenness

This diff touches onlypackages/plugin-timeline/src/ and packages/i18n/src/locales/. The framework chunk is attributed by path — /[\\/]packages[\\/](core|react|types)[\\/]/ — so this PR contributes nothing to it.

⇒ On the new base, this PR's framework figure is main-alone's figure, as directly as CI can give it. So:

⚠️ With the caveat I already had to record on #7399: a docs-only commit moved this chunk ~0.9 KB, so the figure is not a pure function of framework-group content. This reading is strong evidence, not proof. It does not replace the isolation measurement still running.

State of this PR otherwise

Every other check was green before this update — 30 of 31, with Bundle Analysis the single red. ACCEPT stands (5522135254); the by-content verification was done on d2dbdfb8c and the merge does not touch the files it verified. Clause ② no, so this seat lands it once every check is green.

⛔ Still no ceiling raise. ⛔ Still no re-run of the old build.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3180.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-D2L2XDb9.js
StatusFAIL

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 halfVerdict
Aggregate closure ceiling✅ pass
Per-chunk ceilings❌ over its ceiling
Ceiling sensitivity (headroom)✅ pass
Ceiling freshness (checkout vs. base branch)✅ pass

📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

⛔ RETRACTION — the Bundle Analysis failure is this PR's. My standing-down comment was wrong.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Withdrawing 5522177212 ("the failure is not this PR's, and main is over") and the follow-up 5522591077 that reasoned from it.

What I got wrong

I argued that because this diff touches no path matching /packages/(core|react|types)/, it could not contribute to the framework chunk. The chunk is not governed by that regex. Measured module membership: framework also contains all ten @object-ui/i18n locale bundles, which are 78.7% of its bytesframework and infrastructure are tied at priority 80 and framework is listed first, so the i18n subgraph reached via @object-ui/react is absorbed into it.

packages/i18n/src/locales/ is inside the framework chunk. This PR adds a translation key to all ten locale files. Those are its own bytes, in that chunk, and they are what put it over.

Full measurement and controls on #7399 (5522762441) and my retraction of the rest (5522794512). The single reading that settles it — a grep of the emitted chunk, controls firing in the same run:

controls: 'SchemaRenderer' 5 · '_hidden' 3 · 'unscheduled' 10

unscheduled is #7453's locale key, appearing ten times inside assets/framework-*.js. Locale strings ship in this chunk. That is exactly the shape of what this PR adds.

Also retracted, since I said them here: main is not over — it passes at every base (30 B headroom at the tip) — and the rebase I did was therefore not the experiment I described. It changed nothing, correctly: the chunk came back byte-identical.

Where this leaves the PR

The refusal message is required by the ruling this card implements (#7070, 总监批 #28) and has to exist in ten locales. With 30 bytes of headroom on main, it does not fit — the same wall that already stopped #7391 (#7210/#7225) and #7194, both of which are also locale-pack copy. This PR is the third instance of one problem, not a new one.

⇒ ⛔ Not landing this by shrinking ruled copy, and ⛔ not by raising a ceiling. It joins the other two behind the decision on #7399, where the option that actually addresses it — fixing the attribution so framework stops policing translations — is now on the table with measurements attached.

Everything else on this PR stands: the by-content verification, the ablation, the fence check, and the ACCEPT (5522135254). Its other 30 checks are green. Clause ② is still no.

⚠️ For whoever picks this up: the rule I acted on — "this PR touches no framework path, so the bytes aren't mine" — is false in this repo, and I published it twice before measuring. If a PR adds locale keys, it adds framework-chunk bytes.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3180.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-C4NaNsQ-.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

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

Labels

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one - #7467

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-7459-timeline-absent-axis-refusal
Sep 3, 2026
Merged

fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one#7467
os-project-manager merged 3 commits into
mainfrom
claude/issue-7459-timeline-absent-axis-refusal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7459

Steps ① and ② of the three-step sequence ruled on #7070 (project director seat, 总监批 #28, 2026-09-01, maintainer verbatim 「同意」). Step ③ — the 'created_at' floors on the two plugin faces — stays on #7070 and is not touched here.

The gate: both premises re-measured on the current tree

The card's figures were taken on 2c3cd1b75. Everything was re-derived on 3804c222b before the first edit.

Premise 1 — no absent-date-axis refusal path. REPRODUCES. Zero hits for such a refusal in packages/plugin-timeline. The control fires in the same run and in the same file: the term refus matched 6 lines of ObjectTimeline.tsx (the card said 5 — the file has moved, and the control is live rather than stale). Every one of those 6 is the #6655variant: 'gantt' refusal or its prose; none is about a date axis.

Premise 2 — the internal floor. REPRODUCES, and the line numbers happen to still hold.packages/plugin-timeline/src/ObjectTimeline.tsx:247-249, spelled exactly as reported.

Correction to the card's arithmetic. The card describes "four honest rungs, then a fabricated fifth". The chain reads five declared bindings and then a fabricated sixth: timeline.startDateField, timeline.dateField, mapping.date, flat startDateField, flat dateField. All five were re-checked as asked, and all five are genuine declared bindings — the first two on ListViewTimelineConfig (packages/types/src/objectql.ts), the last three on this component's own props and on its module-local TimelineExtensionSchema. No further fabrication among them.

What changed

① The refusal. An object-bound timeline that declares no date axis renders a diagnostic naming every binding it accepts, instead of a chart. Follows the settled in-repo shape (ObjectGantt's "Gantt configuration required" screen) rather than a second dialect: early return, role="alert", names the fields the author must supply. The list is interpolated from the component's own OBJECT_BOUND_TIMELINE_DATE_BINDINGS — the same drift-proof device OBJECT_BOUND_TIMELINE_VARIANTS already uses one refusal up — so the diagnostic cannot name a vocabulary the resolver no longer has.

② The floor is retired, which is the only thing that makes ① reachable. The tombstone comment deliberately does not quote the removed literal: the card's close condition greps this file for it, and a comment quoting it would answer 1 where the truth is 0. Verified after the final commit: 0 hits for the floor, control term firing at 25 lines in the same file.

Enabling change, declared rather than smuggled: the component's success surface is now named data-testid="timeline-canvas". Every other terminal state of this component already named itself (timeline-unsupported-variant, timeline-missing-date-axis, timeline-error, timeline-loading); the rendered timeline was the one outcome a test could not ask for by name, which is exactly the distinction the ruling turns on.

i18n. One new key, timeline.unconfigured.noDateAxis, in all ten locale packs plus the plugin's own default table (byte-identical to en). The German value carries no typographic quote pair on purpose — de-quote-pairing-3876.test.ts ratchets that count, and it stays at 59.

Blast radius, measured

Nothing in the product starts refusing. Both plugin faces still supply startDateField: … || 'created_at' (plugin-list/src/ListView.tsx:2496, plugin-view/src/ObjectView.tsx:1354 — both re-derived; the card's line numbers were stale). App-shell's timelineViewOptions emits no axis, but routes through ListView, which floors. The refusal is reached only by a view forced directly onto the timeline renderer with nothing declared — which is what the card predicted, and what makes ③ the visible step.

The pin

packages/plugin-timeline/src/ObjectTimeline.absentDateAxisRefusal-7459.test.tsx, 11 cases, the twin of ObjectGantt.unconfiguredRefusal-7070.test.tsx.

  • Refusal is not "renders empty". Two independent canvas markers asserted ABSENTtimeline-canvas and role="list" (the ordered-list rail the real renderer emits, the ol element, which an empty timeline still emits). Neither zero is vacuous: the controls in the same file assert both PRESENT.
  • The pairing. One case hands in records that happen to carry a column named date. Restore the floor and it goes red while rendering a completely convincing timeline off a real column — which "no events" would never have caught. Declared-ness, not existence, is what an axis is.
  • The negative direction. All five declared rungs each render the timeline with its events, in their own spelling.
  • The carve-out. An authored items list is never refused (it carries its own time; no field name is read), which is what keeps the three in-repo catalog fixtures rendering.
  • Ordering. The finding(plugin-timeline): an object-bound timeline with variant: "gantt" throws — calculateDateRange reads a row shape ObjectTimeline never composes #6655 variant refusal still wins for a composed gantt, pinned so it is a decision rather than an accident of placement.
  • The renderer is not stubbed, for the reason timeline-object-bound-gantt-refusal.test.tsx states one file over.

Verification

All figures from runs at the final commit d2dbdfb8c; heavy runs serialized through the shared verify lock, verdict lines read rather than a bare $?.

  • Unionplugin-timeline + i18n + plugin-list + plugin-view: Test Files 175 passed (175) / Tests 2288 passed (2288), VERDICT command-exit 0.
  • Baseline for comparison, taken before any edit: plugin-timeline = 21 files / 276 tests. After: 22 / 287.
  • type-checkplugin-timeline + i18n → exit 0, Scope: 2 of 47 workspace projects, each echoing tsc --noEmit && tsc -p tsconfig.test.json + Done (so the zero-match filter trap did not fire). The new test file is genuinely covered, not excluded: tsc -p tsconfig.test.json --listFiles counts it 1.
  • Gates, each verdict line quoted from the gate's own output, exit code captured before any pipe: check:i18n-keys "every in-scope call-site key resolves against the en pack (2960 keys)"; check:i18n-drift "0 en value(s) changed (1 key(s) added…)"; check:i18n-dead-keys (report) 0; check:control-bytes "OK (scanned 6140 tracked text file(s))"; check:vi-mock-specifiers OK; check:vi-mock-inherit "122 call site(s) on @object-ui/react judged (122 inherit, 0 auto-mocked)"; check:side-effects-array OK; check-changeset-presence "13 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"; check-changeset-no-major "No changeset declares a major bump"; check-changeset-fixed OK; check-changeset-overwrite OK.
  • NOT MEASURED, not red:check:readme-exports and check:eager-closure both stop on a prerequisite — all 314 findings of the former are "type entry ./dist/index.d.ts is not on disk — run pnpm build first", and the latter reports "No eager-closure report … the console was not built". Both need a full-monorepo / console build, which CI does regardless. Recorded as unmeasured rather than as failures.
  • eslint, plain form (never --no-inline-config): exit 0, 0 errors, 29 warnings, all pre-existing in class. Declared narrowing: run on the 14 changed files rather than the repo-wide farm. Evidence it excludes nothing — file count 14 read from --format json; this config enables no type-aware linting (no projectService / parserOptions.project in eslint.config.js), so the diff cannot move an untouched file's verdict; CI runs the full farm regardless. The two react-hooks warnings on ObjectTimeline.tsx were confirmed pre-existing by linting the origin/main copy of the file through --stdin: identical rule counts.

Reverse verification (ablation) — direction predicted, then observed

Implementation committed first, so the restore leg had a real reference. Mutation: put the retired floor back while the refusal stays — the pairing itself.

  • Predicted before the run: 4 RED (the three refusal cases + the message case), 7 GREEN by construction (the five declared-rung controls, the authored-items control, the finding(plugin-timeline): an object-bound timeline with variant: "gantt" throws — calculateDateRange reads a row shape ObjectTimeline never composes #6655 ordering case — all fully declared, so unaffected in both worlds).
  • Observed:Tests 4 failed | 7 passed (11), and the four failures are exactly the four predicted, by name.
  • Mutation proven on disk, not by an editor's exit code: anchor lines 1 → 0, injected lines 0 → 1, the injected hit printed and read; blob 1b4575de…8cea816b…. The anchor was asserted unique first, so a zero-match no-op would have aborted rather than reported a healthy run.
  • Restore proven by state, not by exit code: git hash-object == git rev-parse HEAD:PATH (non-empty and matching), git diff HEAD empty, git status clean. Absolute-path trap … EXIT INT TERM.
  • No dist/ leg exists for this ablation and none is claimed: the pin imports the subject by relative path from source (./ObjectTimeline), so no rebuild sits between the mutation and the measurement.

Clause ②

No, and re-declared against the actual diff rather than inherited from the card. Nothing published moves: no spec key, no zod validator, no exported type. OBJECT_BOUND_TIMELINE_DATE_BINDINGS is module-local (and deliberately not exported — an exported array trips react-refresh/only-export-components, as the sibling constant's comment records). The only new published surface is a data-testid attribute and one i18n key. startDateField's internal type widens to string | undefined inside the component; it is a local, not a prop.

Changeset: .changeset/7459-timeline-absent-date-axis-refusal.md, minor for @object-ui/plugin-timeline and @object-ui/i18n — matching the grade #7070 used for the identical class of change. Graded here and confirmed by the gate's own verdict line: check-changeset-no-major → "✅ No changeset declares a major bump".

Not in this card

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…stead of inventing one
Steps (1) and (2) of the three-step sequence ruled on objectui#7070
(maintainer, 2026-09-01). `ObjectTimeline` resolved its date axis through five
declared bindings and then closed the chain with a bare literal field name
nobody has ever declared, so a name always resolved: every record read a key
its object does not carry and bucketed into "No date".
The floor is retired and an absent-axis refusal added, together — a refusal
added while the floor stood would have been unreachable, and the floor retired
without a refusal produces the silent "No date" outcome the ruling rejects.
The refusal names every binding it accepts, in the shape objectui#7070 settled
one renderer over.
The component's success surface is now named (`timeline-canvas`) so a refusal
can be distinguished from a rendered-but-empty timeline, which is the
distinction the ruling turns on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3180.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-D2L2XDb9.js
StatusFAIL

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 halfVerdict
Aggregate closure ceiling✅ pass
Per-chunk ceilings❌ over its ceiling
Ceiling sensitivity (headroom)✅ pass
Ceiling freshness (checkout vs. base branch)✅ pass

📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT — verified on the branch by content, ⛔ not against the report

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Branch head d2dbdfb8c, base c1fe272ad.

⭐ My brief was wrong, and the dev measured it rather than following it

My dispatch — and the card — both say "four honest rungs, then a fabricated fifth." The chain actually reads five declared bindings and a fabricated sixth: timeline.startDateField, timeline.dateField, mapping.date, flat startDateField, flat dateField, then 'date'.

Off by one, and it was mine. The dev re-derived the chain instead of trusting the count, then checked the other five and reported that all are genuine declared bindings — no further fabrication — without extending scope to "fix" anything. That is exactly the shape the brief asked for and the opposite of what an off-by-one usually produces.

The readings

probeorigin/mainbranch
?? 'date' in ObjectTimeline.tsxlive at :2490
⭐ CONTROL startDateField, same file1218
OBJECT_BOUND_TIMELINE_DATE_BINDINGS:68
refusal interpolating that list:574-575
data-testid="timeline-canvas":642
pin ObjectTimeline.absentDateAxisRefusal-7459.test.tsxabsentexists
locale packs carrying noDateAxis010

The tombstone here is deliberately spelled so it does NOT quote the removed literal. The dev says why: a comment quoting it would make the close-condition scan answer 1 where the truth is 0. That is precisely the trap that has fired on me three times today, anticipated and designed out rather than documented after the fact.

⛔ The step-③ fence held, checked rather than assumed

created_at at the two plugin faces is identical on both sidesListView.tsx 4, plugin-view/ObjectView.tsx 1. That was the edit most likely to be made on momentum: the house posture now on record makes those literals look obviously wrong, and they are, but they are step ③ of the #7070 ruling and blocked on this PR landing. Untouched.

Two judgement calls I want on the record

1. The named success surface is an enabling change, declared not smuggled.data-testid="timeline-canvas" is new. Without a name for the rendered timeline, a pin cannot separate "refused" from "rendered an empty timeline" — and that distinction is the entire point of the ruling, since the failure mode it rejects is a silently empty "No date" render. Every other terminal state of this component already named itself; the success state was the gap. Correct to add, and correct to call out.

2. The de value carries no typographic quote pair on purpose.de-quote-pairing-3876.test.ts ratchets paired-span counts; a quoted German string would have pushed it off 59 and invited a ceiling bump. The dev wrote the value to fit the existing ratchet instead. ⛔ Never raise a gate ceiling — met here without being told.

The ablation tests the pairing, which is the right subject

Mutation = put the floor back while the refusal stays. That is not "undo the change" — it is the specific claim this card rests on, that a refusal behind a floor is unreachable. Predicted 4 RED / 7 GREEN by construction, and the seven were named in advance as fully-declared cases unaffected in both worlds. Observed 4 failed / 7 passed, the four failures exactly the four predicted, by name.

That asymmetry is the acceptance criterion for this class — it separates "stopped inventing" from "refuses everything" — and the blast-radius measurement closes it: nothing in the product starts refusing, because both plugin faces still supply created_at and app-shell routes through ListView. The refusal is reached only by a view forced directly onto the renderer with nothing declared.

Landing

Clause ② NO, re-declared against the diff rather than inherited from my brief: no spec key, no zod validator, no exported type. OBJECT_BOUND_TIMELINE_DATE_BINDINGS is module-local and deliberately unexported (an export trips react-refresh/only-export-components). The only new published surface is one data-testid and one i18n key. ⇒ this seat lands it.

Two gates are declared NOT MEASURED rather than green — check:readme-exports and check:eager-closure, both needing a build CI does anyway. Reported as missing prerequisites, not passes. Correct.

CI is still running several jobs. ⛔ Not enqueueing until every check is green — the bar is every check, not the required subset.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

⛔ Standing down on Bundle Analysis — the failure is not this PR's, and main is over. ⚠️ Shared damage.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Not enqueueing while red; not raising any ceiling; no re-run (the build is deterministic, so a second run answers the same).

What failed

From the gate's own output, ⛔ not the summary comment:

✅ Console eager closure is 3180.1 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 11.3 KB).
❌ 1 eager chunk is over its per-chunk budget:
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
❌ framework 512.8 KB / 511.7 KB ceiling (OVER by 1.1 KB)
✅ ui-components 387.8 KB / 389.6 KB ceiling (headroom 1.9 KB)

The aggregate passes with 11.3 KB spare; sensitivity and freshness both pass. Only the per-chunk half objected — which is precisely the case that check was added for (#5490): one chunk growing while others shrink is invisible to a single total.

Why it is not this PR's — structural, not a plea

apps/console/vite.config.ts:742 attributes that chunk by path:

{name: 'framework',test: /[\\/]packages[\\/](core|react|types)[\\/]/,priority: 80}

This PR's complete file list is .changeset/, ten packages/i18n/src/locales/*.ts, and three files under packages/plugin-timeline/src/. Not one path matches core|react|types. So this diff cannot contribute a byte to the framework chunk, and the 512.8 KB it measured is main's own framework chunk.

main is over the per-chunk ceiling. This is not a regression this PR introduced; it is a regression this PR is the first to measure.

⚠️ Which means every PR in this repo is now red on this check

That makes it shared damage rather than one PR's problem, and it is being escalated rather than worked around here.

⭐ The likely cause is a PR I landed, and I am naming it

Diffing the framework-group paths between the last passing measurement (3804c222b, PR #7464 at 07:07) and this PR's base (c1fe272ad), exactly two files entered that group:

packages/react/src/__tests__/SchemaRenderer.hiddenVisibleSynonymy.test.tsx | +84
packages/types/src/base.ts | +33 −3

Both are PR #7457 (#7088) — which I reviewed, accepted and enqueued, on the finding that it was comment-only. That finding was correct about behaviour: the declarations are untouched and no accept set moved. ⛔ I did not weigh it for bytes, and packages/types sits inside a path-attributed chunk that had roughly 177 bytes of headroom — a fact I had measured earlier this same shift while analysing #7442, and did not apply here.

⚠️ Stated as the open question it is, ⛔ not as a conclusion: #7457's ownBundle Analysis passed pre-merge, on a base that predated #7453. A test file under __tests__ should not reach the console graph at all, and JSDoc should not survive minification — so how 33 lines of comment become ~1.1 KB of shipped chunk is not established, and I am not going to guess it. What is established is the path proof above and the fact that nothing else entered that group.

What I am not doing

⛔ Not raising PER_CHUNK_GZIP_CEILINGS['framework']. The gate says it plainly — "do not widen it just to get a green check" — and a ceiling raise here would bury the regression rather than fix it.
⛔ Not re-running. The build is deterministic; a re-run is not a diagnosis.
⛔ Not widening this PR to carry someone else's byte fix.

This PR stays open and watched. It goes green when main does.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Base branch brought in — ⛔ not a re-run, and the result answers #7399

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. I merged main into this branch. Recording why, because "the head moved and CI re-ran" should never be mistaken for kicking a flaky check.

Why this is a content change, not a re-run

This PR's base was c1fe272ad. main has since moved to 0246d116e, and the move materially changed the thing that was failing:

baseframeworkover by
c1fe272ad — this PR's old base512.8 KB1.1 KB
0246d116e — current main (measured through PR #7471)511.9 KB0.2 KB

⇒ roughly 0.9 KB came back out of the chunk between those two commits. Re-running on the old base would have reproduced 512.8 KB exactly — the build is deterministic. Building against the new base is a different measurement, and it is the one that matters now.

That is also what the standing rule prescribes when a base recovers: bring the base branch in and let CI re-run against it, rather than waiting.

⭐ Why this particular PR's verdict is worth more than its own greenness

This diff touches onlypackages/plugin-timeline/src/ and packages/i18n/src/locales/. The framework chunk is attributed by path — /[\\/]packages[\\/](core|react|types)[\\/]/ — so this PR contributes nothing to it.

⇒ On the new base, this PR's framework figure is main-alone's figure, as directly as CI can give it. So:

⚠️ With the caveat I already had to record on #7399: a docs-only commit moved this chunk ~0.9 KB, so the figure is not a pure function of framework-group content. This reading is strong evidence, not proof. It does not replace the isolation measurement still running.

State of this PR otherwise

Every other check was green before this update — 30 of 31, with Bundle Analysis the single red. ACCEPT stands (5522135254); the by-content verification was done on d2dbdfb8c and the merge does not touch the files it verified. Clause ② no, so this seat lands it once every check is green.

⛔ Still no ceiling raise. ⛔ Still no re-run of the old build.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3180.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-D2L2XDb9.js
StatusFAIL

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 halfVerdict
Aggregate closure ceiling✅ pass
Per-chunk ceilings❌ over its ceiling
Ceiling sensitivity (headroom)✅ pass
Ceiling freshness (checkout vs. base branch)✅ pass

📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

⛔ RETRACTION — the Bundle Analysis failure is this PR's. My standing-down comment was wrong.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Withdrawing 5522177212 ("the failure is not this PR's, and main is over") and the follow-up 5522591077 that reasoned from it.

What I got wrong

I argued that because this diff touches no path matching /packages/(core|react|types)/, it could not contribute to the framework chunk. The chunk is not governed by that regex. Measured module membership: framework also contains all ten @object-ui/i18n locale bundles, which are 78.7% of its bytesframework and infrastructure are tied at priority 80 and framework is listed first, so the i18n subgraph reached via @object-ui/react is absorbed into it.

packages/i18n/src/locales/ is inside the framework chunk. This PR adds a translation key to all ten locale files. Those are its own bytes, in that chunk, and they are what put it over.

Full measurement and controls on #7399 (5522762441) and my retraction of the rest (5522794512). The single reading that settles it — a grep of the emitted chunk, controls firing in the same run:

controls: 'SchemaRenderer' 5 · '_hidden' 3 · 'unscheduled' 10

unscheduled is #7453's locale key, appearing ten times inside assets/framework-*.js. Locale strings ship in this chunk. That is exactly the shape of what this PR adds.

Also retracted, since I said them here: main is not over — it passes at every base (30 B headroom at the tip) — and the rebase I did was therefore not the experiment I described. It changed nothing, correctly: the chunk came back byte-identical.

Where this leaves the PR

The refusal message is required by the ruling this card implements (#7070, 总监批 #28) and has to exist in ten locales. With 30 bytes of headroom on main, it does not fit — the same wall that already stopped #7391 (#7210/#7225) and #7194, both of which are also locale-pack copy. This PR is the third instance of one problem, not a new one.

⇒ ⛔ Not landing this by shrinking ruled copy, and ⛔ not by raising a ceiling. It joins the other two behind the decision on #7399, where the option that actually addresses it — fixing the attribution so framework stops policing translations — is now on the table with measurements attached.

Everything else on this PR stands: the by-content verification, the ablation, the fence check, and the ACCEPT (5522135254). Its other 30 checks are green. Clause ② is still no.

⚠️ For whoever picks this up: the rule I acted on — "this PR touches no framework path, so the bytes aren't mine" — is false in this repo, and I published it twice before measuring. If a PR adds locale keys, it adds framework-chunk bytes.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3180.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-C4NaNsQ-.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

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

Labels

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one - #7467

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-7459-timeline-absent-axis-refusal
Sep 3, 2026
Merged

fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one#7467
os-project-manager merged 3 commits into
mainfrom
claude/issue-7459-timeline-absent-axis-refusal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7459

Steps ① and ② of the three-step sequence ruled on #7070 (project director seat, 总监批 #28, 2026-09-01, maintainer verbatim 「同意」). Step ③ — the 'created_at' floors on the two plugin faces — stays on #7070 and is not touched here.

The gate: both premises re-measured on the current tree

The card's figures were taken on 2c3cd1b75. Everything was re-derived on 3804c222b before the first edit.

Premise 1 — no absent-date-axis refusal path. REPRODUCES. Zero hits for such a refusal in packages/plugin-timeline. The control fires in the same run and in the same file: the term refus matched 6 lines of ObjectTimeline.tsx (the card said 5 — the file has moved, and the control is live rather than stale). Every one of those 6 is the #6655variant: 'gantt' refusal or its prose; none is about a date axis.

Premise 2 — the internal floor. REPRODUCES, and the line numbers happen to still hold.packages/plugin-timeline/src/ObjectTimeline.tsx:247-249, spelled exactly as reported.

Correction to the card's arithmetic. The card describes "four honest rungs, then a fabricated fifth". The chain reads five declared bindings and then a fabricated sixth: timeline.startDateField, timeline.dateField, mapping.date, flat startDateField, flat dateField. All five were re-checked as asked, and all five are genuine declared bindings — the first two on ListViewTimelineConfig (packages/types/src/objectql.ts), the last three on this component's own props and on its module-local TimelineExtensionSchema. No further fabrication among them.

What changed

① The refusal. An object-bound timeline that declares no date axis renders a diagnostic naming every binding it accepts, instead of a chart. Follows the settled in-repo shape (ObjectGantt's "Gantt configuration required" screen) rather than a second dialect: early return, role="alert", names the fields the author must supply. The list is interpolated from the component's own OBJECT_BOUND_TIMELINE_DATE_BINDINGS — the same drift-proof device OBJECT_BOUND_TIMELINE_VARIANTS already uses one refusal up — so the diagnostic cannot name a vocabulary the resolver no longer has.

② The floor is retired, which is the only thing that makes ① reachable. The tombstone comment deliberately does not quote the removed literal: the card's close condition greps this file for it, and a comment quoting it would answer 1 where the truth is 0. Verified after the final commit: 0 hits for the floor, control term firing at 25 lines in the same file.

Enabling change, declared rather than smuggled: the component's success surface is now named data-testid="timeline-canvas". Every other terminal state of this component already named itself (timeline-unsupported-variant, timeline-missing-date-axis, timeline-error, timeline-loading); the rendered timeline was the one outcome a test could not ask for by name, which is exactly the distinction the ruling turns on.

i18n. One new key, timeline.unconfigured.noDateAxis, in all ten locale packs plus the plugin's own default table (byte-identical to en). The German value carries no typographic quote pair on purpose — de-quote-pairing-3876.test.ts ratchets that count, and it stays at 59.

Blast radius, measured

Nothing in the product starts refusing. Both plugin faces still supply startDateField: … || 'created_at' (plugin-list/src/ListView.tsx:2496, plugin-view/src/ObjectView.tsx:1354 — both re-derived; the card's line numbers were stale). App-shell's timelineViewOptions emits no axis, but routes through ListView, which floors. The refusal is reached only by a view forced directly onto the timeline renderer with nothing declared — which is what the card predicted, and what makes ③ the visible step.

The pin

packages/plugin-timeline/src/ObjectTimeline.absentDateAxisRefusal-7459.test.tsx, 11 cases, the twin of ObjectGantt.unconfiguredRefusal-7070.test.tsx.

  • Refusal is not "renders empty". Two independent canvas markers asserted ABSENTtimeline-canvas and role="list" (the ordered-list rail the real renderer emits, the ol element, which an empty timeline still emits). Neither zero is vacuous: the controls in the same file assert both PRESENT.
  • The pairing. One case hands in records that happen to carry a column named date. Restore the floor and it goes red while rendering a completely convincing timeline off a real column — which "no events" would never have caught. Declared-ness, not existence, is what an axis is.
  • The negative direction. All five declared rungs each render the timeline with its events, in their own spelling.
  • The carve-out. An authored items list is never refused (it carries its own time; no field name is read), which is what keeps the three in-repo catalog fixtures rendering.
  • Ordering. The finding(plugin-timeline): an object-bound timeline with variant: "gantt" throws — calculateDateRange reads a row shape ObjectTimeline never composes #6655 variant refusal still wins for a composed gantt, pinned so it is a decision rather than an accident of placement.
  • The renderer is not stubbed, for the reason timeline-object-bound-gantt-refusal.test.tsx states one file over.

Verification

All figures from runs at the final commit d2dbdfb8c; heavy runs serialized through the shared verify lock, verdict lines read rather than a bare $?.

  • Unionplugin-timeline + i18n + plugin-list + plugin-view: Test Files 175 passed (175) / Tests 2288 passed (2288), VERDICT command-exit 0.
  • Baseline for comparison, taken before any edit: plugin-timeline = 21 files / 276 tests. After: 22 / 287.
  • type-checkplugin-timeline + i18n → exit 0, Scope: 2 of 47 workspace projects, each echoing tsc --noEmit && tsc -p tsconfig.test.json + Done (so the zero-match filter trap did not fire). The new test file is genuinely covered, not excluded: tsc -p tsconfig.test.json --listFiles counts it 1.
  • Gates, each verdict line quoted from the gate's own output, exit code captured before any pipe: check:i18n-keys "every in-scope call-site key resolves against the en pack (2960 keys)"; check:i18n-drift "0 en value(s) changed (1 key(s) added…)"; check:i18n-dead-keys (report) 0; check:control-bytes "OK (scanned 6140 tracked text file(s))"; check:vi-mock-specifiers OK; check:vi-mock-inherit "122 call site(s) on @object-ui/react judged (122 inherit, 0 auto-mocked)"; check:side-effects-array OK; check-changeset-presence "13 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"; check-changeset-no-major "No changeset declares a major bump"; check-changeset-fixed OK; check-changeset-overwrite OK.
  • NOT MEASURED, not red:check:readme-exports and check:eager-closure both stop on a prerequisite — all 314 findings of the former are "type entry ./dist/index.d.ts is not on disk — run pnpm build first", and the latter reports "No eager-closure report … the console was not built". Both need a full-monorepo / console build, which CI does regardless. Recorded as unmeasured rather than as failures.
  • eslint, plain form (never --no-inline-config): exit 0, 0 errors, 29 warnings, all pre-existing in class. Declared narrowing: run on the 14 changed files rather than the repo-wide farm. Evidence it excludes nothing — file count 14 read from --format json; this config enables no type-aware linting (no projectService / parserOptions.project in eslint.config.js), so the diff cannot move an untouched file's verdict; CI runs the full farm regardless. The two react-hooks warnings on ObjectTimeline.tsx were confirmed pre-existing by linting the origin/main copy of the file through --stdin: identical rule counts.

Reverse verification (ablation) — direction predicted, then observed

Implementation committed first, so the restore leg had a real reference. Mutation: put the retired floor back while the refusal stays — the pairing itself.

  • Predicted before the run: 4 RED (the three refusal cases + the message case), 7 GREEN by construction (the five declared-rung controls, the authored-items control, the finding(plugin-timeline): an object-bound timeline with variant: "gantt" throws — calculateDateRange reads a row shape ObjectTimeline never composes #6655 ordering case — all fully declared, so unaffected in both worlds).
  • Observed:Tests 4 failed | 7 passed (11), and the four failures are exactly the four predicted, by name.
  • Mutation proven on disk, not by an editor's exit code: anchor lines 1 → 0, injected lines 0 → 1, the injected hit printed and read; blob 1b4575de…8cea816b…. The anchor was asserted unique first, so a zero-match no-op would have aborted rather than reported a healthy run.
  • Restore proven by state, not by exit code: git hash-object == git rev-parse HEAD:PATH (non-empty and matching), git diff HEAD empty, git status clean. Absolute-path trap … EXIT INT TERM.
  • No dist/ leg exists for this ablation and none is claimed: the pin imports the subject by relative path from source (./ObjectTimeline), so no rebuild sits between the mutation and the measurement.

Clause ②

No, and re-declared against the actual diff rather than inherited from the card. Nothing published moves: no spec key, no zod validator, no exported type. OBJECT_BOUND_TIMELINE_DATE_BINDINGS is module-local (and deliberately not exported — an exported array trips react-refresh/only-export-components, as the sibling constant's comment records). The only new published surface is a data-testid attribute and one i18n key. startDateField's internal type widens to string | undefined inside the component; it is a local, not a prop.

Changeset: .changeset/7459-timeline-absent-date-axis-refusal.md, minor for @object-ui/plugin-timeline and @object-ui/i18n — matching the grade #7070 used for the identical class of change. Graded here and confirmed by the gate's own verdict line: check-changeset-no-major → "✅ No changeset declares a major bump".

Not in this card

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…stead of inventing one
Steps (1) and (2) of the three-step sequence ruled on objectui#7070
(maintainer, 2026-09-01). `ObjectTimeline` resolved its date axis through five
declared bindings and then closed the chain with a bare literal field name
nobody has ever declared, so a name always resolved: every record read a key
its object does not carry and bucketed into "No date".
The floor is retired and an absent-axis refusal added, together — a refusal
added while the floor stood would have been unreachable, and the floor retired
without a refusal produces the silent "No date" outcome the ruling rejects.
The refusal names every binding it accepts, in the shape objectui#7070 settled
one renderer over.
The component's success surface is now named (`timeline-canvas`) so a refusal
can be distinguished from a rendered-but-empty timeline, which is the
distinction the ruling turns on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3180.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-D2L2XDb9.js
StatusFAIL

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 halfVerdict
Aggregate closure ceiling✅ pass
Per-chunk ceilings❌ over its ceiling
Ceiling sensitivity (headroom)✅ pass
Ceiling freshness (checkout vs. base branch)✅ pass

📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT — verified on the branch by content, ⛔ not against the report

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Branch head d2dbdfb8c, base c1fe272ad.

⭐ My brief was wrong, and the dev measured it rather than following it

My dispatch — and the card — both say "four honest rungs, then a fabricated fifth." The chain actually reads five declared bindings and a fabricated sixth: timeline.startDateField, timeline.dateField, mapping.date, flat startDateField, flat dateField, then 'date'.

Off by one, and it was mine. The dev re-derived the chain instead of trusting the count, then checked the other five and reported that all are genuine declared bindings — no further fabrication — without extending scope to "fix" anything. That is exactly the shape the brief asked for and the opposite of what an off-by-one usually produces.

The readings

probeorigin/mainbranch
?? 'date' in ObjectTimeline.tsxlive at :2490
⭐ CONTROL startDateField, same file1218
OBJECT_BOUND_TIMELINE_DATE_BINDINGS:68
refusal interpolating that list:574-575
data-testid="timeline-canvas":642
pin ObjectTimeline.absentDateAxisRefusal-7459.test.tsxabsentexists
locale packs carrying noDateAxis010

The tombstone here is deliberately spelled so it does NOT quote the removed literal. The dev says why: a comment quoting it would make the close-condition scan answer 1 where the truth is 0. That is precisely the trap that has fired on me three times today, anticipated and designed out rather than documented after the fact.

⛔ The step-③ fence held, checked rather than assumed

created_at at the two plugin faces is identical on both sidesListView.tsx 4, plugin-view/ObjectView.tsx 1. That was the edit most likely to be made on momentum: the house posture now on record makes those literals look obviously wrong, and they are, but they are step ③ of the #7070 ruling and blocked on this PR landing. Untouched.

Two judgement calls I want on the record

1. The named success surface is an enabling change, declared not smuggled.data-testid="timeline-canvas" is new. Without a name for the rendered timeline, a pin cannot separate "refused" from "rendered an empty timeline" — and that distinction is the entire point of the ruling, since the failure mode it rejects is a silently empty "No date" render. Every other terminal state of this component already named itself; the success state was the gap. Correct to add, and correct to call out.

2. The de value carries no typographic quote pair on purpose.de-quote-pairing-3876.test.ts ratchets paired-span counts; a quoted German string would have pushed it off 59 and invited a ceiling bump. The dev wrote the value to fit the existing ratchet instead. ⛔ Never raise a gate ceiling — met here without being told.

The ablation tests the pairing, which is the right subject

Mutation = put the floor back while the refusal stays. That is not "undo the change" — it is the specific claim this card rests on, that a refusal behind a floor is unreachable. Predicted 4 RED / 7 GREEN by construction, and the seven were named in advance as fully-declared cases unaffected in both worlds. Observed 4 failed / 7 passed, the four failures exactly the four predicted, by name.

That asymmetry is the acceptance criterion for this class — it separates "stopped inventing" from "refuses everything" — and the blast-radius measurement closes it: nothing in the product starts refusing, because both plugin faces still supply created_at and app-shell routes through ListView. The refusal is reached only by a view forced directly onto the renderer with nothing declared.

Landing

Clause ② NO, re-declared against the diff rather than inherited from my brief: no spec key, no zod validator, no exported type. OBJECT_BOUND_TIMELINE_DATE_BINDINGS is module-local and deliberately unexported (an export trips react-refresh/only-export-components). The only new published surface is one data-testid and one i18n key. ⇒ this seat lands it.

Two gates are declared NOT MEASURED rather than green — check:readme-exports and check:eager-closure, both needing a build CI does anyway. Reported as missing prerequisites, not passes. Correct.

CI is still running several jobs. ⛔ Not enqueueing until every check is green — the bar is every check, not the required subset.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

⛔ Standing down on Bundle Analysis — the failure is not this PR's, and main is over. ⚠️ Shared damage.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Not enqueueing while red; not raising any ceiling; no re-run (the build is deterministic, so a second run answers the same).

What failed

From the gate's own output, ⛔ not the summary comment:

✅ Console eager closure is 3180.1 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 11.3 KB).
❌ 1 eager chunk is over its per-chunk budget:
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
❌ framework 512.8 KB / 511.7 KB ceiling (OVER by 1.1 KB)
✅ ui-components 387.8 KB / 389.6 KB ceiling (headroom 1.9 KB)

The aggregate passes with 11.3 KB spare; sensitivity and freshness both pass. Only the per-chunk half objected — which is precisely the case that check was added for (#5490): one chunk growing while others shrink is invisible to a single total.

Why it is not this PR's — structural, not a plea

apps/console/vite.config.ts:742 attributes that chunk by path:

{name: 'framework',test: /[\\/]packages[\\/](core|react|types)[\\/]/,priority: 80}

This PR's complete file list is .changeset/, ten packages/i18n/src/locales/*.ts, and three files under packages/plugin-timeline/src/. Not one path matches core|react|types. So this diff cannot contribute a byte to the framework chunk, and the 512.8 KB it measured is main's own framework chunk.

main is over the per-chunk ceiling. This is not a regression this PR introduced; it is a regression this PR is the first to measure.

⚠️ Which means every PR in this repo is now red on this check

That makes it shared damage rather than one PR's problem, and it is being escalated rather than worked around here.

⭐ The likely cause is a PR I landed, and I am naming it

Diffing the framework-group paths between the last passing measurement (3804c222b, PR #7464 at 07:07) and this PR's base (c1fe272ad), exactly two files entered that group:

packages/react/src/__tests__/SchemaRenderer.hiddenVisibleSynonymy.test.tsx | +84
packages/types/src/base.ts | +33 −3

Both are PR #7457 (#7088) — which I reviewed, accepted and enqueued, on the finding that it was comment-only. That finding was correct about behaviour: the declarations are untouched and no accept set moved. ⛔ I did not weigh it for bytes, and packages/types sits inside a path-attributed chunk that had roughly 177 bytes of headroom — a fact I had measured earlier this same shift while analysing #7442, and did not apply here.

⚠️ Stated as the open question it is, ⛔ not as a conclusion: #7457's ownBundle Analysis passed pre-merge, on a base that predated #7453. A test file under __tests__ should not reach the console graph at all, and JSDoc should not survive minification — so how 33 lines of comment become ~1.1 KB of shipped chunk is not established, and I am not going to guess it. What is established is the path proof above and the fact that nothing else entered that group.

What I am not doing

⛔ Not raising PER_CHUNK_GZIP_CEILINGS['framework']. The gate says it plainly — "do not widen it just to get a green check" — and a ceiling raise here would bury the regression rather than fix it.
⛔ Not re-running. The build is deterministic; a re-run is not a diagnosis.
⛔ Not widening this PR to carry someone else's byte fix.

This PR stays open and watched. It goes green when main does.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Base branch brought in — ⛔ not a re-run, and the result answers #7399

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. I merged main into this branch. Recording why, because "the head moved and CI re-ran" should never be mistaken for kicking a flaky check.

Why this is a content change, not a re-run

This PR's base was c1fe272ad. main has since moved to 0246d116e, and the move materially changed the thing that was failing:

baseframeworkover by
c1fe272ad — this PR's old base512.8 KB1.1 KB
0246d116e — current main (measured through PR #7471)511.9 KB0.2 KB

⇒ roughly 0.9 KB came back out of the chunk between those two commits. Re-running on the old base would have reproduced 512.8 KB exactly — the build is deterministic. Building against the new base is a different measurement, and it is the one that matters now.

That is also what the standing rule prescribes when a base recovers: bring the base branch in and let CI re-run against it, rather than waiting.

⭐ Why this particular PR's verdict is worth more than its own greenness

This diff touches onlypackages/plugin-timeline/src/ and packages/i18n/src/locales/. The framework chunk is attributed by path — /[\\/]packages[\\/](core|react|types)[\\/]/ — so this PR contributes nothing to it.

⇒ On the new base, this PR's framework figure is main-alone's figure, as directly as CI can give it. So:

⚠️ With the caveat I already had to record on #7399: a docs-only commit moved this chunk ~0.9 KB, so the figure is not a pure function of framework-group content. This reading is strong evidence, not proof. It does not replace the isolation measurement still running.

State of this PR otherwise

Every other check was green before this update — 30 of 31, with Bundle Analysis the single red. ACCEPT stands (5522135254); the by-content verification was done on d2dbdfb8c and the merge does not touch the files it verified. Clause ② no, so this seat lands it once every check is green.

⛔ Still no ceiling raise. ⛔ Still no re-run of the old build.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3180.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-D2L2XDb9.js
StatusFAIL

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 halfVerdict
Aggregate closure ceiling✅ pass
Per-chunk ceilings❌ over its ceiling
Ceiling sensitivity (headroom)✅ pass
Ceiling freshness (checkout vs. base branch)✅ pass

📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

⛔ RETRACTION — the Bundle Analysis failure is this PR's. My standing-down comment was wrong.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Withdrawing 5522177212 ("the failure is not this PR's, and main is over") and the follow-up 5522591077 that reasoned from it.

What I got wrong

I argued that because this diff touches no path matching /packages/(core|react|types)/, it could not contribute to the framework chunk. The chunk is not governed by that regex. Measured module membership: framework also contains all ten @object-ui/i18n locale bundles, which are 78.7% of its bytesframework and infrastructure are tied at priority 80 and framework is listed first, so the i18n subgraph reached via @object-ui/react is absorbed into it.

packages/i18n/src/locales/ is inside the framework chunk. This PR adds a translation key to all ten locale files. Those are its own bytes, in that chunk, and they are what put it over.

Full measurement and controls on #7399 (5522762441) and my retraction of the rest (5522794512). The single reading that settles it — a grep of the emitted chunk, controls firing in the same run:

controls: 'SchemaRenderer' 5 · '_hidden' 3 · 'unscheduled' 10

unscheduled is #7453's locale key, appearing ten times inside assets/framework-*.js. Locale strings ship in this chunk. That is exactly the shape of what this PR adds.

Also retracted, since I said them here: main is not over — it passes at every base (30 B headroom at the tip) — and the rebase I did was therefore not the experiment I described. It changed nothing, correctly: the chunk came back byte-identical.

Where this leaves the PR

The refusal message is required by the ruling this card implements (#7070, 总监批 #28) and has to exist in ten locales. With 30 bytes of headroom on main, it does not fit — the same wall that already stopped #7391 (#7210/#7225) and #7194, both of which are also locale-pack copy. This PR is the third instance of one problem, not a new one.

⇒ ⛔ Not landing this by shrinking ruled copy, and ⛔ not by raising a ceiling. It joins the other two behind the decision on #7399, where the option that actually addresses it — fixing the attribution so framework stops policing translations — is now on the table with measurements attached.

Everything else on this PR stands: the by-content verification, the ablation, the fence check, and the ACCEPT (5522135254). Its other 30 checks are green. Clause ② is still no.

⚠️ For whoever picks this up: the rule I acted on — "this PR touches no framework path, so the bytes aren't mine" — is false in this repo, and I published it twice before measuring. If a PR adds locale keys, it adds framework-chunk bytes.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3180.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-C4NaNsQ-.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

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

Labels

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one - #7467

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-7459-timeline-absent-axis-refusal
Sep 3, 2026
Merged

fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one#7467
os-project-manager merged 3 commits into
mainfrom
claude/issue-7459-timeline-absent-axis-refusal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7459

Steps ① and ② of the three-step sequence ruled on #7070 (project director seat, 总监批 #28, 2026-09-01, maintainer verbatim 「同意」). Step ③ — the 'created_at' floors on the two plugin faces — stays on #7070 and is not touched here.

The gate: both premises re-measured on the current tree

The card's figures were taken on 2c3cd1b75. Everything was re-derived on 3804c222b before the first edit.

Premise 1 — no absent-date-axis refusal path. REPRODUCES. Zero hits for such a refusal in packages/plugin-timeline. The control fires in the same run and in the same file: the term refus matched 6 lines of ObjectTimeline.tsx (the card said 5 — the file has moved, and the control is live rather than stale). Every one of those 6 is the #6655variant: 'gantt' refusal or its prose; none is about a date axis.

Premise 2 — the internal floor. REPRODUCES, and the line numbers happen to still hold.packages/plugin-timeline/src/ObjectTimeline.tsx:247-249, spelled exactly as reported.

Correction to the card's arithmetic. The card describes "four honest rungs, then a fabricated fifth". The chain reads five declared bindings and then a fabricated sixth: timeline.startDateField, timeline.dateField, mapping.date, flat startDateField, flat dateField. All five were re-checked as asked, and all five are genuine declared bindings — the first two on ListViewTimelineConfig (packages/types/src/objectql.ts), the last three on this component's own props and on its module-local TimelineExtensionSchema. No further fabrication among them.

What changed

① The refusal. An object-bound timeline that declares no date axis renders a diagnostic naming every binding it accepts, instead of a chart. Follows the settled in-repo shape (ObjectGantt's "Gantt configuration required" screen) rather than a second dialect: early return, role="alert", names the fields the author must supply. The list is interpolated from the component's own OBJECT_BOUND_TIMELINE_DATE_BINDINGS — the same drift-proof device OBJECT_BOUND_TIMELINE_VARIANTS already uses one refusal up — so the diagnostic cannot name a vocabulary the resolver no longer has.

② The floor is retired, which is the only thing that makes ① reachable. The tombstone comment deliberately does not quote the removed literal: the card's close condition greps this file for it, and a comment quoting it would answer 1 where the truth is 0. Verified after the final commit: 0 hits for the floor, control term firing at 25 lines in the same file.

Enabling change, declared rather than smuggled: the component's success surface is now named data-testid="timeline-canvas". Every other terminal state of this component already named itself (timeline-unsupported-variant, timeline-missing-date-axis, timeline-error, timeline-loading); the rendered timeline was the one outcome a test could not ask for by name, which is exactly the distinction the ruling turns on.

i18n. One new key, timeline.unconfigured.noDateAxis, in all ten locale packs plus the plugin's own default table (byte-identical to en). The German value carries no typographic quote pair on purpose — de-quote-pairing-3876.test.ts ratchets that count, and it stays at 59.

Blast radius, measured

Nothing in the product starts refusing. Both plugin faces still supply startDateField: … || 'created_at' (plugin-list/src/ListView.tsx:2496, plugin-view/src/ObjectView.tsx:1354 — both re-derived; the card's line numbers were stale). App-shell's timelineViewOptions emits no axis, but routes through ListView, which floors. The refusal is reached only by a view forced directly onto the timeline renderer with nothing declared — which is what the card predicted, and what makes ③ the visible step.

The pin

packages/plugin-timeline/src/ObjectTimeline.absentDateAxisRefusal-7459.test.tsx, 11 cases, the twin of ObjectGantt.unconfiguredRefusal-7070.test.tsx.

  • Refusal is not "renders empty". Two independent canvas markers asserted ABSENTtimeline-canvas and role="list" (the ordered-list rail the real renderer emits, the ol element, which an empty timeline still emits). Neither zero is vacuous: the controls in the same file assert both PRESENT.
  • The pairing. One case hands in records that happen to carry a column named date. Restore the floor and it goes red while rendering a completely convincing timeline off a real column — which "no events" would never have caught. Declared-ness, not existence, is what an axis is.
  • The negative direction. All five declared rungs each render the timeline with its events, in their own spelling.
  • The carve-out. An authored items list is never refused (it carries its own time; no field name is read), which is what keeps the three in-repo catalog fixtures rendering.
  • Ordering. The finding(plugin-timeline): an object-bound timeline with variant: "gantt" throws — calculateDateRange reads a row shape ObjectTimeline never composes #6655 variant refusal still wins for a composed gantt, pinned so it is a decision rather than an accident of placement.
  • The renderer is not stubbed, for the reason timeline-object-bound-gantt-refusal.test.tsx states one file over.

Verification

All figures from runs at the final commit d2dbdfb8c; heavy runs serialized through the shared verify lock, verdict lines read rather than a bare $?.

  • Unionplugin-timeline + i18n + plugin-list + plugin-view: Test Files 175 passed (175) / Tests 2288 passed (2288), VERDICT command-exit 0.
  • Baseline for comparison, taken before any edit: plugin-timeline = 21 files / 276 tests. After: 22 / 287.
  • type-checkplugin-timeline + i18n → exit 0, Scope: 2 of 47 workspace projects, each echoing tsc --noEmit && tsc -p tsconfig.test.json + Done (so the zero-match filter trap did not fire). The new test file is genuinely covered, not excluded: tsc -p tsconfig.test.json --listFiles counts it 1.
  • Gates, each verdict line quoted from the gate's own output, exit code captured before any pipe: check:i18n-keys "every in-scope call-site key resolves against the en pack (2960 keys)"; check:i18n-drift "0 en value(s) changed (1 key(s) added…)"; check:i18n-dead-keys (report) 0; check:control-bytes "OK (scanned 6140 tracked text file(s))"; check:vi-mock-specifiers OK; check:vi-mock-inherit "122 call site(s) on @object-ui/react judged (122 inherit, 0 auto-mocked)"; check:side-effects-array OK; check-changeset-presence "13 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"; check-changeset-no-major "No changeset declares a major bump"; check-changeset-fixed OK; check-changeset-overwrite OK.
  • NOT MEASURED, not red:check:readme-exports and check:eager-closure both stop on a prerequisite — all 314 findings of the former are "type entry ./dist/index.d.ts is not on disk — run pnpm build first", and the latter reports "No eager-closure report … the console was not built". Both need a full-monorepo / console build, which CI does regardless. Recorded as unmeasured rather than as failures.
  • eslint, plain form (never --no-inline-config): exit 0, 0 errors, 29 warnings, all pre-existing in class. Declared narrowing: run on the 14 changed files rather than the repo-wide farm. Evidence it excludes nothing — file count 14 read from --format json; this config enables no type-aware linting (no projectService / parserOptions.project in eslint.config.js), so the diff cannot move an untouched file's verdict; CI runs the full farm regardless. The two react-hooks warnings on ObjectTimeline.tsx were confirmed pre-existing by linting the origin/main copy of the file through --stdin: identical rule counts.

Reverse verification (ablation) — direction predicted, then observed

Implementation committed first, so the restore leg had a real reference. Mutation: put the retired floor back while the refusal stays — the pairing itself.

  • Predicted before the run: 4 RED (the three refusal cases + the message case), 7 GREEN by construction (the five declared-rung controls, the authored-items control, the finding(plugin-timeline): an object-bound timeline with variant: "gantt" throws — calculateDateRange reads a row shape ObjectTimeline never composes #6655 ordering case — all fully declared, so unaffected in both worlds).
  • Observed:Tests 4 failed | 7 passed (11), and the four failures are exactly the four predicted, by name.
  • Mutation proven on disk, not by an editor's exit code: anchor lines 1 → 0, injected lines 0 → 1, the injected hit printed and read; blob 1b4575de…8cea816b…. The anchor was asserted unique first, so a zero-match no-op would have aborted rather than reported a healthy run.
  • Restore proven by state, not by exit code: git hash-object == git rev-parse HEAD:PATH (non-empty and matching), git diff HEAD empty, git status clean. Absolute-path trap … EXIT INT TERM.
  • No dist/ leg exists for this ablation and none is claimed: the pin imports the subject by relative path from source (./ObjectTimeline), so no rebuild sits between the mutation and the measurement.

Clause ②

No, and re-declared against the actual diff rather than inherited from the card. Nothing published moves: no spec key, no zod validator, no exported type. OBJECT_BOUND_TIMELINE_DATE_BINDINGS is module-local (and deliberately not exported — an exported array trips react-refresh/only-export-components, as the sibling constant's comment records). The only new published surface is a data-testid attribute and one i18n key. startDateField's internal type widens to string | undefined inside the component; it is a local, not a prop.

Changeset: .changeset/7459-timeline-absent-date-axis-refusal.md, minor for @object-ui/plugin-timeline and @object-ui/i18n — matching the grade #7070 used for the identical class of change. Graded here and confirmed by the gate's own verdict line: check-changeset-no-major → "✅ No changeset declares a major bump".

Not in this card

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…stead of inventing one
Steps (1) and (2) of the three-step sequence ruled on objectui#7070
(maintainer, 2026-09-01). `ObjectTimeline` resolved its date axis through five
declared bindings and then closed the chain with a bare literal field name
nobody has ever declared, so a name always resolved: every record read a key
its object does not carry and bucketed into "No date".
The floor is retired and an absent-axis refusal added, together — a refusal
added while the floor stood would have been unreachable, and the floor retired
without a refusal produces the silent "No date" outcome the ruling rejects.
The refusal names every binding it accepts, in the shape objectui#7070 settled
one renderer over.
The component's success surface is now named (`timeline-canvas`) so a refusal
can be distinguished from a rendered-but-empty timeline, which is the
distinction the ruling turns on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3180.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-D2L2XDb9.js
StatusFAIL

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 halfVerdict
Aggregate closure ceiling✅ pass
Per-chunk ceilings❌ over its ceiling
Ceiling sensitivity (headroom)✅ pass
Ceiling freshness (checkout vs. base branch)✅ pass

📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT — verified on the branch by content, ⛔ not against the report

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Branch head d2dbdfb8c, base c1fe272ad.

⭐ My brief was wrong, and the dev measured it rather than following it

My dispatch — and the card — both say "four honest rungs, then a fabricated fifth." The chain actually reads five declared bindings and a fabricated sixth: timeline.startDateField, timeline.dateField, mapping.date, flat startDateField, flat dateField, then 'date'.

Off by one, and it was mine. The dev re-derived the chain instead of trusting the count, then checked the other five and reported that all are genuine declared bindings — no further fabrication — without extending scope to "fix" anything. That is exactly the shape the brief asked for and the opposite of what an off-by-one usually produces.

The readings

probeorigin/mainbranch
?? 'date' in ObjectTimeline.tsxlive at :2490
⭐ CONTROL startDateField, same file1218
OBJECT_BOUND_TIMELINE_DATE_BINDINGS:68
refusal interpolating that list:574-575
data-testid="timeline-canvas":642
pin ObjectTimeline.absentDateAxisRefusal-7459.test.tsxabsentexists
locale packs carrying noDateAxis010

The tombstone here is deliberately spelled so it does NOT quote the removed literal. The dev says why: a comment quoting it would make the close-condition scan answer 1 where the truth is 0. That is precisely the trap that has fired on me three times today, anticipated and designed out rather than documented after the fact.

⛔ The step-③ fence held, checked rather than assumed

created_at at the two plugin faces is identical on both sidesListView.tsx 4, plugin-view/ObjectView.tsx 1. That was the edit most likely to be made on momentum: the house posture now on record makes those literals look obviously wrong, and they are, but they are step ③ of the #7070 ruling and blocked on this PR landing. Untouched.

Two judgement calls I want on the record

1. The named success surface is an enabling change, declared not smuggled.data-testid="timeline-canvas" is new. Without a name for the rendered timeline, a pin cannot separate "refused" from "rendered an empty timeline" — and that distinction is the entire point of the ruling, since the failure mode it rejects is a silently empty "No date" render. Every other terminal state of this component already named itself; the success state was the gap. Correct to add, and correct to call out.

2. The de value carries no typographic quote pair on purpose.de-quote-pairing-3876.test.ts ratchets paired-span counts; a quoted German string would have pushed it off 59 and invited a ceiling bump. The dev wrote the value to fit the existing ratchet instead. ⛔ Never raise a gate ceiling — met here without being told.

The ablation tests the pairing, which is the right subject

Mutation = put the floor back while the refusal stays. That is not "undo the change" — it is the specific claim this card rests on, that a refusal behind a floor is unreachable. Predicted 4 RED / 7 GREEN by construction, and the seven were named in advance as fully-declared cases unaffected in both worlds. Observed 4 failed / 7 passed, the four failures exactly the four predicted, by name.

That asymmetry is the acceptance criterion for this class — it separates "stopped inventing" from "refuses everything" — and the blast-radius measurement closes it: nothing in the product starts refusing, because both plugin faces still supply created_at and app-shell routes through ListView. The refusal is reached only by a view forced directly onto the renderer with nothing declared.

Landing

Clause ② NO, re-declared against the diff rather than inherited from my brief: no spec key, no zod validator, no exported type. OBJECT_BOUND_TIMELINE_DATE_BINDINGS is module-local and deliberately unexported (an export trips react-refresh/only-export-components). The only new published surface is one data-testid and one i18n key. ⇒ this seat lands it.

Two gates are declared NOT MEASURED rather than green — check:readme-exports and check:eager-closure, both needing a build CI does anyway. Reported as missing prerequisites, not passes. Correct.

CI is still running several jobs. ⛔ Not enqueueing until every check is green — the bar is every check, not the required subset.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

⛔ Standing down on Bundle Analysis — the failure is not this PR's, and main is over. ⚠️ Shared damage.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Not enqueueing while red; not raising any ceiling; no re-run (the build is deterministic, so a second run answers the same).

What failed

From the gate's own output, ⛔ not the summary comment:

✅ Console eager closure is 3180.1 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 11.3 KB).
❌ 1 eager chunk is over its per-chunk budget:
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
❌ framework 512.8 KB / 511.7 KB ceiling (OVER by 1.1 KB)
✅ ui-components 387.8 KB / 389.6 KB ceiling (headroom 1.9 KB)

The aggregate passes with 11.3 KB spare; sensitivity and freshness both pass. Only the per-chunk half objected — which is precisely the case that check was added for (#5490): one chunk growing while others shrink is invisible to a single total.

Why it is not this PR's — structural, not a plea

apps/console/vite.config.ts:742 attributes that chunk by path:

{name: 'framework',test: /[\\/]packages[\\/](core|react|types)[\\/]/,priority: 80}

This PR's complete file list is .changeset/, ten packages/i18n/src/locales/*.ts, and three files under packages/plugin-timeline/src/. Not one path matches core|react|types. So this diff cannot contribute a byte to the framework chunk, and the 512.8 KB it measured is main's own framework chunk.

main is over the per-chunk ceiling. This is not a regression this PR introduced; it is a regression this PR is the first to measure.

⚠️ Which means every PR in this repo is now red on this check

That makes it shared damage rather than one PR's problem, and it is being escalated rather than worked around here.

⭐ The likely cause is a PR I landed, and I am naming it

Diffing the framework-group paths between the last passing measurement (3804c222b, PR #7464 at 07:07) and this PR's base (c1fe272ad), exactly two files entered that group:

packages/react/src/__tests__/SchemaRenderer.hiddenVisibleSynonymy.test.tsx | +84
packages/types/src/base.ts | +33 −3

Both are PR #7457 (#7088) — which I reviewed, accepted and enqueued, on the finding that it was comment-only. That finding was correct about behaviour: the declarations are untouched and no accept set moved. ⛔ I did not weigh it for bytes, and packages/types sits inside a path-attributed chunk that had roughly 177 bytes of headroom — a fact I had measured earlier this same shift while analysing #7442, and did not apply here.

⚠️ Stated as the open question it is, ⛔ not as a conclusion: #7457's ownBundle Analysis passed pre-merge, on a base that predated #7453. A test file under __tests__ should not reach the console graph at all, and JSDoc should not survive minification — so how 33 lines of comment become ~1.1 KB of shipped chunk is not established, and I am not going to guess it. What is established is the path proof above and the fact that nothing else entered that group.

What I am not doing

⛔ Not raising PER_CHUNK_GZIP_CEILINGS['framework']. The gate says it plainly — "do not widen it just to get a green check" — and a ceiling raise here would bury the regression rather than fix it.
⛔ Not re-running. The build is deterministic; a re-run is not a diagnosis.
⛔ Not widening this PR to carry someone else's byte fix.

This PR stays open and watched. It goes green when main does.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Base branch brought in — ⛔ not a re-run, and the result answers #7399

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. I merged main into this branch. Recording why, because "the head moved and CI re-ran" should never be mistaken for kicking a flaky check.

Why this is a content change, not a re-run

This PR's base was c1fe272ad. main has since moved to 0246d116e, and the move materially changed the thing that was failing:

baseframeworkover by
c1fe272ad — this PR's old base512.8 KB1.1 KB
0246d116e — current main (measured through PR #7471)511.9 KB0.2 KB

⇒ roughly 0.9 KB came back out of the chunk between those two commits. Re-running on the old base would have reproduced 512.8 KB exactly — the build is deterministic. Building against the new base is a different measurement, and it is the one that matters now.

That is also what the standing rule prescribes when a base recovers: bring the base branch in and let CI re-run against it, rather than waiting.

⭐ Why this particular PR's verdict is worth more than its own greenness

This diff touches onlypackages/plugin-timeline/src/ and packages/i18n/src/locales/. The framework chunk is attributed by path — /[\\/]packages[\\/](core|react|types)[\\/]/ — so this PR contributes nothing to it.

⇒ On the new base, this PR's framework figure is main-alone's figure, as directly as CI can give it. So:

⚠️ With the caveat I already had to record on #7399: a docs-only commit moved this chunk ~0.9 KB, so the figure is not a pure function of framework-group content. This reading is strong evidence, not proof. It does not replace the isolation measurement still running.

State of this PR otherwise

Every other check was green before this update — 30 of 31, with Bundle Analysis the single red. ACCEPT stands (5522135254); the by-content verification was done on d2dbdfb8c and the merge does not touch the files it verified. Clause ② no, so this seat lands it once every check is green.

⛔ Still no ceiling raise. ⛔ Still no re-run of the old build.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3180.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-D2L2XDb9.js
StatusFAIL

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 halfVerdict
Aggregate closure ceiling✅ pass
Per-chunk ceilings❌ over its ceiling
Ceiling sensitivity (headroom)✅ pass
Ceiling freshness (checkout vs. base branch)✅ pass

📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

⛔ RETRACTION — the Bundle Analysis failure is this PR's. My standing-down comment was wrong.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Withdrawing 5522177212 ("the failure is not this PR's, and main is over") and the follow-up 5522591077 that reasoned from it.

What I got wrong

I argued that because this diff touches no path matching /packages/(core|react|types)/, it could not contribute to the framework chunk. The chunk is not governed by that regex. Measured module membership: framework also contains all ten @object-ui/i18n locale bundles, which are 78.7% of its bytesframework and infrastructure are tied at priority 80 and framework is listed first, so the i18n subgraph reached via @object-ui/react is absorbed into it.

packages/i18n/src/locales/ is inside the framework chunk. This PR adds a translation key to all ten locale files. Those are its own bytes, in that chunk, and they are what put it over.

Full measurement and controls on #7399 (5522762441) and my retraction of the rest (5522794512). The single reading that settles it — a grep of the emitted chunk, controls firing in the same run:

controls: 'SchemaRenderer' 5 · '_hidden' 3 · 'unscheduled' 10

unscheduled is #7453's locale key, appearing ten times inside assets/framework-*.js. Locale strings ship in this chunk. That is exactly the shape of what this PR adds.

Also retracted, since I said them here: main is not over — it passes at every base (30 B headroom at the tip) — and the rebase I did was therefore not the experiment I described. It changed nothing, correctly: the chunk came back byte-identical.

Where this leaves the PR

The refusal message is required by the ruling this card implements (#7070, 总监批 #28) and has to exist in ten locales. With 30 bytes of headroom on main, it does not fit — the same wall that already stopped #7391 (#7210/#7225) and #7194, both of which are also locale-pack copy. This PR is the third instance of one problem, not a new one.

⇒ ⛔ Not landing this by shrinking ruled copy, and ⛔ not by raising a ceiling. It joins the other two behind the decision on #7399, where the option that actually addresses it — fixing the attribution so framework stops policing translations — is now on the table with measurements attached.

Everything else on this PR stands: the by-content verification, the ablation, the fence check, and the ACCEPT (5522135254). Its other 30 checks are green. Clause ② is still no.

⚠️ For whoever picks this up: the rule I acted on — "this PR touches no framework path, so the bytes aren't mine" — is false in this repo, and I published it twice before measuring. If a PR adds locale keys, it adds framework-chunk bytes.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3180.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-C4NaNsQ-.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

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

Labels

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one - #7467

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-7459-timeline-absent-axis-refusal
Sep 3, 2026
Merged

fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one#7467
os-project-manager merged 3 commits into
mainfrom
claude/issue-7459-timeline-absent-axis-refusal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7459

Steps ① and ② of the three-step sequence ruled on #7070 (project director seat, 总监批 #28, 2026-09-01, maintainer verbatim 「同意」). Step ③ — the 'created_at' floors on the two plugin faces — stays on #7070 and is not touched here.

The gate: both premises re-measured on the current tree

The card's figures were taken on 2c3cd1b75. Everything was re-derived on 3804c222b before the first edit.

Premise 1 — no absent-date-axis refusal path. REPRODUCES. Zero hits for such a refusal in packages/plugin-timeline. The control fires in the same run and in the same file: the term refus matched 6 lines of ObjectTimeline.tsx (the card said 5 — the file has moved, and the control is live rather than stale). Every one of those 6 is the #6655variant: 'gantt' refusal or its prose; none is about a date axis.

Premise 2 — the internal floor. REPRODUCES, and the line numbers happen to still hold.packages/plugin-timeline/src/ObjectTimeline.tsx:247-249, spelled exactly as reported.

Correction to the card's arithmetic. The card describes "four honest rungs, then a fabricated fifth". The chain reads five declared bindings and then a fabricated sixth: timeline.startDateField, timeline.dateField, mapping.date, flat startDateField, flat dateField. All five were re-checked as asked, and all five are genuine declared bindings — the first two on ListViewTimelineConfig (packages/types/src/objectql.ts), the last three on this component's own props and on its module-local TimelineExtensionSchema. No further fabrication among them.

What changed

① The refusal. An object-bound timeline that declares no date axis renders a diagnostic naming every binding it accepts, instead of a chart. Follows the settled in-repo shape (ObjectGantt's "Gantt configuration required" screen) rather than a second dialect: early return, role="alert", names the fields the author must supply. The list is interpolated from the component's own OBJECT_BOUND_TIMELINE_DATE_BINDINGS — the same drift-proof device OBJECT_BOUND_TIMELINE_VARIANTS already uses one refusal up — so the diagnostic cannot name a vocabulary the resolver no longer has.

② The floor is retired, which is the only thing that makes ① reachable. The tombstone comment deliberately does not quote the removed literal: the card's close condition greps this file for it, and a comment quoting it would answer 1 where the truth is 0. Verified after the final commit: 0 hits for the floor, control term firing at 25 lines in the same file.

Enabling change, declared rather than smuggled: the component's success surface is now named data-testid="timeline-canvas". Every other terminal state of this component already named itself (timeline-unsupported-variant, timeline-missing-date-axis, timeline-error, timeline-loading); the rendered timeline was the one outcome a test could not ask for by name, which is exactly the distinction the ruling turns on.

i18n. One new key, timeline.unconfigured.noDateAxis, in all ten locale packs plus the plugin's own default table (byte-identical to en). The German value carries no typographic quote pair on purpose — de-quote-pairing-3876.test.ts ratchets that count, and it stays at 59.

Blast radius, measured

Nothing in the product starts refusing. Both plugin faces still supply startDateField: … || 'created_at' (plugin-list/src/ListView.tsx:2496, plugin-view/src/ObjectView.tsx:1354 — both re-derived; the card's line numbers were stale). App-shell's timelineViewOptions emits no axis, but routes through ListView, which floors. The refusal is reached only by a view forced directly onto the timeline renderer with nothing declared — which is what the card predicted, and what makes ③ the visible step.

The pin

packages/plugin-timeline/src/ObjectTimeline.absentDateAxisRefusal-7459.test.tsx, 11 cases, the twin of ObjectGantt.unconfiguredRefusal-7070.test.tsx.

  • Refusal is not "renders empty". Two independent canvas markers asserted ABSENTtimeline-canvas and role="list" (the ordered-list rail the real renderer emits, the ol element, which an empty timeline still emits). Neither zero is vacuous: the controls in the same file assert both PRESENT.
  • The pairing. One case hands in records that happen to carry a column named date. Restore the floor and it goes red while rendering a completely convincing timeline off a real column — which "no events" would never have caught. Declared-ness, not existence, is what an axis is.
  • The negative direction. All five declared rungs each render the timeline with its events, in their own spelling.
  • The carve-out. An authored items list is never refused (it carries its own time; no field name is read), which is what keeps the three in-repo catalog fixtures rendering.
  • Ordering. The finding(plugin-timeline): an object-bound timeline with variant: "gantt" throws — calculateDateRange reads a row shape ObjectTimeline never composes #6655 variant refusal still wins for a composed gantt, pinned so it is a decision rather than an accident of placement.
  • The renderer is not stubbed, for the reason timeline-object-bound-gantt-refusal.test.tsx states one file over.

Verification

All figures from runs at the final commit d2dbdfb8c; heavy runs serialized through the shared verify lock, verdict lines read rather than a bare $?.

  • Unionplugin-timeline + i18n + plugin-list + plugin-view: Test Files 175 passed (175) / Tests 2288 passed (2288), VERDICT command-exit 0.
  • Baseline for comparison, taken before any edit: plugin-timeline = 21 files / 276 tests. After: 22 / 287.
  • type-checkplugin-timeline + i18n → exit 0, Scope: 2 of 47 workspace projects, each echoing tsc --noEmit && tsc -p tsconfig.test.json + Done (so the zero-match filter trap did not fire). The new test file is genuinely covered, not excluded: tsc -p tsconfig.test.json --listFiles counts it 1.
  • Gates, each verdict line quoted from the gate's own output, exit code captured before any pipe: check:i18n-keys "every in-scope call-site key resolves against the en pack (2960 keys)"; check:i18n-drift "0 en value(s) changed (1 key(s) added…)"; check:i18n-dead-keys (report) 0; check:control-bytes "OK (scanned 6140 tracked text file(s))"; check:vi-mock-specifiers OK; check:vi-mock-inherit "122 call site(s) on @object-ui/react judged (122 inherit, 0 auto-mocked)"; check:side-effects-array OK; check-changeset-presence "13 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"; check-changeset-no-major "No changeset declares a major bump"; check-changeset-fixed OK; check-changeset-overwrite OK.
  • NOT MEASURED, not red:check:readme-exports and check:eager-closure both stop on a prerequisite — all 314 findings of the former are "type entry ./dist/index.d.ts is not on disk — run pnpm build first", and the latter reports "No eager-closure report … the console was not built". Both need a full-monorepo / console build, which CI does regardless. Recorded as unmeasured rather than as failures.
  • eslint, plain form (never --no-inline-config): exit 0, 0 errors, 29 warnings, all pre-existing in class. Declared narrowing: run on the 14 changed files rather than the repo-wide farm. Evidence it excludes nothing — file count 14 read from --format json; this config enables no type-aware linting (no projectService / parserOptions.project in eslint.config.js), so the diff cannot move an untouched file's verdict; CI runs the full farm regardless. The two react-hooks warnings on ObjectTimeline.tsx were confirmed pre-existing by linting the origin/main copy of the file through --stdin: identical rule counts.

Reverse verification (ablation) — direction predicted, then observed

Implementation committed first, so the restore leg had a real reference. Mutation: put the retired floor back while the refusal stays — the pairing itself.

  • Predicted before the run: 4 RED (the three refusal cases + the message case), 7 GREEN by construction (the five declared-rung controls, the authored-items control, the finding(plugin-timeline): an object-bound timeline with variant: "gantt" throws — calculateDateRange reads a row shape ObjectTimeline never composes #6655 ordering case — all fully declared, so unaffected in both worlds).
  • Observed:Tests 4 failed | 7 passed (11), and the four failures are exactly the four predicted, by name.
  • Mutation proven on disk, not by an editor's exit code: anchor lines 1 → 0, injected lines 0 → 1, the injected hit printed and read; blob 1b4575de…8cea816b…. The anchor was asserted unique first, so a zero-match no-op would have aborted rather than reported a healthy run.
  • Restore proven by state, not by exit code: git hash-object == git rev-parse HEAD:PATH (non-empty and matching), git diff HEAD empty, git status clean. Absolute-path trap … EXIT INT TERM.
  • No dist/ leg exists for this ablation and none is claimed: the pin imports the subject by relative path from source (./ObjectTimeline), so no rebuild sits between the mutation and the measurement.

Clause ②

No, and re-declared against the actual diff rather than inherited from the card. Nothing published moves: no spec key, no zod validator, no exported type. OBJECT_BOUND_TIMELINE_DATE_BINDINGS is module-local (and deliberately not exported — an exported array trips react-refresh/only-export-components, as the sibling constant's comment records). The only new published surface is a data-testid attribute and one i18n key. startDateField's internal type widens to string | undefined inside the component; it is a local, not a prop.

Changeset: .changeset/7459-timeline-absent-date-axis-refusal.md, minor for @object-ui/plugin-timeline and @object-ui/i18n — matching the grade #7070 used for the identical class of change. Graded here and confirmed by the gate's own verdict line: check-changeset-no-major → "✅ No changeset declares a major bump".

Not in this card

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…stead of inventing one
Steps (1) and (2) of the three-step sequence ruled on objectui#7070
(maintainer, 2026-09-01). `ObjectTimeline` resolved its date axis through five
declared bindings and then closed the chain with a bare literal field name
nobody has ever declared, so a name always resolved: every record read a key
its object does not carry and bucketed into "No date".
The floor is retired and an absent-axis refusal added, together — a refusal
added while the floor stood would have been unreachable, and the floor retired
without a refusal produces the silent "No date" outcome the ruling rejects.
The refusal names every binding it accepts, in the shape objectui#7070 settled
one renderer over.
The component's success surface is now named (`timeline-canvas`) so a refusal
can be distinguished from a rendered-but-empty timeline, which is the
distinction the ruling turns on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3180.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-D2L2XDb9.js
StatusFAIL

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 halfVerdict
Aggregate closure ceiling✅ pass
Per-chunk ceilings❌ over its ceiling
Ceiling sensitivity (headroom)✅ pass
Ceiling freshness (checkout vs. base branch)✅ pass

📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT — verified on the branch by content, ⛔ not against the report

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Branch head d2dbdfb8c, base c1fe272ad.

⭐ My brief was wrong, and the dev measured it rather than following it

My dispatch — and the card — both say "four honest rungs, then a fabricated fifth." The chain actually reads five declared bindings and a fabricated sixth: timeline.startDateField, timeline.dateField, mapping.date, flat startDateField, flat dateField, then 'date'.

Off by one, and it was mine. The dev re-derived the chain instead of trusting the count, then checked the other five and reported that all are genuine declared bindings — no further fabrication — without extending scope to "fix" anything. That is exactly the shape the brief asked for and the opposite of what an off-by-one usually produces.

The readings

probeorigin/mainbranch
?? 'date' in ObjectTimeline.tsxlive at :2490
⭐ CONTROL startDateField, same file1218
OBJECT_BOUND_TIMELINE_DATE_BINDINGS:68
refusal interpolating that list:574-575
data-testid="timeline-canvas":642
pin ObjectTimeline.absentDateAxisRefusal-7459.test.tsxabsentexists
locale packs carrying noDateAxis010

The tombstone here is deliberately spelled so it does NOT quote the removed literal. The dev says why: a comment quoting it would make the close-condition scan answer 1 where the truth is 0. That is precisely the trap that has fired on me three times today, anticipated and designed out rather than documented after the fact.

⛔ The step-③ fence held, checked rather than assumed

created_at at the two plugin faces is identical on both sidesListView.tsx 4, plugin-view/ObjectView.tsx 1. That was the edit most likely to be made on momentum: the house posture now on record makes those literals look obviously wrong, and they are, but they are step ③ of the #7070 ruling and blocked on this PR landing. Untouched.

Two judgement calls I want on the record

1. The named success surface is an enabling change, declared not smuggled.data-testid="timeline-canvas" is new. Without a name for the rendered timeline, a pin cannot separate "refused" from "rendered an empty timeline" — and that distinction is the entire point of the ruling, since the failure mode it rejects is a silently empty "No date" render. Every other terminal state of this component already named itself; the success state was the gap. Correct to add, and correct to call out.

2. The de value carries no typographic quote pair on purpose.de-quote-pairing-3876.test.ts ratchets paired-span counts; a quoted German string would have pushed it off 59 and invited a ceiling bump. The dev wrote the value to fit the existing ratchet instead. ⛔ Never raise a gate ceiling — met here without being told.

The ablation tests the pairing, which is the right subject

Mutation = put the floor back while the refusal stays. That is not "undo the change" — it is the specific claim this card rests on, that a refusal behind a floor is unreachable. Predicted 4 RED / 7 GREEN by construction, and the seven were named in advance as fully-declared cases unaffected in both worlds. Observed 4 failed / 7 passed, the four failures exactly the four predicted, by name.

That asymmetry is the acceptance criterion for this class — it separates "stopped inventing" from "refuses everything" — and the blast-radius measurement closes it: nothing in the product starts refusing, because both plugin faces still supply created_at and app-shell routes through ListView. The refusal is reached only by a view forced directly onto the renderer with nothing declared.

Landing

Clause ② NO, re-declared against the diff rather than inherited from my brief: no spec key, no zod validator, no exported type. OBJECT_BOUND_TIMELINE_DATE_BINDINGS is module-local and deliberately unexported (an export trips react-refresh/only-export-components). The only new published surface is one data-testid and one i18n key. ⇒ this seat lands it.

Two gates are declared NOT MEASURED rather than green — check:readme-exports and check:eager-closure, both needing a build CI does anyway. Reported as missing prerequisites, not passes. Correct.

CI is still running several jobs. ⛔ Not enqueueing until every check is green — the bar is every check, not the required subset.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

⛔ Standing down on Bundle Analysis — the failure is not this PR's, and main is over. ⚠️ Shared damage.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Not enqueueing while red; not raising any ceiling; no re-run (the build is deterministic, so a second run answers the same).

What failed

From the gate's own output, ⛔ not the summary comment:

✅ Console eager closure is 3180.1 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 11.3 KB).
❌ 1 eager chunk is over its per-chunk budget:
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
❌ framework 512.8 KB / 511.7 KB ceiling (OVER by 1.1 KB)
✅ ui-components 387.8 KB / 389.6 KB ceiling (headroom 1.9 KB)

The aggregate passes with 11.3 KB spare; sensitivity and freshness both pass. Only the per-chunk half objected — which is precisely the case that check was added for (#5490): one chunk growing while others shrink is invisible to a single total.

Why it is not this PR's — structural, not a plea

apps/console/vite.config.ts:742 attributes that chunk by path:

{name: 'framework',test: /[\\/]packages[\\/](core|react|types)[\\/]/,priority: 80}

This PR's complete file list is .changeset/, ten packages/i18n/src/locales/*.ts, and three files under packages/plugin-timeline/src/. Not one path matches core|react|types. So this diff cannot contribute a byte to the framework chunk, and the 512.8 KB it measured is main's own framework chunk.

main is over the per-chunk ceiling. This is not a regression this PR introduced; it is a regression this PR is the first to measure.

⚠️ Which means every PR in this repo is now red on this check

That makes it shared damage rather than one PR's problem, and it is being escalated rather than worked around here.

⭐ The likely cause is a PR I landed, and I am naming it

Diffing the framework-group paths between the last passing measurement (3804c222b, PR #7464 at 07:07) and this PR's base (c1fe272ad), exactly two files entered that group:

packages/react/src/__tests__/SchemaRenderer.hiddenVisibleSynonymy.test.tsx | +84
packages/types/src/base.ts | +33 −3

Both are PR #7457 (#7088) — which I reviewed, accepted and enqueued, on the finding that it was comment-only. That finding was correct about behaviour: the declarations are untouched and no accept set moved. ⛔ I did not weigh it for bytes, and packages/types sits inside a path-attributed chunk that had roughly 177 bytes of headroom — a fact I had measured earlier this same shift while analysing #7442, and did not apply here.

⚠️ Stated as the open question it is, ⛔ not as a conclusion: #7457's ownBundle Analysis passed pre-merge, on a base that predated #7453. A test file under __tests__ should not reach the console graph at all, and JSDoc should not survive minification — so how 33 lines of comment become ~1.1 KB of shipped chunk is not established, and I am not going to guess it. What is established is the path proof above and the fact that nothing else entered that group.

What I am not doing

⛔ Not raising PER_CHUNK_GZIP_CEILINGS['framework']. The gate says it plainly — "do not widen it just to get a green check" — and a ceiling raise here would bury the regression rather than fix it.
⛔ Not re-running. The build is deterministic; a re-run is not a diagnosis.
⛔ Not widening this PR to carry someone else's byte fix.

This PR stays open and watched. It goes green when main does.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Base branch brought in — ⛔ not a re-run, and the result answers #7399

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. I merged main into this branch. Recording why, because "the head moved and CI re-ran" should never be mistaken for kicking a flaky check.

Why this is a content change, not a re-run

This PR's base was c1fe272ad. main has since moved to 0246d116e, and the move materially changed the thing that was failing:

baseframeworkover by
c1fe272ad — this PR's old base512.8 KB1.1 KB
0246d116e — current main (measured through PR #7471)511.9 KB0.2 KB

⇒ roughly 0.9 KB came back out of the chunk between those two commits. Re-running on the old base would have reproduced 512.8 KB exactly — the build is deterministic. Building against the new base is a different measurement, and it is the one that matters now.

That is also what the standing rule prescribes when a base recovers: bring the base branch in and let CI re-run against it, rather than waiting.

⭐ Why this particular PR's verdict is worth more than its own greenness

This diff touches onlypackages/plugin-timeline/src/ and packages/i18n/src/locales/. The framework chunk is attributed by path — /[\\/]packages[\\/](core|react|types)[\\/]/ — so this PR contributes nothing to it.

⇒ On the new base, this PR's framework figure is main-alone's figure, as directly as CI can give it. So:

⚠️ With the caveat I already had to record on #7399: a docs-only commit moved this chunk ~0.9 KB, so the figure is not a pure function of framework-group content. This reading is strong evidence, not proof. It does not replace the isolation measurement still running.

State of this PR otherwise

Every other check was green before this update — 30 of 31, with Bundle Analysis the single red. ACCEPT stands (5522135254); the by-content verification was done on d2dbdfb8c and the merge does not touch the files it verified. Clause ② no, so this seat lands it once every check is green.

⛔ Still no ceiling raise. ⛔ Still no re-run of the old build.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3180.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-D2L2XDb9.js
StatusFAIL

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 halfVerdict
Aggregate closure ceiling✅ pass
Per-chunk ceilings❌ over its ceiling
Ceiling sensitivity (headroom)✅ pass
Ceiling freshness (checkout vs. base branch)✅ pass

📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

⛔ RETRACTION — the Bundle Analysis failure is this PR's. My standing-down comment was wrong.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Withdrawing 5522177212 ("the failure is not this PR's, and main is over") and the follow-up 5522591077 that reasoned from it.

What I got wrong

I argued that because this diff touches no path matching /packages/(core|react|types)/, it could not contribute to the framework chunk. The chunk is not governed by that regex. Measured module membership: framework also contains all ten @object-ui/i18n locale bundles, which are 78.7% of its bytesframework and infrastructure are tied at priority 80 and framework is listed first, so the i18n subgraph reached via @object-ui/react is absorbed into it.

packages/i18n/src/locales/ is inside the framework chunk. This PR adds a translation key to all ten locale files. Those are its own bytes, in that chunk, and they are what put it over.

Full measurement and controls on #7399 (5522762441) and my retraction of the rest (5522794512). The single reading that settles it — a grep of the emitted chunk, controls firing in the same run:

controls: 'SchemaRenderer' 5 · '_hidden' 3 · 'unscheduled' 10

unscheduled is #7453's locale key, appearing ten times inside assets/framework-*.js. Locale strings ship in this chunk. That is exactly the shape of what this PR adds.

Also retracted, since I said them here: main is not over — it passes at every base (30 B headroom at the tip) — and the rebase I did was therefore not the experiment I described. It changed nothing, correctly: the chunk came back byte-identical.

Where this leaves the PR

The refusal message is required by the ruling this card implements (#7070, 总监批 #28) and has to exist in ten locales. With 30 bytes of headroom on main, it does not fit — the same wall that already stopped #7391 (#7210/#7225) and #7194, both of which are also locale-pack copy. This PR is the third instance of one problem, not a new one.

⇒ ⛔ Not landing this by shrinking ruled copy, and ⛔ not by raising a ceiling. It joins the other two behind the decision on #7399, where the option that actually addresses it — fixing the attribution so framework stops policing translations — is now on the table with measurements attached.

Everything else on this PR stands: the by-content verification, the ablation, the fence check, and the ACCEPT (5522135254). Its other 30 checks are green. Clause ② is still no.

⚠️ For whoever picks this up: the rule I acted on — "this PR touches no framework path, so the bytes aren't mine" — is false in this repo, and I published it twice before measuring. If a PR adds locale keys, it adds framework-chunk bytes.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3180.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-C4NaNsQ-.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

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

Labels

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one - #7467

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-7459-timeline-absent-axis-refusal
Sep 3, 2026
Merged

fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one#7467
os-project-manager merged 3 commits into
mainfrom
claude/issue-7459-timeline-absent-axis-refusal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7459

Steps ① and ② of the three-step sequence ruled on #7070 (project director seat, 总监批 #28, 2026-09-01, maintainer verbatim 「同意」). Step ③ — the 'created_at' floors on the two plugin faces — stays on #7070 and is not touched here.

The gate: both premises re-measured on the current tree

The card's figures were taken on 2c3cd1b75. Everything was re-derived on 3804c222b before the first edit.

Premise 1 — no absent-date-axis refusal path. REPRODUCES. Zero hits for such a refusal in packages/plugin-timeline. The control fires in the same run and in the same file: the term refus matched 6 lines of ObjectTimeline.tsx (the card said 5 — the file has moved, and the control is live rather than stale). Every one of those 6 is the #6655variant: 'gantt' refusal or its prose; none is about a date axis.

Premise 2 — the internal floor. REPRODUCES, and the line numbers happen to still hold.packages/plugin-timeline/src/ObjectTimeline.tsx:247-249, spelled exactly as reported.

Correction to the card's arithmetic. The card describes "four honest rungs, then a fabricated fifth". The chain reads five declared bindings and then a fabricated sixth: timeline.startDateField, timeline.dateField, mapping.date, flat startDateField, flat dateField. All five were re-checked as asked, and all five are genuine declared bindings — the first two on ListViewTimelineConfig (packages/types/src/objectql.ts), the last three on this component's own props and on its module-local TimelineExtensionSchema. No further fabrication among them.

What changed

① The refusal. An object-bound timeline that declares no date axis renders a diagnostic naming every binding it accepts, instead of a chart. Follows the settled in-repo shape (ObjectGantt's "Gantt configuration required" screen) rather than a second dialect: early return, role="alert", names the fields the author must supply. The list is interpolated from the component's own OBJECT_BOUND_TIMELINE_DATE_BINDINGS — the same drift-proof device OBJECT_BOUND_TIMELINE_VARIANTS already uses one refusal up — so the diagnostic cannot name a vocabulary the resolver no longer has.

② The floor is retired, which is the only thing that makes ① reachable. The tombstone comment deliberately does not quote the removed literal: the card's close condition greps this file for it, and a comment quoting it would answer 1 where the truth is 0. Verified after the final commit: 0 hits for the floor, control term firing at 25 lines in the same file.

Enabling change, declared rather than smuggled: the component's success surface is now named data-testid="timeline-canvas". Every other terminal state of this component already named itself (timeline-unsupported-variant, timeline-missing-date-axis, timeline-error, timeline-loading); the rendered timeline was the one outcome a test could not ask for by name, which is exactly the distinction the ruling turns on.

i18n. One new key, timeline.unconfigured.noDateAxis, in all ten locale packs plus the plugin's own default table (byte-identical to en). The German value carries no typographic quote pair on purpose — de-quote-pairing-3876.test.ts ratchets that count, and it stays at 59.

Blast radius, measured

Nothing in the product starts refusing. Both plugin faces still supply startDateField: … || 'created_at' (plugin-list/src/ListView.tsx:2496, plugin-view/src/ObjectView.tsx:1354 — both re-derived; the card's line numbers were stale). App-shell's timelineViewOptions emits no axis, but routes through ListView, which floors. The refusal is reached only by a view forced directly onto the timeline renderer with nothing declared — which is what the card predicted, and what makes ③ the visible step.

The pin

packages/plugin-timeline/src/ObjectTimeline.absentDateAxisRefusal-7459.test.tsx, 11 cases, the twin of ObjectGantt.unconfiguredRefusal-7070.test.tsx.

  • Refusal is not "renders empty". Two independent canvas markers asserted ABSENTtimeline-canvas and role="list" (the ordered-list rail the real renderer emits, the ol element, which an empty timeline still emits). Neither zero is vacuous: the controls in the same file assert both PRESENT.
  • The pairing. One case hands in records that happen to carry a column named date. Restore the floor and it goes red while rendering a completely convincing timeline off a real column — which "no events" would never have caught. Declared-ness, not existence, is what an axis is.
  • The negative direction. All five declared rungs each render the timeline with its events, in their own spelling.
  • The carve-out. An authored items list is never refused (it carries its own time; no field name is read), which is what keeps the three in-repo catalog fixtures rendering.
  • Ordering. The finding(plugin-timeline): an object-bound timeline with variant: "gantt" throws — calculateDateRange reads a row shape ObjectTimeline never composes #6655 variant refusal still wins for a composed gantt, pinned so it is a decision rather than an accident of placement.
  • The renderer is not stubbed, for the reason timeline-object-bound-gantt-refusal.test.tsx states one file over.

Verification

All figures from runs at the final commit d2dbdfb8c; heavy runs serialized through the shared verify lock, verdict lines read rather than a bare $?.

  • Unionplugin-timeline + i18n + plugin-list + plugin-view: Test Files 175 passed (175) / Tests 2288 passed (2288), VERDICT command-exit 0.
  • Baseline for comparison, taken before any edit: plugin-timeline = 21 files / 276 tests. After: 22 / 287.
  • type-checkplugin-timeline + i18n → exit 0, Scope: 2 of 47 workspace projects, each echoing tsc --noEmit && tsc -p tsconfig.test.json + Done (so the zero-match filter trap did not fire). The new test file is genuinely covered, not excluded: tsc -p tsconfig.test.json --listFiles counts it 1.
  • Gates, each verdict line quoted from the gate's own output, exit code captured before any pipe: check:i18n-keys "every in-scope call-site key resolves against the en pack (2960 keys)"; check:i18n-drift "0 en value(s) changed (1 key(s) added…)"; check:i18n-dead-keys (report) 0; check:control-bytes "OK (scanned 6140 tracked text file(s))"; check:vi-mock-specifiers OK; check:vi-mock-inherit "122 call site(s) on @object-ui/react judged (122 inherit, 0 auto-mocked)"; check:side-effects-array OK; check-changeset-presence "13 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"; check-changeset-no-major "No changeset declares a major bump"; check-changeset-fixed OK; check-changeset-overwrite OK.
  • NOT MEASURED, not red:check:readme-exports and check:eager-closure both stop on a prerequisite — all 314 findings of the former are "type entry ./dist/index.d.ts is not on disk — run pnpm build first", and the latter reports "No eager-closure report … the console was not built". Both need a full-monorepo / console build, which CI does regardless. Recorded as unmeasured rather than as failures.
  • eslint, plain form (never --no-inline-config): exit 0, 0 errors, 29 warnings, all pre-existing in class. Declared narrowing: run on the 14 changed files rather than the repo-wide farm. Evidence it excludes nothing — file count 14 read from --format json; this config enables no type-aware linting (no projectService / parserOptions.project in eslint.config.js), so the diff cannot move an untouched file's verdict; CI runs the full farm regardless. The two react-hooks warnings on ObjectTimeline.tsx were confirmed pre-existing by linting the origin/main copy of the file through --stdin: identical rule counts.

Reverse verification (ablation) — direction predicted, then observed

Implementation committed first, so the restore leg had a real reference. Mutation: put the retired floor back while the refusal stays — the pairing itself.

  • Predicted before the run: 4 RED (the three refusal cases + the message case), 7 GREEN by construction (the five declared-rung controls, the authored-items control, the finding(plugin-timeline): an object-bound timeline with variant: "gantt" throws — calculateDateRange reads a row shape ObjectTimeline never composes #6655 ordering case — all fully declared, so unaffected in both worlds).
  • Observed:Tests 4 failed | 7 passed (11), and the four failures are exactly the four predicted, by name.
  • Mutation proven on disk, not by an editor's exit code: anchor lines 1 → 0, injected lines 0 → 1, the injected hit printed and read; blob 1b4575de…8cea816b…. The anchor was asserted unique first, so a zero-match no-op would have aborted rather than reported a healthy run.
  • Restore proven by state, not by exit code: git hash-object == git rev-parse HEAD:PATH (non-empty and matching), git diff HEAD empty, git status clean. Absolute-path trap … EXIT INT TERM.
  • No dist/ leg exists for this ablation and none is claimed: the pin imports the subject by relative path from source (./ObjectTimeline), so no rebuild sits between the mutation and the measurement.

Clause ②

No, and re-declared against the actual diff rather than inherited from the card. Nothing published moves: no spec key, no zod validator, no exported type. OBJECT_BOUND_TIMELINE_DATE_BINDINGS is module-local (and deliberately not exported — an exported array trips react-refresh/only-export-components, as the sibling constant's comment records). The only new published surface is a data-testid attribute and one i18n key. startDateField's internal type widens to string | undefined inside the component; it is a local, not a prop.

Changeset: .changeset/7459-timeline-absent-date-axis-refusal.md, minor for @object-ui/plugin-timeline and @object-ui/i18n — matching the grade #7070 used for the identical class of change. Graded here and confirmed by the gate's own verdict line: check-changeset-no-major → "✅ No changeset declares a major bump".

Not in this card

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…stead of inventing one
Steps (1) and (2) of the three-step sequence ruled on objectui#7070
(maintainer, 2026-09-01). `ObjectTimeline` resolved its date axis through five
declared bindings and then closed the chain with a bare literal field name
nobody has ever declared, so a name always resolved: every record read a key
its object does not carry and bucketed into "No date".
The floor is retired and an absent-axis refusal added, together — a refusal
added while the floor stood would have been unreachable, and the floor retired
without a refusal produces the silent "No date" outcome the ruling rejects.
The refusal names every binding it accepts, in the shape objectui#7070 settled
one renderer over.
The component's success surface is now named (`timeline-canvas`) so a refusal
can be distinguished from a rendered-but-empty timeline, which is the
distinction the ruling turns on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3180.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-D2L2XDb9.js
StatusFAIL

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 halfVerdict
Aggregate closure ceiling✅ pass
Per-chunk ceilings❌ over its ceiling
Ceiling sensitivity (headroom)✅ pass
Ceiling freshness (checkout vs. base branch)✅ pass

📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT — verified on the branch by content, ⛔ not against the report

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Branch head d2dbdfb8c, base c1fe272ad.

⭐ My brief was wrong, and the dev measured it rather than following it

My dispatch — and the card — both say "four honest rungs, then a fabricated fifth." The chain actually reads five declared bindings and a fabricated sixth: timeline.startDateField, timeline.dateField, mapping.date, flat startDateField, flat dateField, then 'date'.

Off by one, and it was mine. The dev re-derived the chain instead of trusting the count, then checked the other five and reported that all are genuine declared bindings — no further fabrication — without extending scope to "fix" anything. That is exactly the shape the brief asked for and the opposite of what an off-by-one usually produces.

The readings

probeorigin/mainbranch
?? 'date' in ObjectTimeline.tsxlive at :2490
⭐ CONTROL startDateField, same file1218
OBJECT_BOUND_TIMELINE_DATE_BINDINGS:68
refusal interpolating that list:574-575
data-testid="timeline-canvas":642
pin ObjectTimeline.absentDateAxisRefusal-7459.test.tsxabsentexists
locale packs carrying noDateAxis010

The tombstone here is deliberately spelled so it does NOT quote the removed literal. The dev says why: a comment quoting it would make the close-condition scan answer 1 where the truth is 0. That is precisely the trap that has fired on me three times today, anticipated and designed out rather than documented after the fact.

⛔ The step-③ fence held, checked rather than assumed

created_at at the two plugin faces is identical on both sidesListView.tsx 4, plugin-view/ObjectView.tsx 1. That was the edit most likely to be made on momentum: the house posture now on record makes those literals look obviously wrong, and they are, but they are step ③ of the #7070 ruling and blocked on this PR landing. Untouched.

Two judgement calls I want on the record

1. The named success surface is an enabling change, declared not smuggled.data-testid="timeline-canvas" is new. Without a name for the rendered timeline, a pin cannot separate "refused" from "rendered an empty timeline" — and that distinction is the entire point of the ruling, since the failure mode it rejects is a silently empty "No date" render. Every other terminal state of this component already named itself; the success state was the gap. Correct to add, and correct to call out.

2. The de value carries no typographic quote pair on purpose.de-quote-pairing-3876.test.ts ratchets paired-span counts; a quoted German string would have pushed it off 59 and invited a ceiling bump. The dev wrote the value to fit the existing ratchet instead. ⛔ Never raise a gate ceiling — met here without being told.

The ablation tests the pairing, which is the right subject

Mutation = put the floor back while the refusal stays. That is not "undo the change" — it is the specific claim this card rests on, that a refusal behind a floor is unreachable. Predicted 4 RED / 7 GREEN by construction, and the seven were named in advance as fully-declared cases unaffected in both worlds. Observed 4 failed / 7 passed, the four failures exactly the four predicted, by name.

That asymmetry is the acceptance criterion for this class — it separates "stopped inventing" from "refuses everything" — and the blast-radius measurement closes it: nothing in the product starts refusing, because both plugin faces still supply created_at and app-shell routes through ListView. The refusal is reached only by a view forced directly onto the renderer with nothing declared.

Landing

Clause ② NO, re-declared against the diff rather than inherited from my brief: no spec key, no zod validator, no exported type. OBJECT_BOUND_TIMELINE_DATE_BINDINGS is module-local and deliberately unexported (an export trips react-refresh/only-export-components). The only new published surface is one data-testid and one i18n key. ⇒ this seat lands it.

Two gates are declared NOT MEASURED rather than green — check:readme-exports and check:eager-closure, both needing a build CI does anyway. Reported as missing prerequisites, not passes. Correct.

CI is still running several jobs. ⛔ Not enqueueing until every check is green — the bar is every check, not the required subset.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

⛔ Standing down on Bundle Analysis — the failure is not this PR's, and main is over. ⚠️ Shared damage.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Not enqueueing while red; not raising any ceiling; no re-run (the build is deterministic, so a second run answers the same).

What failed

From the gate's own output, ⛔ not the summary comment:

✅ Console eager closure is 3180.1 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 11.3 KB).
❌ 1 eager chunk is over its per-chunk budget:
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
❌ framework 512.8 KB / 511.7 KB ceiling (OVER by 1.1 KB)
✅ ui-components 387.8 KB / 389.6 KB ceiling (headroom 1.9 KB)

The aggregate passes with 11.3 KB spare; sensitivity and freshness both pass. Only the per-chunk half objected — which is precisely the case that check was added for (#5490): one chunk growing while others shrink is invisible to a single total.

Why it is not this PR's — structural, not a plea

apps/console/vite.config.ts:742 attributes that chunk by path:

{name: 'framework',test: /[\\/]packages[\\/](core|react|types)[\\/]/,priority: 80}

This PR's complete file list is .changeset/, ten packages/i18n/src/locales/*.ts, and three files under packages/plugin-timeline/src/. Not one path matches core|react|types. So this diff cannot contribute a byte to the framework chunk, and the 512.8 KB it measured is main's own framework chunk.

main is over the per-chunk ceiling. This is not a regression this PR introduced; it is a regression this PR is the first to measure.

⚠️ Which means every PR in this repo is now red on this check

That makes it shared damage rather than one PR's problem, and it is being escalated rather than worked around here.

⭐ The likely cause is a PR I landed, and I am naming it

Diffing the framework-group paths between the last passing measurement (3804c222b, PR #7464 at 07:07) and this PR's base (c1fe272ad), exactly two files entered that group:

packages/react/src/__tests__/SchemaRenderer.hiddenVisibleSynonymy.test.tsx | +84
packages/types/src/base.ts | +33 −3

Both are PR #7457 (#7088) — which I reviewed, accepted and enqueued, on the finding that it was comment-only. That finding was correct about behaviour: the declarations are untouched and no accept set moved. ⛔ I did not weigh it for bytes, and packages/types sits inside a path-attributed chunk that had roughly 177 bytes of headroom — a fact I had measured earlier this same shift while analysing #7442, and did not apply here.

⚠️ Stated as the open question it is, ⛔ not as a conclusion: #7457's ownBundle Analysis passed pre-merge, on a base that predated #7453. A test file under __tests__ should not reach the console graph at all, and JSDoc should not survive minification — so how 33 lines of comment become ~1.1 KB of shipped chunk is not established, and I am not going to guess it. What is established is the path proof above and the fact that nothing else entered that group.

What I am not doing

⛔ Not raising PER_CHUNK_GZIP_CEILINGS['framework']. The gate says it plainly — "do not widen it just to get a green check" — and a ceiling raise here would bury the regression rather than fix it.
⛔ Not re-running. The build is deterministic; a re-run is not a diagnosis.
⛔ Not widening this PR to carry someone else's byte fix.

This PR stays open and watched. It goes green when main does.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Base branch brought in — ⛔ not a re-run, and the result answers #7399

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. I merged main into this branch. Recording why, because "the head moved and CI re-ran" should never be mistaken for kicking a flaky check.

Why this is a content change, not a re-run

This PR's base was c1fe272ad. main has since moved to 0246d116e, and the move materially changed the thing that was failing:

baseframeworkover by
c1fe272ad — this PR's old base512.8 KB1.1 KB
0246d116e — current main (measured through PR #7471)511.9 KB0.2 KB

⇒ roughly 0.9 KB came back out of the chunk between those two commits. Re-running on the old base would have reproduced 512.8 KB exactly — the build is deterministic. Building against the new base is a different measurement, and it is the one that matters now.

That is also what the standing rule prescribes when a base recovers: bring the base branch in and let CI re-run against it, rather than waiting.

⭐ Why this particular PR's verdict is worth more than its own greenness

This diff touches onlypackages/plugin-timeline/src/ and packages/i18n/src/locales/. The framework chunk is attributed by path — /[\\/]packages[\\/](core|react|types)[\\/]/ — so this PR contributes nothing to it.

⇒ On the new base, this PR's framework figure is main-alone's figure, as directly as CI can give it. So:

⚠️ With the caveat I already had to record on #7399: a docs-only commit moved this chunk ~0.9 KB, so the figure is not a pure function of framework-group content. This reading is strong evidence, not proof. It does not replace the isolation measurement still running.

State of this PR otherwise

Every other check was green before this update — 30 of 31, with Bundle Analysis the single red. ACCEPT stands (5522135254); the by-content verification was done on d2dbdfb8c and the merge does not touch the files it verified. Clause ② no, so this seat lands it once every check is green.

⛔ Still no ceiling raise. ⛔ Still no re-run of the old build.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3180.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-D2L2XDb9.js
StatusFAIL

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 halfVerdict
Aggregate closure ceiling✅ pass
Per-chunk ceilings❌ over its ceiling
Ceiling sensitivity (headroom)✅ pass
Ceiling freshness (checkout vs. base branch)✅ pass

📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

⛔ RETRACTION — the Bundle Analysis failure is this PR's. My standing-down comment was wrong.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Withdrawing 5522177212 ("the failure is not this PR's, and main is over") and the follow-up 5522591077 that reasoned from it.

What I got wrong

I argued that because this diff touches no path matching /packages/(core|react|types)/, it could not contribute to the framework chunk. The chunk is not governed by that regex. Measured module membership: framework also contains all ten @object-ui/i18n locale bundles, which are 78.7% of its bytesframework and infrastructure are tied at priority 80 and framework is listed first, so the i18n subgraph reached via @object-ui/react is absorbed into it.

packages/i18n/src/locales/ is inside the framework chunk. This PR adds a translation key to all ten locale files. Those are its own bytes, in that chunk, and they are what put it over.

Full measurement and controls on #7399 (5522762441) and my retraction of the rest (5522794512). The single reading that settles it — a grep of the emitted chunk, controls firing in the same run:

controls: 'SchemaRenderer' 5 · '_hidden' 3 · 'unscheduled' 10

unscheduled is #7453's locale key, appearing ten times inside assets/framework-*.js. Locale strings ship in this chunk. That is exactly the shape of what this PR adds.

Also retracted, since I said them here: main is not over — it passes at every base (30 B headroom at the tip) — and the rebase I did was therefore not the experiment I described. It changed nothing, correctly: the chunk came back byte-identical.

Where this leaves the PR

The refusal message is required by the ruling this card implements (#7070, 总监批 #28) and has to exist in ten locales. With 30 bytes of headroom on main, it does not fit — the same wall that already stopped #7391 (#7210/#7225) and #7194, both of which are also locale-pack copy. This PR is the third instance of one problem, not a new one.

⇒ ⛔ Not landing this by shrinking ruled copy, and ⛔ not by raising a ceiling. It joins the other two behind the decision on #7399, where the option that actually addresses it — fixing the attribution so framework stops policing translations — is now on the table with measurements attached.

Everything else on this PR stands: the by-content verification, the ablation, the fence check, and the ACCEPT (5522135254). Its other 30 checks are green. Clause ② is still no.

⚠️ For whoever picks this up: the rule I acted on — "this PR touches no framework path, so the bytes aren't mine" — is false in this repo, and I published it twice before measuring. If a PR adds locale keys, it adds framework-chunk bytes.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3180.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-C4NaNsQ-.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

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

Labels

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one - #7467

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-7459-timeline-absent-axis-refusal
Sep 3, 2026
Merged

fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one#7467
os-project-manager merged 3 commits into
mainfrom
claude/issue-7459-timeline-absent-axis-refusal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7459

Steps ① and ② of the three-step sequence ruled on #7070 (project director seat, 总监批 #28, 2026-09-01, maintainer verbatim 「同意」). Step ③ — the 'created_at' floors on the two plugin faces — stays on #7070 and is not touched here.

The gate: both premises re-measured on the current tree

The card's figures were taken on 2c3cd1b75. Everything was re-derived on 3804c222b before the first edit.

Premise 1 — no absent-date-axis refusal path. REPRODUCES. Zero hits for such a refusal in packages/plugin-timeline. The control fires in the same run and in the same file: the term refus matched 6 lines of ObjectTimeline.tsx (the card said 5 — the file has moved, and the control is live rather than stale). Every one of those 6 is the #6655variant: 'gantt' refusal or its prose; none is about a date axis.

Premise 2 — the internal floor. REPRODUCES, and the line numbers happen to still hold.packages/plugin-timeline/src/ObjectTimeline.tsx:247-249, spelled exactly as reported.

Correction to the card's arithmetic. The card describes "four honest rungs, then a fabricated fifth". The chain reads five declared bindings and then a fabricated sixth: timeline.startDateField, timeline.dateField, mapping.date, flat startDateField, flat dateField. All five were re-checked as asked, and all five are genuine declared bindings — the first two on ListViewTimelineConfig (packages/types/src/objectql.ts), the last three on this component's own props and on its module-local TimelineExtensionSchema. No further fabrication among them.

What changed

① The refusal. An object-bound timeline that declares no date axis renders a diagnostic naming every binding it accepts, instead of a chart. Follows the settled in-repo shape (ObjectGantt's "Gantt configuration required" screen) rather than a second dialect: early return, role="alert", names the fields the author must supply. The list is interpolated from the component's own OBJECT_BOUND_TIMELINE_DATE_BINDINGS — the same drift-proof device OBJECT_BOUND_TIMELINE_VARIANTS already uses one refusal up — so the diagnostic cannot name a vocabulary the resolver no longer has.

② The floor is retired, which is the only thing that makes ① reachable. The tombstone comment deliberately does not quote the removed literal: the card's close condition greps this file for it, and a comment quoting it would answer 1 where the truth is 0. Verified after the final commit: 0 hits for the floor, control term firing at 25 lines in the same file.

Enabling change, declared rather than smuggled: the component's success surface is now named data-testid="timeline-canvas". Every other terminal state of this component already named itself (timeline-unsupported-variant, timeline-missing-date-axis, timeline-error, timeline-loading); the rendered timeline was the one outcome a test could not ask for by name, which is exactly the distinction the ruling turns on.

i18n. One new key, timeline.unconfigured.noDateAxis, in all ten locale packs plus the plugin's own default table (byte-identical to en). The German value carries no typographic quote pair on purpose — de-quote-pairing-3876.test.ts ratchets that count, and it stays at 59.

Blast radius, measured

Nothing in the product starts refusing. Both plugin faces still supply startDateField: … || 'created_at' (plugin-list/src/ListView.tsx:2496, plugin-view/src/ObjectView.tsx:1354 — both re-derived; the card's line numbers were stale). App-shell's timelineViewOptions emits no axis, but routes through ListView, which floors. The refusal is reached only by a view forced directly onto the timeline renderer with nothing declared — which is what the card predicted, and what makes ③ the visible step.

The pin

packages/plugin-timeline/src/ObjectTimeline.absentDateAxisRefusal-7459.test.tsx, 11 cases, the twin of ObjectGantt.unconfiguredRefusal-7070.test.tsx.

  • Refusal is not "renders empty". Two independent canvas markers asserted ABSENTtimeline-canvas and role="list" (the ordered-list rail the real renderer emits, the ol element, which an empty timeline still emits). Neither zero is vacuous: the controls in the same file assert both PRESENT.
  • The pairing. One case hands in records that happen to carry a column named date. Restore the floor and it goes red while rendering a completely convincing timeline off a real column — which "no events" would never have caught. Declared-ness, not existence, is what an axis is.
  • The negative direction. All five declared rungs each render the timeline with its events, in their own spelling.
  • The carve-out. An authored items list is never refused (it carries its own time; no field name is read), which is what keeps the three in-repo catalog fixtures rendering.
  • Ordering. The finding(plugin-timeline): an object-bound timeline with variant: "gantt" throws — calculateDateRange reads a row shape ObjectTimeline never composes #6655 variant refusal still wins for a composed gantt, pinned so it is a decision rather than an accident of placement.
  • The renderer is not stubbed, for the reason timeline-object-bound-gantt-refusal.test.tsx states one file over.

Verification

All figures from runs at the final commit d2dbdfb8c; heavy runs serialized through the shared verify lock, verdict lines read rather than a bare $?.

  • Unionplugin-timeline + i18n + plugin-list + plugin-view: Test Files 175 passed (175) / Tests 2288 passed (2288), VERDICT command-exit 0.
  • Baseline for comparison, taken before any edit: plugin-timeline = 21 files / 276 tests. After: 22 / 287.
  • type-checkplugin-timeline + i18n → exit 0, Scope: 2 of 47 workspace projects, each echoing tsc --noEmit && tsc -p tsconfig.test.json + Done (so the zero-match filter trap did not fire). The new test file is genuinely covered, not excluded: tsc -p tsconfig.test.json --listFiles counts it 1.
  • Gates, each verdict line quoted from the gate's own output, exit code captured before any pipe: check:i18n-keys "every in-scope call-site key resolves against the en pack (2960 keys)"; check:i18n-drift "0 en value(s) changed (1 key(s) added…)"; check:i18n-dead-keys (report) 0; check:control-bytes "OK (scanned 6140 tracked text file(s))"; check:vi-mock-specifiers OK; check:vi-mock-inherit "122 call site(s) on @object-ui/react judged (122 inherit, 0 auto-mocked)"; check:side-effects-array OK; check-changeset-presence "13 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"; check-changeset-no-major "No changeset declares a major bump"; check-changeset-fixed OK; check-changeset-overwrite OK.
  • NOT MEASURED, not red:check:readme-exports and check:eager-closure both stop on a prerequisite — all 314 findings of the former are "type entry ./dist/index.d.ts is not on disk — run pnpm build first", and the latter reports "No eager-closure report … the console was not built". Both need a full-monorepo / console build, which CI does regardless. Recorded as unmeasured rather than as failures.
  • eslint, plain form (never --no-inline-config): exit 0, 0 errors, 29 warnings, all pre-existing in class. Declared narrowing: run on the 14 changed files rather than the repo-wide farm. Evidence it excludes nothing — file count 14 read from --format json; this config enables no type-aware linting (no projectService / parserOptions.project in eslint.config.js), so the diff cannot move an untouched file's verdict; CI runs the full farm regardless. The two react-hooks warnings on ObjectTimeline.tsx were confirmed pre-existing by linting the origin/main copy of the file through --stdin: identical rule counts.

Reverse verification (ablation) — direction predicted, then observed

Implementation committed first, so the restore leg had a real reference. Mutation: put the retired floor back while the refusal stays — the pairing itself.

  • Predicted before the run: 4 RED (the three refusal cases + the message case), 7 GREEN by construction (the five declared-rung controls, the authored-items control, the finding(plugin-timeline): an object-bound timeline with variant: "gantt" throws — calculateDateRange reads a row shape ObjectTimeline never composes #6655 ordering case — all fully declared, so unaffected in both worlds).
  • Observed:Tests 4 failed | 7 passed (11), and the four failures are exactly the four predicted, by name.
  • Mutation proven on disk, not by an editor's exit code: anchor lines 1 → 0, injected lines 0 → 1, the injected hit printed and read; blob 1b4575de…8cea816b…. The anchor was asserted unique first, so a zero-match no-op would have aborted rather than reported a healthy run.
  • Restore proven by state, not by exit code: git hash-object == git rev-parse HEAD:PATH (non-empty and matching), git diff HEAD empty, git status clean. Absolute-path trap … EXIT INT TERM.
  • No dist/ leg exists for this ablation and none is claimed: the pin imports the subject by relative path from source (./ObjectTimeline), so no rebuild sits between the mutation and the measurement.

Clause ②

No, and re-declared against the actual diff rather than inherited from the card. Nothing published moves: no spec key, no zod validator, no exported type. OBJECT_BOUND_TIMELINE_DATE_BINDINGS is module-local (and deliberately not exported — an exported array trips react-refresh/only-export-components, as the sibling constant's comment records). The only new published surface is a data-testid attribute and one i18n key. startDateField's internal type widens to string | undefined inside the component; it is a local, not a prop.

Changeset: .changeset/7459-timeline-absent-date-axis-refusal.md, minor for @object-ui/plugin-timeline and @object-ui/i18n — matching the grade #7070 used for the identical class of change. Graded here and confirmed by the gate's own verdict line: check-changeset-no-major → "✅ No changeset declares a major bump".

Not in this card

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…stead of inventing one
Steps (1) and (2) of the three-step sequence ruled on objectui#7070
(maintainer, 2026-09-01). `ObjectTimeline` resolved its date axis through five
declared bindings and then closed the chain with a bare literal field name
nobody has ever declared, so a name always resolved: every record read a key
its object does not carry and bucketed into "No date".
The floor is retired and an absent-axis refusal added, together — a refusal
added while the floor stood would have been unreachable, and the floor retired
without a refusal produces the silent "No date" outcome the ruling rejects.
The refusal names every binding it accepts, in the shape objectui#7070 settled
one renderer over.
The component's success surface is now named (`timeline-canvas`) so a refusal
can be distinguished from a rendered-but-empty timeline, which is the
distinction the ruling turns on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3180.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-D2L2XDb9.js
StatusFAIL

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 halfVerdict
Aggregate closure ceiling✅ pass
Per-chunk ceilings❌ over its ceiling
Ceiling sensitivity (headroom)✅ pass
Ceiling freshness (checkout vs. base branch)✅ pass

📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT — verified on the branch by content, ⛔ not against the report

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Branch head d2dbdfb8c, base c1fe272ad.

⭐ My brief was wrong, and the dev measured it rather than following it

My dispatch — and the card — both say "four honest rungs, then a fabricated fifth." The chain actually reads five declared bindings and a fabricated sixth: timeline.startDateField, timeline.dateField, mapping.date, flat startDateField, flat dateField, then 'date'.

Off by one, and it was mine. The dev re-derived the chain instead of trusting the count, then checked the other five and reported that all are genuine declared bindings — no further fabrication — without extending scope to "fix" anything. That is exactly the shape the brief asked for and the opposite of what an off-by-one usually produces.

The readings

probeorigin/mainbranch
?? 'date' in ObjectTimeline.tsxlive at :2490
⭐ CONTROL startDateField, same file1218
OBJECT_BOUND_TIMELINE_DATE_BINDINGS:68
refusal interpolating that list:574-575
data-testid="timeline-canvas":642
pin ObjectTimeline.absentDateAxisRefusal-7459.test.tsxabsentexists
locale packs carrying noDateAxis010

The tombstone here is deliberately spelled so it does NOT quote the removed literal. The dev says why: a comment quoting it would make the close-condition scan answer 1 where the truth is 0. That is precisely the trap that has fired on me three times today, anticipated and designed out rather than documented after the fact.

⛔ The step-③ fence held, checked rather than assumed

created_at at the two plugin faces is identical on both sidesListView.tsx 4, plugin-view/ObjectView.tsx 1. That was the edit most likely to be made on momentum: the house posture now on record makes those literals look obviously wrong, and they are, but they are step ③ of the #7070 ruling and blocked on this PR landing. Untouched.

Two judgement calls I want on the record

1. The named success surface is an enabling change, declared not smuggled.data-testid="timeline-canvas" is new. Without a name for the rendered timeline, a pin cannot separate "refused" from "rendered an empty timeline" — and that distinction is the entire point of the ruling, since the failure mode it rejects is a silently empty "No date" render. Every other terminal state of this component already named itself; the success state was the gap. Correct to add, and correct to call out.

2. The de value carries no typographic quote pair on purpose.de-quote-pairing-3876.test.ts ratchets paired-span counts; a quoted German string would have pushed it off 59 and invited a ceiling bump. The dev wrote the value to fit the existing ratchet instead. ⛔ Never raise a gate ceiling — met here without being told.

The ablation tests the pairing, which is the right subject

Mutation = put the floor back while the refusal stays. That is not "undo the change" — it is the specific claim this card rests on, that a refusal behind a floor is unreachable. Predicted 4 RED / 7 GREEN by construction, and the seven were named in advance as fully-declared cases unaffected in both worlds. Observed 4 failed / 7 passed, the four failures exactly the four predicted, by name.

That asymmetry is the acceptance criterion for this class — it separates "stopped inventing" from "refuses everything" — and the blast-radius measurement closes it: nothing in the product starts refusing, because both plugin faces still supply created_at and app-shell routes through ListView. The refusal is reached only by a view forced directly onto the renderer with nothing declared.

Landing

Clause ② NO, re-declared against the diff rather than inherited from my brief: no spec key, no zod validator, no exported type. OBJECT_BOUND_TIMELINE_DATE_BINDINGS is module-local and deliberately unexported (an export trips react-refresh/only-export-components). The only new published surface is one data-testid and one i18n key. ⇒ this seat lands it.

Two gates are declared NOT MEASURED rather than green — check:readme-exports and check:eager-closure, both needing a build CI does anyway. Reported as missing prerequisites, not passes. Correct.

CI is still running several jobs. ⛔ Not enqueueing until every check is green — the bar is every check, not the required subset.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

⛔ Standing down on Bundle Analysis — the failure is not this PR's, and main is over. ⚠️ Shared damage.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Not enqueueing while red; not raising any ceiling; no re-run (the build is deterministic, so a second run answers the same).

What failed

From the gate's own output, ⛔ not the summary comment:

✅ Console eager closure is 3180.1 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 11.3 KB).
❌ 1 eager chunk is over its per-chunk budget:
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
❌ framework 512.8 KB / 511.7 KB ceiling (OVER by 1.1 KB)
✅ ui-components 387.8 KB / 389.6 KB ceiling (headroom 1.9 KB)

The aggregate passes with 11.3 KB spare; sensitivity and freshness both pass. Only the per-chunk half objected — which is precisely the case that check was added for (#5490): one chunk growing while others shrink is invisible to a single total.

Why it is not this PR's — structural, not a plea

apps/console/vite.config.ts:742 attributes that chunk by path:

{name: 'framework',test: /[\\/]packages[\\/](core|react|types)[\\/]/,priority: 80}

This PR's complete file list is .changeset/, ten packages/i18n/src/locales/*.ts, and three files under packages/plugin-timeline/src/. Not one path matches core|react|types. So this diff cannot contribute a byte to the framework chunk, and the 512.8 KB it measured is main's own framework chunk.

main is over the per-chunk ceiling. This is not a regression this PR introduced; it is a regression this PR is the first to measure.

⚠️ Which means every PR in this repo is now red on this check

That makes it shared damage rather than one PR's problem, and it is being escalated rather than worked around here.

⭐ The likely cause is a PR I landed, and I am naming it

Diffing the framework-group paths between the last passing measurement (3804c222b, PR #7464 at 07:07) and this PR's base (c1fe272ad), exactly two files entered that group:

packages/react/src/__tests__/SchemaRenderer.hiddenVisibleSynonymy.test.tsx | +84
packages/types/src/base.ts | +33 −3

Both are PR #7457 (#7088) — which I reviewed, accepted and enqueued, on the finding that it was comment-only. That finding was correct about behaviour: the declarations are untouched and no accept set moved. ⛔ I did not weigh it for bytes, and packages/types sits inside a path-attributed chunk that had roughly 177 bytes of headroom — a fact I had measured earlier this same shift while analysing #7442, and did not apply here.

⚠️ Stated as the open question it is, ⛔ not as a conclusion: #7457's ownBundle Analysis passed pre-merge, on a base that predated #7453. A test file under __tests__ should not reach the console graph at all, and JSDoc should not survive minification — so how 33 lines of comment become ~1.1 KB of shipped chunk is not established, and I am not going to guess it. What is established is the path proof above and the fact that nothing else entered that group.

What I am not doing

⛔ Not raising PER_CHUNK_GZIP_CEILINGS['framework']. The gate says it plainly — "do not widen it just to get a green check" — and a ceiling raise here would bury the regression rather than fix it.
⛔ Not re-running. The build is deterministic; a re-run is not a diagnosis.
⛔ Not widening this PR to carry someone else's byte fix.

This PR stays open and watched. It goes green when main does.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Base branch brought in — ⛔ not a re-run, and the result answers #7399

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. I merged main into this branch. Recording why, because "the head moved and CI re-ran" should never be mistaken for kicking a flaky check.

Why this is a content change, not a re-run

This PR's base was c1fe272ad. main has since moved to 0246d116e, and the move materially changed the thing that was failing:

baseframeworkover by
c1fe272ad — this PR's old base512.8 KB1.1 KB
0246d116e — current main (measured through PR #7471)511.9 KB0.2 KB

⇒ roughly 0.9 KB came back out of the chunk between those two commits. Re-running on the old base would have reproduced 512.8 KB exactly — the build is deterministic. Building against the new base is a different measurement, and it is the one that matters now.

That is also what the standing rule prescribes when a base recovers: bring the base branch in and let CI re-run against it, rather than waiting.

⭐ Why this particular PR's verdict is worth more than its own greenness

This diff touches onlypackages/plugin-timeline/src/ and packages/i18n/src/locales/. The framework chunk is attributed by path — /[\\/]packages[\\/](core|react|types)[\\/]/ — so this PR contributes nothing to it.

⇒ On the new base, this PR's framework figure is main-alone's figure, as directly as CI can give it. So:

⚠️ With the caveat I already had to record on #7399: a docs-only commit moved this chunk ~0.9 KB, so the figure is not a pure function of framework-group content. This reading is strong evidence, not proof. It does not replace the isolation measurement still running.

State of this PR otherwise

Every other check was green before this update — 30 of 31, with Bundle Analysis the single red. ACCEPT stands (5522135254); the by-content verification was done on d2dbdfb8c and the merge does not touch the files it verified. Clause ② no, so this seat lands it once every check is green.

⛔ Still no ceiling raise. ⛔ Still no re-run of the old build.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3180.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-D2L2XDb9.js
StatusFAIL

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 halfVerdict
Aggregate closure ceiling✅ pass
Per-chunk ceilings❌ over its ceiling
Ceiling sensitivity (headroom)✅ pass
Ceiling freshness (checkout vs. base branch)✅ pass

📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

⛔ RETRACTION — the Bundle Analysis failure is this PR's. My standing-down comment was wrong.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Withdrawing 5522177212 ("the failure is not this PR's, and main is over") and the follow-up 5522591077 that reasoned from it.

What I got wrong

I argued that because this diff touches no path matching /packages/(core|react|types)/, it could not contribute to the framework chunk. The chunk is not governed by that regex. Measured module membership: framework also contains all ten @object-ui/i18n locale bundles, which are 78.7% of its bytesframework and infrastructure are tied at priority 80 and framework is listed first, so the i18n subgraph reached via @object-ui/react is absorbed into it.

packages/i18n/src/locales/ is inside the framework chunk. This PR adds a translation key to all ten locale files. Those are its own bytes, in that chunk, and they are what put it over.

Full measurement and controls on #7399 (5522762441) and my retraction of the rest (5522794512). The single reading that settles it — a grep of the emitted chunk, controls firing in the same run:

controls: 'SchemaRenderer' 5 · '_hidden' 3 · 'unscheduled' 10

unscheduled is #7453's locale key, appearing ten times inside assets/framework-*.js. Locale strings ship in this chunk. That is exactly the shape of what this PR adds.

Also retracted, since I said them here: main is not over — it passes at every base (30 B headroom at the tip) — and the rebase I did was therefore not the experiment I described. It changed nothing, correctly: the chunk came back byte-identical.

Where this leaves the PR

The refusal message is required by the ruling this card implements (#7070, 总监批 #28) and has to exist in ten locales. With 30 bytes of headroom on main, it does not fit — the same wall that already stopped #7391 (#7210/#7225) and #7194, both of which are also locale-pack copy. This PR is the third instance of one problem, not a new one.

⇒ ⛔ Not landing this by shrinking ruled copy, and ⛔ not by raising a ceiling. It joins the other two behind the decision on #7399, where the option that actually addresses it — fixing the attribution so framework stops policing translations — is now on the table with measurements attached.

Everything else on this PR stands: the by-content verification, the ablation, the fence check, and the ACCEPT (5522135254). Its other 30 checks are green. Clause ② is still no.

⚠️ For whoever picks this up: the rule I acted on — "this PR touches no framework path, so the bytes aren't mine" — is false in this repo, and I published it twice before measuring. If a PR adds locale keys, it adds framework-chunk bytes.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3180.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-C4NaNsQ-.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

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

Labels

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one - #7467

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-7459-timeline-absent-axis-refusal
Sep 3, 2026
Merged

fix(plugin-timeline,i18n): refuse an undeclared timeline date axis instead of inventing one#7467
os-project-manager merged 3 commits into
mainfrom
claude/issue-7459-timeline-absent-axis-refusal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#7459

Steps ① and ② of the three-step sequence ruled on #7070 (project director seat, 总监批 #28, 2026-09-01, maintainer verbatim 「同意」). Step ③ — the 'created_at' floors on the two plugin faces — stays on #7070 and is not touched here.

The gate: both premises re-measured on the current tree

The card's figures were taken on 2c3cd1b75. Everything was re-derived on 3804c222b before the first edit.

Premise 1 — no absent-date-axis refusal path. REPRODUCES. Zero hits for such a refusal in packages/plugin-timeline. The control fires in the same run and in the same file: the term refus matched 6 lines of ObjectTimeline.tsx (the card said 5 — the file has moved, and the control is live rather than stale). Every one of those 6 is the #6655variant: 'gantt' refusal or its prose; none is about a date axis.

Premise 2 — the internal floor. REPRODUCES, and the line numbers happen to still hold.packages/plugin-timeline/src/ObjectTimeline.tsx:247-249, spelled exactly as reported.

Correction to the card's arithmetic. The card describes "four honest rungs, then a fabricated fifth". The chain reads five declared bindings and then a fabricated sixth: timeline.startDateField, timeline.dateField, mapping.date, flat startDateField, flat dateField. All five were re-checked as asked, and all five are genuine declared bindings — the first two on ListViewTimelineConfig (packages/types/src/objectql.ts), the last three on this component's own props and on its module-local TimelineExtensionSchema. No further fabrication among them.

What changed

① The refusal. An object-bound timeline that declares no date axis renders a diagnostic naming every binding it accepts, instead of a chart. Follows the settled in-repo shape (ObjectGantt's "Gantt configuration required" screen) rather than a second dialect: early return, role="alert", names the fields the author must supply. The list is interpolated from the component's own OBJECT_BOUND_TIMELINE_DATE_BINDINGS — the same drift-proof device OBJECT_BOUND_TIMELINE_VARIANTS already uses one refusal up — so the diagnostic cannot name a vocabulary the resolver no longer has.

② The floor is retired, which is the only thing that makes ① reachable. The tombstone comment deliberately does not quote the removed literal: the card's close condition greps this file for it, and a comment quoting it would answer 1 where the truth is 0. Verified after the final commit: 0 hits for the floor, control term firing at 25 lines in the same file.

Enabling change, declared rather than smuggled: the component's success surface is now named data-testid="timeline-canvas". Every other terminal state of this component already named itself (timeline-unsupported-variant, timeline-missing-date-axis, timeline-error, timeline-loading); the rendered timeline was the one outcome a test could not ask for by name, which is exactly the distinction the ruling turns on.

i18n. One new key, timeline.unconfigured.noDateAxis, in all ten locale packs plus the plugin's own default table (byte-identical to en). The German value carries no typographic quote pair on purpose — de-quote-pairing-3876.test.ts ratchets that count, and it stays at 59.

Blast radius, measured

Nothing in the product starts refusing. Both plugin faces still supply startDateField: … || 'created_at' (plugin-list/src/ListView.tsx:2496, plugin-view/src/ObjectView.tsx:1354 — both re-derived; the card's line numbers were stale). App-shell's timelineViewOptions emits no axis, but routes through ListView, which floors. The refusal is reached only by a view forced directly onto the timeline renderer with nothing declared — which is what the card predicted, and what makes ③ the visible step.

The pin

packages/plugin-timeline/src/ObjectTimeline.absentDateAxisRefusal-7459.test.tsx, 11 cases, the twin of ObjectGantt.unconfiguredRefusal-7070.test.tsx.

  • Refusal is not "renders empty". Two independent canvas markers asserted ABSENTtimeline-canvas and role="list" (the ordered-list rail the real renderer emits, the ol element, which an empty timeline still emits). Neither zero is vacuous: the controls in the same file assert both PRESENT.
  • The pairing. One case hands in records that happen to carry a column named date. Restore the floor and it goes red while rendering a completely convincing timeline off a real column — which "no events" would never have caught. Declared-ness, not existence, is what an axis is.
  • The negative direction. All five declared rungs each render the timeline with its events, in their own spelling.
  • The carve-out. An authored items list is never refused (it carries its own time; no field name is read), which is what keeps the three in-repo catalog fixtures rendering.
  • Ordering. The finding(plugin-timeline): an object-bound timeline with variant: "gantt" throws — calculateDateRange reads a row shape ObjectTimeline never composes #6655 variant refusal still wins for a composed gantt, pinned so it is a decision rather than an accident of placement.
  • The renderer is not stubbed, for the reason timeline-object-bound-gantt-refusal.test.tsx states one file over.

Verification

All figures from runs at the final commit d2dbdfb8c; heavy runs serialized through the shared verify lock, verdict lines read rather than a bare $?.

  • Unionplugin-timeline + i18n + plugin-list + plugin-view: Test Files 175 passed (175) / Tests 2288 passed (2288), VERDICT command-exit 0.
  • Baseline for comparison, taken before any edit: plugin-timeline = 21 files / 276 tests. After: 22 / 287.
  • type-checkplugin-timeline + i18n → exit 0, Scope: 2 of 47 workspace projects, each echoing tsc --noEmit && tsc -p tsconfig.test.json + Done (so the zero-match filter trap did not fire). The new test file is genuinely covered, not excluded: tsc -p tsconfig.test.json --listFiles counts it 1.
  • Gates, each verdict line quoted from the gate's own output, exit code captured before any pipe: check:i18n-keys "every in-scope call-site key resolves against the en pack (2960 keys)"; check:i18n-drift "0 en value(s) changed (1 key(s) added…)"; check:i18n-dead-keys (report) 0; check:control-bytes "OK (scanned 6140 tracked text file(s))"; check:vi-mock-specifiers OK; check:vi-mock-inherit "122 call site(s) on @object-ui/react judged (122 inherit, 0 auto-mocked)"; check:side-effects-array OK; check-changeset-presence "13 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"; check-changeset-no-major "No changeset declares a major bump"; check-changeset-fixed OK; check-changeset-overwrite OK.
  • NOT MEASURED, not red:check:readme-exports and check:eager-closure both stop on a prerequisite — all 314 findings of the former are "type entry ./dist/index.d.ts is not on disk — run pnpm build first", and the latter reports "No eager-closure report … the console was not built". Both need a full-monorepo / console build, which CI does regardless. Recorded as unmeasured rather than as failures.
  • eslint, plain form (never --no-inline-config): exit 0, 0 errors, 29 warnings, all pre-existing in class. Declared narrowing: run on the 14 changed files rather than the repo-wide farm. Evidence it excludes nothing — file count 14 read from --format json; this config enables no type-aware linting (no projectService / parserOptions.project in eslint.config.js), so the diff cannot move an untouched file's verdict; CI runs the full farm regardless. The two react-hooks warnings on ObjectTimeline.tsx were confirmed pre-existing by linting the origin/main copy of the file through --stdin: identical rule counts.

Reverse verification (ablation) — direction predicted, then observed

Implementation committed first, so the restore leg had a real reference. Mutation: put the retired floor back while the refusal stays — the pairing itself.

  • Predicted before the run: 4 RED (the three refusal cases + the message case), 7 GREEN by construction (the five declared-rung controls, the authored-items control, the finding(plugin-timeline): an object-bound timeline with variant: "gantt" throws — calculateDateRange reads a row shape ObjectTimeline never composes #6655 ordering case — all fully declared, so unaffected in both worlds).
  • Observed:Tests 4 failed | 7 passed (11), and the four failures are exactly the four predicted, by name.
  • Mutation proven on disk, not by an editor's exit code: anchor lines 1 → 0, injected lines 0 → 1, the injected hit printed and read; blob 1b4575de…8cea816b…. The anchor was asserted unique first, so a zero-match no-op would have aborted rather than reported a healthy run.
  • Restore proven by state, not by exit code: git hash-object == git rev-parse HEAD:PATH (non-empty and matching), git diff HEAD empty, git status clean. Absolute-path trap … EXIT INT TERM.
  • No dist/ leg exists for this ablation and none is claimed: the pin imports the subject by relative path from source (./ObjectTimeline), so no rebuild sits between the mutation and the measurement.

Clause ②

No, and re-declared against the actual diff rather than inherited from the card. Nothing published moves: no spec key, no zod validator, no exported type. OBJECT_BOUND_TIMELINE_DATE_BINDINGS is module-local (and deliberately not exported — an exported array trips react-refresh/only-export-components, as the sibling constant's comment records). The only new published surface is a data-testid attribute and one i18n key. startDateField's internal type widens to string | undefined inside the component; it is a local, not a prop.

Changeset: .changeset/7459-timeline-absent-date-axis-refusal.md, minor for @object-ui/plugin-timeline and @object-ui/i18n — matching the grade #7070 used for the identical class of change. Graded here and confirmed by the gate's own verdict line: check-changeset-no-major → "✅ No changeset declares a major bump".

Not in this card

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC


Generated by Claude Code

…stead of inventing one
Steps (1) and (2) of the three-step sequence ruled on objectui#7070
(maintainer, 2026-09-01). `ObjectTimeline` resolved its date axis through five
declared bindings and then closed the chain with a bare literal field name
nobody has ever declared, so a name always resolved: every record read a key
its object does not carry and bucketed into "No date".
The floor is retired and an absent-axis refusal added, together — a refusal
added while the floor stood would have been unreachable, and the floor retired
without a refusal produces the silent "No date" outcome the ruling rejects.
The refusal names every binding it accepts, in the shape objectui#7070 settled
one renderer over.
The component's success surface is now named (`timeline-canvas`) so a refusal
can be distinguished from a rendered-but-empty timeline, which is the
distinction the ruling turns on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3180.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-D2L2XDb9.js
StatusFAIL

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 halfVerdict
Aggregate closure ceiling✅ pass
Per-chunk ceilings❌ over its ceiling
Ceiling sensitivity (headroom)✅ pass
Ceiling freshness (checkout vs. base branch)✅ pass

📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT — verified on the branch by content, ⛔ not against the report

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Branch head d2dbdfb8c, base c1fe272ad.

⭐ My brief was wrong, and the dev measured it rather than following it

My dispatch — and the card — both say "four honest rungs, then a fabricated fifth." The chain actually reads five declared bindings and a fabricated sixth: timeline.startDateField, timeline.dateField, mapping.date, flat startDateField, flat dateField, then 'date'.

Off by one, and it was mine. The dev re-derived the chain instead of trusting the count, then checked the other five and reported that all are genuine declared bindings — no further fabrication — without extending scope to "fix" anything. That is exactly the shape the brief asked for and the opposite of what an off-by-one usually produces.

The readings

probeorigin/mainbranch
?? 'date' in ObjectTimeline.tsxlive at :2490
⭐ CONTROL startDateField, same file1218
OBJECT_BOUND_TIMELINE_DATE_BINDINGS:68
refusal interpolating that list:574-575
data-testid="timeline-canvas":642
pin ObjectTimeline.absentDateAxisRefusal-7459.test.tsxabsentexists
locale packs carrying noDateAxis010

The tombstone here is deliberately spelled so it does NOT quote the removed literal. The dev says why: a comment quoting it would make the close-condition scan answer 1 where the truth is 0. That is precisely the trap that has fired on me three times today, anticipated and designed out rather than documented after the fact.

⛔ The step-③ fence held, checked rather than assumed

created_at at the two plugin faces is identical on both sidesListView.tsx 4, plugin-view/ObjectView.tsx 1. That was the edit most likely to be made on momentum: the house posture now on record makes those literals look obviously wrong, and they are, but they are step ③ of the #7070 ruling and blocked on this PR landing. Untouched.

Two judgement calls I want on the record

1. The named success surface is an enabling change, declared not smuggled.data-testid="timeline-canvas" is new. Without a name for the rendered timeline, a pin cannot separate "refused" from "rendered an empty timeline" — and that distinction is the entire point of the ruling, since the failure mode it rejects is a silently empty "No date" render. Every other terminal state of this component already named itself; the success state was the gap. Correct to add, and correct to call out.

2. The de value carries no typographic quote pair on purpose.de-quote-pairing-3876.test.ts ratchets paired-span counts; a quoted German string would have pushed it off 59 and invited a ceiling bump. The dev wrote the value to fit the existing ratchet instead. ⛔ Never raise a gate ceiling — met here without being told.

The ablation tests the pairing, which is the right subject

Mutation = put the floor back while the refusal stays. That is not "undo the change" — it is the specific claim this card rests on, that a refusal behind a floor is unreachable. Predicted 4 RED / 7 GREEN by construction, and the seven were named in advance as fully-declared cases unaffected in both worlds. Observed 4 failed / 7 passed, the four failures exactly the four predicted, by name.

That asymmetry is the acceptance criterion for this class — it separates "stopped inventing" from "refuses everything" — and the blast-radius measurement closes it: nothing in the product starts refusing, because both plugin faces still supply created_at and app-shell routes through ListView. The refusal is reached only by a view forced directly onto the renderer with nothing declared.

Landing

Clause ② NO, re-declared against the diff rather than inherited from my brief: no spec key, no zod validator, no exported type. OBJECT_BOUND_TIMELINE_DATE_BINDINGS is module-local and deliberately unexported (an export trips react-refresh/only-export-components). The only new published surface is one data-testid and one i18n key. ⇒ this seat lands it.

Two gates are declared NOT MEASURED rather than green — check:readme-exports and check:eager-closure, both needing a build CI does anyway. Reported as missing prerequisites, not passes. Correct.

CI is still running several jobs. ⛔ Not enqueueing until every check is green — the bar is every check, not the required subset.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

⛔ Standing down on Bundle Analysis — the failure is not this PR's, and main is over. ⚠️ Shared damage.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Not enqueueing while red; not raising any ceiling; no re-run (the build is deterministic, so a second run answers the same).

What failed

From the gate's own output, ⛔ not the summary comment:

✅ Console eager closure is 3180.1 KB gzipped across 48 of 516 chunks (budget: 3191.4 KB, headroom: 11.3 KB).
❌ 1 eager chunk is over its per-chunk budget:
✅ vendor-objectstack 926.1 KB / 944.3 KB ceiling (headroom 18.2 KB)
❌ framework 512.8 KB / 511.7 KB ceiling (OVER by 1.1 KB)
✅ ui-components 387.8 KB / 389.6 KB ceiling (headroom 1.9 KB)

The aggregate passes with 11.3 KB spare; sensitivity and freshness both pass. Only the per-chunk half objected — which is precisely the case that check was added for (#5490): one chunk growing while others shrink is invisible to a single total.

Why it is not this PR's — structural, not a plea

apps/console/vite.config.ts:742 attributes that chunk by path:

{name: 'framework',test: /[\\/]packages[\\/](core|react|types)[\\/]/,priority: 80}

This PR's complete file list is .changeset/, ten packages/i18n/src/locales/*.ts, and three files under packages/plugin-timeline/src/. Not one path matches core|react|types. So this diff cannot contribute a byte to the framework chunk, and the 512.8 KB it measured is main's own framework chunk.

main is over the per-chunk ceiling. This is not a regression this PR introduced; it is a regression this PR is the first to measure.

⚠️ Which means every PR in this repo is now red on this check

That makes it shared damage rather than one PR's problem, and it is being escalated rather than worked around here.

⭐ The likely cause is a PR I landed, and I am naming it

Diffing the framework-group paths between the last passing measurement (3804c222b, PR #7464 at 07:07) and this PR's base (c1fe272ad), exactly two files entered that group:

packages/react/src/__tests__/SchemaRenderer.hiddenVisibleSynonymy.test.tsx | +84
packages/types/src/base.ts | +33 −3

Both are PR #7457 (#7088) — which I reviewed, accepted and enqueued, on the finding that it was comment-only. That finding was correct about behaviour: the declarations are untouched and no accept set moved. ⛔ I did not weigh it for bytes, and packages/types sits inside a path-attributed chunk that had roughly 177 bytes of headroom — a fact I had measured earlier this same shift while analysing #7442, and did not apply here.

⚠️ Stated as the open question it is, ⛔ not as a conclusion: #7457's ownBundle Analysis passed pre-merge, on a base that predated #7453. A test file under __tests__ should not reach the console graph at all, and JSDoc should not survive minification — so how 33 lines of comment become ~1.1 KB of shipped chunk is not established, and I am not going to guess it. What is established is the path proof above and the fact that nothing else entered that group.

What I am not doing

⛔ Not raising PER_CHUNK_GZIP_CEILINGS['framework']. The gate says it plainly — "do not widen it just to get a green check" — and a ceiling raise here would bury the regression rather than fix it.
⛔ Not re-running. The build is deterministic; a re-run is not a diagnosis.
⛔ Not widening this PR to carry someone else's byte fix.

This PR stays open and watched. It goes green when main does.


Generated by Claude Code

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

Base branch brought in — ⛔ not a re-run, and the result answers #7399

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. I merged main into this branch. Recording why, because "the head moved and CI re-ran" should never be mistaken for kicking a flaky check.

Why this is a content change, not a re-run

This PR's base was c1fe272ad. main has since moved to 0246d116e, and the move materially changed the thing that was failing:

baseframeworkover by
c1fe272ad — this PR's old base512.8 KB1.1 KB
0246d116e — current main (measured through PR #7471)511.9 KB0.2 KB

⇒ roughly 0.9 KB came back out of the chunk between those two commits. Re-running on the old base would have reproduced 512.8 KB exactly — the build is deterministic. Building against the new base is a different measurement, and it is the one that matters now.

That is also what the standing rule prescribes when a base recovers: bring the base branch in and let CI re-run against it, rather than waiting.

⭐ Why this particular PR's verdict is worth more than its own greenness

This diff touches onlypackages/plugin-timeline/src/ and packages/i18n/src/locales/. The framework chunk is attributed by path — /[\\/]packages[\\/](core|react|types)[\\/]/ — so this PR contributes nothing to it.

⇒ On the new base, this PR's framework figure is main-alone's figure, as directly as CI can give it. So:

⚠️ With the caveat I already had to record on #7399: a docs-only commit moved this chunk ~0.9 KB, so the figure is not a pure function of framework-group content. This reading is strong evidence, not proof. It does not replace the isolation measurement still running.

State of this PR otherwise

Every other check was green before this update — 30 of 31, with Bundle Analysis the single red. ACCEPT stands (5522135254); the by-content verification was done on d2dbdfb8c and the merge does not touch the files it verified. Clause ② no, so this seat lands it once every check is green.

⛔ Still no ceiling raise. ⛔ Still no re-run of the old build.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3180.1 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-D2L2XDb9.js
StatusFAIL

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 halfVerdict
Aggregate closure ceiling✅ pass
Per-chunk ceilings❌ over its ceiling
Ceiling sensitivity (headroom)✅ pass
Ceiling freshness (checkout vs. base branch)✅ pass

📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.27KB64.20KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

⛔ RETRACTION — the Bundle Analysis failure is this PR's. My standing-down comment was wrong.

domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. Withdrawing 5522177212 ("the failure is not this PR's, and main is over") and the follow-up 5522591077 that reasoned from it.

What I got wrong

I argued that because this diff touches no path matching /packages/(core|react|types)/, it could not contribute to the framework chunk. The chunk is not governed by that regex. Measured module membership: framework also contains all ten @object-ui/i18n locale bundles, which are 78.7% of its bytesframework and infrastructure are tied at priority 80 and framework is listed first, so the i18n subgraph reached via @object-ui/react is absorbed into it.

packages/i18n/src/locales/ is inside the framework chunk. This PR adds a translation key to all ten locale files. Those are its own bytes, in that chunk, and they are what put it over.

Full measurement and controls on #7399 (5522762441) and my retraction of the rest (5522794512). The single reading that settles it — a grep of the emitted chunk, controls firing in the same run:

controls: 'SchemaRenderer' 5 · '_hidden' 3 · 'unscheduled' 10

unscheduled is #7453's locale key, appearing ten times inside assets/framework-*.js. Locale strings ship in this chunk. That is exactly the shape of what this PR adds.

Also retracted, since I said them here: main is not over — it passes at every base (30 B headroom at the tip) — and the rebase I did was therefore not the experiment I described. It changed nothing, correctly: the chunk came back byte-identical.

Where this leaves the PR

The refusal message is required by the ruling this card implements (#7070, 总监批 #28) and has to exist in ten locales. With 30 bytes of headroom on main, it does not fit — the same wall that already stopped #7391 (#7210/#7225) and #7194, both of which are also locale-pack copy. This PR is the third instance of one problem, not a new one.

⇒ ⛔ Not landing this by shrinking ruled copy, and ⛔ not by raising a ceiling. It joins the other two behind the decision on #7399, where the option that actually addresses it — fixing the attribution so framework stops policing translations — is now on the table with measurements attached.

Everything else on this PR stands: the by-content verification, the ablation, the fence check, and the ACCEPT (5522135254). Its other 30 checks are green. Clause ② is still no.

⚠️ For whoever picks this up: the rule I acted on — "this PR touches no framework path, so the bytes aren't mine" — is false in this repo, and I published it twice before measuring. If a PR adds locale keys, it adds framework-chunk bytes.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3180.7 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-C4NaNsQ-.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)516.24KB117.82KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.08KB61.71KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.27KB13.32KB
plugin-charts (index.js)70.31KB19.55KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.79KB34.62KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.19KB64.18KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.18KB40.94KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.53KB27.66KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

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

Labels

Projects

None yet

2 participants

@os-project-manager@claude