Skip to content

fix(plugin-timeline): judge a gantt Date by its [[DateValue]] slot, not its prototype - #7037

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7027-gantt-date-brand-gate
Aug 31, 2026
Merged

fix(plugin-timeline): judge a gantt Date by its [[DateValue]] slot, not its prototype#7037
os-sam merged 1 commit into
mainfrom
claude/issue-7027-gantt-date-brand-gate

Conversation

@claude

@claude claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes #7027

The decision: route 1, with a brand test that is neither of the two the card proposed

The card offered two routes and triage judged route 2 (narrow the stated claim) the likely answer, on the ground that route 1 "is not free". I measured both proposed route-1 spellings, and both are indeed wrong — but the conclusion that follows is not route 2. It is that route 1 has a third implementation the card did not consider, and that implementation is free.

What ships: one module-local predicate, used at both places in renderer.tsx that ask "is this a Date".

const isDate = (value: unknown): value is Date => {
  try {
    Date.prototype.getTime.call(value as Date);
    return true;
  } catch {
    return false;
  }
};

It invokes the builtin with .call. That reads the receiver's [[DateValue]] internal slot and nothing else: no property is fetched off value, so no author getter runs, no Symbol.toStringTag is consulted, no proxy trap fires, and a subclass cannot hijack it. The slot is not observable any other way — the language exposes that bit only by throwing when it is absent — so the catch is the read, not error handling draped over a fallible operation.

Why not route 2

Route 2 asks the docblock to state which inputs the totality claim covers and why the rest cannot arrive. That is a claim about reachability, which is a global property of the whole repo — every adapter, every test helper, every deserializer anyone writes later. Route 1's property is local: "this predicate cannot throw", checkable in one file forever and pinnable by a test. Route 2 replaces a false local claim with a true-today global one, which is a strictly harder invariant to keep and exactly the shape that has been falsified three times on this path already.

There is also a mechanical reason route 2 could not have carried the deliverable: under route 2 Object.create(Date.prototype) still crashes, so the pinned adversarial set could not assert a named diagnostic for it.

Why not either brand test the card suggested — both measured, both refuted, both pinned red

suggested spelling the input that refutes it why
Object.prototype.toString.call(value) === '[object Date]' { get [Symbol.toStringTag]() { throw } } Object.prototype.toString performs Get(O, @@toStringTag) unconditionally (ES2015 19.1.3.6 step 16), even once the builtin tag is decided. Trades an impostor crash for a getter crash — the same input #6907 measured crashing the speller.
Number.isFinite(value.getTime()) class X extends Date { getTime() { throw } } Calls the author's getTime. That subclass is a real Date — super() gave it the slot, so #6781's accept set contains it and the chart must draw — and this spelling would refuse it by dying on it. Worse than the bug: it breaks a value that is not even wrong.

Both rows are in the pinned suite. A later "simplification" into either spelling fails there rather than in a fifth card.

The four-facet analysis

Real business need (实际业务需求). Low, and stated honestly: no authored document reaches this. I verified the card's reachability analysis rather than accepting it (see below) and it holds. The need this serves is not an author's — it is the next engineer's. #6905's totality argument is written out loud in the code and is load-bearing for the order of two operations; a reader who trusts it and reorders them ships a crash. The cost of being right here is 8 lines of code.

Long-term soundness for this project (长远合理性). This is the facet that decides it. The path now carries a local, checkable property instead of a global, unverifiable one, and the property is exercised rather than asserted. Contract-first is respected: nothing became tolerant, no consumer gained a fallback, and the producer-side contract (#6781's accept set) is untouched — the change makes an existing refusal arrive instead of crashing on the way.

Making AI-written code hard to get wrong (防 AI 写错). The strongest argument for route 1 over route 2. A narrowed prose claim is text that a later agent can silently re-widen, and nothing goes red. A total predicate plus a pinned adversarial set is structural: the two obvious "cleanups" of isDate each have a dedicated failing test with the reason written on it. The docblock now also says, in the imperative, what to do with the next gap — add the row to that file, do not write a fifth sentence.

Startup scope discipline (创业阶段不扩散). Net +8 lines of code, one module-local helper, zero exports, no new capability, no spec key, no new i18n key, no locale packs opened, no accept-set adjudication. The suite is the bulk of the diff and it is the deliverable the card asked for. I deliberately did not fix the one remaining non-total operation I found on this path (filed as #7036 instead) precisely on this facet — see below.

The measurement: crash reproduced on base first, then closed

The pinned suite was written against unmodified 7fc5c3c12 and run there before renderer.tsx was touched. Three rows failed, each an uncaught TypeError mid-render:

FAIL ... > names Object.create(Date.prototype)
TypeError: Method Date.prototype.toString called on incompatible receiver [object Object]
 ❯ isUnusable packages/plugin-timeline/src/renderer.tsx:648:45
 ❯ findUnusableGanttDate packages/plugin-timeline/src/renderer.tsx:655:13
 ❯ TimelineRenderer packages/plugin-timeline/src/renderer.tsx:982:28

FAIL ... > names the same impostor behind a Proxy that throws on every get
Error: get trap: no property of this value may be read
 ❯ isUnusable packages/plugin-timeline/src/renderer.tsx:648:45

FAIL ... > names a Proxy whose getPrototypeOf trap throws
Error: getPrototypeOf trap: this value has no readable prototype
 ❯ isGanttDateType packages/plugin-timeline/src/renderer.tsx:624:3

Test Files  1 failed (1)   Tests  3 failed | 6 passed (9)

The third row is a finding of its own: instanceof is not total on its own terms — it walks [[GetPrototypeOf]], so a proxy that traps it crashes inside the operator. That was a live crash on main and the card did not know about it.

After the fix, at e44f8ad82: Test Files 16 passed (16) · Tests 204 passed (204) across the whole plugin-timeline suite, so #6759 / #6770 / #6781 / #6907's pinned spellings and the inverted-range message are all still green, unchanged.

The pinned adversarial input set

packages/plugin-timeline/src/__tests__/timeline-gantt-date-brand-7027.test.tsx. Every input reaches exactly one of ACCEPTED (it is a real Date, the chart draws) or NAMED (refused through the single existing diagnostic, with the authored path and a spelling) — and never THREW.

⚠️ Note that not every row is a refusal, and forcing them all to be one would change the accept set. The real Date and the hijacked-getTime subclass are controls that must be accepted.

input base 7fc5c3c12 at e44f8ad82
Object.create(Date.prototype) THREW in isUnusable NAMED — path + is an object
that impostor behind a Proxy throwing on every get THREW (get trap, via ToPrimitive) NAMED — path + is an object
Proxy with a throwing getPrototypeOf trap THREW inside instanceof NAMED — path + is an object
Proxy throwing on every get NAMED NAMED (regression pin: the new gate must not start reading properties)
class X extends Date { getTime() { throw } } ACCEPTED, chart drew ACCEPTED, chart drew — 1 bar, axis Jan/Feb/Mar 2024
{ get [Symbol.toStringTag]() { throw } } NAMED NAMED (control against the brand test)
new Date('2024-01-01') ACCEPTED ACCEPTED
new Date(NaN) NAMED Invalid Date NAMED Invalid Date

The accept set does not move — and the one edit outside the gate that was forced

Accept set: unchanged. isDate is strictly narrower than instanceof Date over the values that reach it, and everything it newly refuses is a value no authored document can carry. new Date(NaN) owns its slot, so it still passes the type gate and is still refused one step later by the parse check, keeping its Invalid Date spelling — pinned. A compact accept/refuse control row re-asserts string / finite number / real Date accepted and null / undefined / boolean refused, exactly where #6781 left them.

⚠️ Deviation to review: spellGanttDateValue's branch selector had to move with the gate. The dispatch said that helper is finished and must not be touched. It is finished as a set of branches and I changed none of them — but its Date branch was selected by value instanceof Date, and that selector has the identical defect. With the gate fixed and the selector left alone, the impostor is refused by the gate and then crashes inside Date.prototype.toString.call while being named: the crash relocates one function downstream instead of closing, which is precisely the shape #6907 recorded when it found #6905 had moved the crash rather than removed it.

That is not an argument, it is an ablation. Committed the fix first, then reverted only the speller's selector back to instanceof Date (mutation confirmed on disk by grep counts in both directions plus a blob-hash difference against HEAD), keeping the gate fixed:

MUTATION CONFIRMED ON DISK (speller reverted to instanceof; gate keeps isDate)
 × names Object.create(Date.prototype) ...
 × names the same impostor behind a Proxy that throws on every get
 × names a Proxy whose getPrototypeOf trap throws
TypeError: Method Date.prototype.toString called on incompatible receiver [object Object]
 ❯ spellGanttDateValue packages/plugin-timeline/src/renderer.tsx:496:61
Test Files  1 failed (1)   Tests  3 failed | 6 passed (9)

Same three tests, same class of crash, new line number. The restore leg was proved by state, not by an exit code: the file's blob hash is byte-identical to HEAD:packages/plugin-timeline/src/renderer.tsx (648f7784...) and git diff HEAD is empty.

No spelling changes for any value that reaches the speller today. The two predicates disagree only on slot-less impostors (which crashed before, so nothing reached the speller) and on cross-realm Dates (which no code path here produces). The full 204-test suite is the control.

Reachability: verified, not accepted — and it holds

The card's analysis is the crux, so I checked it on 7fc5c3c12 rather than inheriting it:

  • No construction site. Object.create( appears nowhere in packages/*/src or apps/*/src outside comments; every hit is prose about __proto__ key hazards. No setPrototypeOf in source at all.
  • No prototype-reconstructing deserializer. No superjson, devalue, flatted, no JSON.parse reviver, no date-revival helper anywhere in source. The only structural cloner is structuredClone, which produces a real Date (its own slot, receiving realm) — not an impostor.
  • JSON cannot spell it. ObjectUI metadata is JSON, and JSON has no syntax for a prototype.

No reachable path found. So the p3 grade stands and the crash is a latent-invariant repair, not a live author-facing bug — which is why the argument above leans on the long-term and anti-AI-error facets rather than on business need.

The pattern this card sits in, and why the suite is the real deliverable

  1. finding(plugin-timeline): two more unusable gantt date ranges — a malformed date still throws, and an inverted author-pinned range silently draws a negative-width bar on no axis #6759 declared spellGanttDateValue total by construction.
  2. fix(plugin-timeline): judge gantt dates by TYPE — string, finite number, or Date #6905 made that property load-bearing by ordering the type gate first.
  3. finding(plugin-timeline): the unusable-gantt-date diagnostic spells an array or an object either BLANKLY or as a text that looks like a valid date #6907 (PR fix(plugin-timeline): spell a refused gantt date by a rule, not by String #7026) measured that the speller was not total — three inputs crashed the render.
  4. This card: the gate has a gap of the same kind, one function upstream.

Four totality claims, three falsified by the next card's measurement. A fifth prose assertion continues the sequence; an exercised input set ends it. Both docblocks now say so in the imperative, and the pinned suite is where the next gap goes.

Out of scope, filed not fixed

#7036spellGanttDateValue's Array.isArray is now the last non-total operation on this path: a revoked Proxy throws TypeError: Cannot perform 'IsArray' on a proxy that has been revoked. Not a regression (it crashed inside instanceof before this PR and inside Array.isArray after — same render, different line), not reachable, and #6907 chose Array.isArray deliberately with a measured argument, so the correct replacement shape is an adjudication rather than a mechanical fix. Recorded rather than swept in. #7036 is not addressed here.

Checks

Exit codes captured before any pipe; each verdict quoted from the check's own output.

check command result
unit (affected package) pnpm vitest run packages/plugin-timeline/src/__tests__/ Test Files 16 passed (16) · Tests 204 passed (204) at e44f8ad82
crash repro (base) same, on unmodified 7fc5c3c12 Tests 3 failed | 6 passed (9) — the defect
ablation (forced speller edit) speller selector reverted, gate kept Tests 3 failed | 6 passed (9), crash at renderer.tsx:496
typecheck pnpm --filter @object-ui/plugin-timeline run type-check exit 0 (tsc --noEmit && tsc -p tsconfig.test.json), after building the dependency closure
typecheck really covers the new test tsc -p tsconfig.test.json --listFiles the new test file is in the program (count 1, not 0)
eslint pnpm exec eslint packages/plugin-timeline 26 files, 0 errors, 97 pre-existing warnings
check:control-bytes pnpm check:control-bytes OK (scanned 5837 tracked text file(s))
check:vi-mock-specifiers pnpm check:vi-mock-specifiers OK (509 carry a mock ...)
check:vi-mock-inherit pnpm check:vi-mock-inherit OK (112 call site(s) on @object-ui/react judged, 112 inherit, 0 auto-mocked)
check:shell-escape-residue pnpm check:shell-escape-residue OK (0 occurrence(s) outside a fence)
changeset:check pnpm changeset:check All workspace packages are in the changeset fixed group · No changeset declares a major bump

Declared narrowing, with its evidence. Repo-wide pnpm lint was not run locally; the affected package was, and the narrowing is measured rather than assumed: (1) the population came from eslint's own config resolution, not from my guess about which files count — pnpm exec eslint packages/plugin-timeline resolved the set itself; (2) the count is read from --format json: 26 files, 0 errors; (3) type-aware linting is not enabled (eslint.config.js declares no projectService and no parserOptions.project), so a file's verdict depends only on its own bytes plus the shared config — and this diff touches 3 files, none of them eslint.config.js, so no untouched file's verdict can move. CI runs the full farm regardless.

Not run and not owed: no consumer sweep — isDate is module-local and unexported, no package boundary or .d.ts changed. No i18n gates — no t() call site or key was added or removed; the refusal reuses timeline.gantt.unusableRange.malformedDate and the ten locale packs are untouched.

Diff

  • packages/plugin-timeline/src/renderer.tsxisDate plus its docblock; both instanceof Date call sites swapped; three docblock passages corrected where they asserted a totality that was not true.
  • packages/plugin-timeline/src/__tests__/timeline-gantt-date-brand-7027.test.tsx — new, the pinned adversarial set.
  • .changeset/7027-gantt-date-brand-gate.md — patch to @object-ui/plugin-timeline.

Draft on purpose — not marking ready, not merging.


Generated by Claude Code

…ot its prototype

`isGanttDateType` used `value instanceof Date`, which answers "does this
inherit from `Date.prototype`" rather than "is this a Date". A slot-less
impostor passed the gate, reached `new Date(value)`, ran ToPrimitive and threw
uncaught mid-render — a blank screen where objectui#6781's named diagnostic
belongs.

Both Date tests in this file now ask a total brand test built on the builtin
`Date.prototype.getTime` invoked with `.call`: it reads the receiver's
`[[DateValue]]` slot and nothing else, so no author getter runs, no
`Symbol.toStringTag` is consulted and no proxy trap fires.

The speller's branch selector had to move with the gate: with the gate fixed
and `spellGanttDateValue` still selecting on `instanceof Date`, the impostor
is refused and then crashes inside `Date.prototype.toString.call` while being
named — the crash relocates instead of closing. Measured both ways; no spelling
changes for any value that reaches the speller today.

Adds a pinned adversarial input set so the totality is exercised rather than
asserted for a fifth time.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 48 chunks) 3149.2 KB 3191.4 KB
Main entry chunk (gzip) 143.6 KB 350 KB
Entry file index-D23GN0Ha.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 14.51KB 5.35KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 512.30KB 116.52KB
core (index.js) 5.30KB 2.13KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 177.67KB 49.45KB
fields (index.js) 243.64KB 61.64KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.44KB 1.39KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 33.40KB 8.71KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.95KB 10.97KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.55KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useResponsiveConfig.js) 1.37KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.92KB 12.93KB
plugin-charts (index.js) 64.68KB 18.35KB
plugin-chatbot (index.js) 190.53KB 45.18KB
plugin-dashboard (index.js) 133.48KB 34.51KB
plugin-designer (index.js) 212.87KB 43.19KB
plugin-detail (index.js) 247.30KB 63.18KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 133.11KB 32.61KB
plugin-gantt (index.js) 165.21KB 40.37KB
plugin-grid (index.js) 202.07KB 54.61KB
plugin-kanban (index.js) 53.14KB 14.64KB
plugin-list (index.js) 113.15KB 27.59KB
plugin-map (index.js) 20.20KB 6.66KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 43.51KB 11.94KB
plugin-timeline (index.js) 29.34KB 8.47KB
plugin-tree (index.js) 8.98KB 3.08KB
plugin-view (index.js) 85.79KB 21.10KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 3.11KB 1.48KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.72KB 2.24KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@os-sam
os-sam marked this pull request as ready for review August 31, 2026 15:17
@os-sam
os-sam added this pull request to the merge queue Aug 31, 2026
Merged via the queue into main with commit c17446a Aug 31, 2026
32 checks passed
@os-sam
os-sam deleted the claude/issue-7027-gantt-date-brand-gate branch August 31, 2026 15:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(plugin-timeline): a slot-less Date impostor passes the gantt type gate and then crashes new Date mid-render

1 participant