Skip to content

fix(types): ChartDataSeriesSchema declares the six series keys the renderer reads (objectui#7546) - #7684

Merged
os-sam merged 5 commits into
mainfrom
claude/issue-7546-chart-series-silent-strip
Sep 5, 2026
Merged

fix(types): ChartDataSeriesSchema declares the six series keys the renderer reads (objectui#7546)#7684
os-sam merged 5 commits into
mainfrom
claude/issue-7546-chart-series-silent-strip

Conversation

@os-sam

@os-sam os-sam commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Fixes #7546

What

ChartDataSeriesSchema is a non-strict z.object — not .passthrough() like BaseSchema — so an undeclared key is stripped in silence while safeParse reports success. Six keys that normalizeSeries reads (normalizeChartSchema.ts:242-255) and AdvancedChartImpl does real work with were undeclared: label (legend name, :1364), variant (comparison overlay, :2010-2033), opacity / dashArray (stroke and fill, :94-96), stack (stackId, :1893), yAxis (axis binding, :1887). Both faces — the TS twin ChartDataSeries and the Zod mirror — now declare them, each with its own measured value domain. No reader changed. The object stays non-strict; a truly undeclared key is still stripped, as chart-inline-data-retired.test.ts pins.

chartType, the seventh key the prior review found, is measured not live on this authoring face and is deliberately not declared — reported below with its evidence, per the ruling (comment 5548523375: declare what is live, report the rest, retire nothing here). Its carrier is objectui#7694 (see "Out of scope").

Remediation after the second contract review (comment 5550238043, REFUSE) — head 5f7e032a8

The one blocking item: the "Per-series options" example taught dashArray on a chartType: 'bar' chart, where it reaches nothing. Re-measured before rewriting — neither the verdict's reading (comparisonStyle sets strokeDasharray to undefined for bar) nor the dispatcher's taken on trust — through the real ChartRenderer under happy-dom (Recharts' ResponsiveContainer sized as in ChartRenderer.specSeries.test.tsx), on this branch with main bc640ec56 merged. Predictions stated first; a lit control on every probe; readings persisted to disk because vitest hides passing tests' stdout.

probe schema predicted observed
P1 the OLD example verbatim — bar, comparison { dashArray: '4 4', opacity: 0.6 } fill-opacity="0.6" on the comparison rects (lit), zero stroke-dasharray 2 recharts-rectangle paths at 0.6; stroke-dasharray hits: 0
P2 the NEW example — area, comparison { dashArray: '8 4', opacity: 0.6 } stroke-dasharray="8 4", stroke-opacity="0.6", fill-opacity="0.6" on the comparison mark; primaries untouched 8 4 on both the comparison's area-area and area-curve paths, 0.6 stroke and fill on the same two; the four primary paths keep fill-opacity="1", no dash, and the default 4 4 appears nowhere
P2b area, two series with stack: 'money' vs without stacked: the cost area's lower edge IS the revenue area's upper edge; control: unstacked both sit on the baseline stacked cost path …L475,180L53,145Z against revenue M53,145L475,180…; unstacked both end on 285
P3 line, comparison { dashArray: '2 6', opacity: 0.6 } dash 2 6 + stroke-opacity 0.6 on the line curve; no fill-opacity from it exactly that
P4 scatter, one comparison series with both keys fill-opacity="0.6" on every symbol, zero stroke-dasharray 3 recharts-symbols at 0.6; dash hits 0
P5a/b comparison line with NO dashArray; comparison bar with NO opacity the family defaults 4 4 / 0.5 and 0.4 (instrument sees the attributes) 4 4 + 0.5; 0.4
P5c PRIMARY series with opacity: 0.6, dashArray: '2 6' on bar and on line nothing reaches the DOM (objectui#7698) dash: [] fillOp: [] strokeOp: [] on both
P6 bar chart, comparison series with per-series type: 'line' and dashArray: '2 6' the dash lands — the condition is the MARK, not chartType 2 6 + 0.6 on the line curve

9/9 on the final run. One control miss, reported: P2b's first control predicted the bottom (revenue) area path unchanged by stacking — wrong, stacking rescales the y-domain so both paths move (M53,5…M53,145…); the readings themselves already showed the stacking (the lower edge coincidence above), so the control was recast geometrically and the probe re-run: 9/9.

The mechanism is one step lower than the verdict puts it (renderer blob 63a5e34c9, identical on main and here — no reader changed): comparisonStyle (AdvancedChartImpl.tsx:96) returns the AUTHORED dashArray for every family — the undefined at :96 is only the fallback half. What drops it is the mark: Scatter :1832, combo Bar :1911 and Bar :2037 pass fillOpacity only; Line :1898 / :2048 pass strokeOpacity + strokeDasharray; Area :1905 / :2056 pass all three. So the two keys split: opacity is live on every family under comparison (fill on bar/scatter, stroke on line, both on area); dashArray — and strokeOpacity with it — only on a line/area mark.

What changed (wording plus one example's chartType; no contract, reader or changeset-level change):

  1. plugin-charts.mdx — the example moves to chartType: 'area', the one family where stack, variant, dashArray and opacity are ALL live (P2 + P2b); dropping dashArray from the bar example was the alternative, rejected because the row would then have no live demonstration at all. The dash is '8 4', not '4 4': on a comparison line/area '4 4' IS the default, so an example writing it would be inert in a second, quieter way. A one-line lead-in says why area. The dashArray row states the full condition — comparison and a line/area mark (chart chartType or per-series type), bar/scatter take opacity only; the opacity row now says every mark family, fill vs stroke per family (it neither over- nor understated before, it was silent on the split).
  2. data-display.ts — the dashArray docblock carries the full condition with the mark sites; the opacity docblock says it reaches every family. The Zod .describe strings are deliberately untouched: on all six keys they state the value domain, not the renderer's current coverage, and the coverage lives in the rows and docblocks.
  3. Changeset (non-blocking item) — the ⚠️ refusal list now names variant: 'current' as refused-but-honoured: the normalizer keeps it and draws it as primary; it is refused because it is not a member of the published pair.
  4. Not taken, recorded: .min(0).max(1) for mirror/spec range parity on opacity — the maintainer's follow-up, not this card's.

main bc640ec56 merged again (merge commit 51f2a9634; #7691, #7649 and #7662 landed after the previous merge) — clean, no conflicts; the lockfile did NOT move — corrected 2026-09-05 by the domain:ui PM seat after the round-3 contract review measured it: pnpm-lock.yaml is blob df8fbcd77 at 6eebc54b6, bc640ec56, 493b52eb3 and 5f7e032a8 alike, 0 commits touched it in the merge window (lit control: registry.ts moved once in the same window), and its last movement on main was 2f61238b9 (#7670), before this PR's original base. The re-install was a no-op in the worktree; git diff --stat origin/main..HEAD is still exactly this PR's six files.

Remediation after the contract review (comment 5548861339, REFUSE) — head 493b52eb3

  1. variant narrowed to the spec pair on both facesz.enum(['primary', 'comparison']) and variant?: 'primary' | 'comparison'. Re-measured before rewriting, on origin/main 6eebc54b6 (neither the PM's summary nor the reviewer's numbers taken on trust): the only writers of 'current' are the two internal-shape producers ObjectChart.tsx:852 (s.variant || 'current') and DatasetWidget.tsx:1450 (s.variant ?? 'current') plus their four dataKey-shaped test assertions; docs 0, fixtures 0, designer 0 — git grep over content/**, examples/**, apps/** in both the TS and the JSON spelling, controls lit (variant: 'comparison' 1 hit in content/** on this branch; the same query over apps/** finds the button-action variant: 'primary' sites). The installed @objectstack/spec@17.2.0 declares variant: z.enum(["primary", "comparison"]) (dist/ui/index.js:2395). Core's own authored-series reader seriesPresentation keeps the pair and drops current (chart-presentation.ts:125). So current is the renderer's internal default and nothing an author writes — the same reading that excluded chartType — and declaring it would have fossilised a renderer-side tolerance into a second contract (AGENTS.md #0.1). The narrowing refuses zero live producers. Pin test: it.each over the pair, a new refusal pin for variant: 'current' at its own path, the type-level Eq pin narrowed. Both doc pages, the TS docblock (the "one wider than the spec" paragraph removed) and the changeset's FROM→TO block and paragraph rewritten. No reader changed — the normalizer's tolerance for current (normalizeChartSchema.ts:247) is untouched; objectui#7682 owns it.
  2. chartType carrier — objectui#7694 exists (filed by the PM seat; re-read 2026-09-05: open, pm:queue) and is named in the out-of-scope list below, so Fixes #7546 no longer sinks a measured finding.
  3. opacity (the non-blocking item) — took the wording option, not .min(0).max(1): the mirror stays z.number(); the doc row and the .describe now read "any finite number (the spec bounds it to 0–1)" instead of claiming a bound the mirror does not enforce. Why: every domain in this change is the read's own (num() = Number.isFinite), and the renderer never drops an out-of-range opacity (SVG clamps at paint), so .min(0).max(1) would refuse a document class that renders today — a second narrowing nothing in this PR measured; its own card if triage wants it. Census: every series-level opacity literal in the repo is within 0–1 (0.4, 0.6; the same grep is the control).
  4. main merged into the branch (merge commit 71f0c1941 — no rebase, no amend, no force): one conflict, content/docs/api/schema-reference.md, where docs(charts): rewrite the inline-series chart examples into the model the renderer implements #7679 rewrote the chart node's property table (categories / data / xAxisKey rows) while this branch rewrote the series row. Resolution keeps docs(charts): rewrite the inline-series chart examples into the model the renderer implements #7679's table and folds the declared keys into its series row. No lockfile or generated file moved on main since the merge-base (git diff --stat a472b0716..origin/main -- pnpm-lock.yaml is empty; pnpm install not re-run). After the merge git diff --stat origin/main..HEAD lists exactly this PR's six files.

Found while re-measuring — filed, left to its own card: objectui#7698

The renderer's ONLY read of series-level opacity / dashArray is inside comparisonStyle (AdvancedChartImpl.tsx:92-97), which returns null unless variant === 'comparison'; every mark reads through cmp?.…, so on a primary series both keys are read and unused (and, measured in the second remediation above, the marks split further: Bar :1911 / :2037 and Scatter :1832 pass fillOpacity only, so dashArray is line/area-only even on a comparison series — the PM seat has already posted that correction on #7698 as comment 5550238143; not this PR's change) — while the spec declares them as unconditional overrides. The declarations stay (spec members, real work on comparison series). My own doc example had taught opacity: 0.6 on a plain stacked series, a no-op: the example now puts it on the comparison series, both rows say so, and the TS docblocks carry the condition. The renderer gap is objectui#7698's (duplicate check: 119 open issues read via the repo-scoped list endpoint — every open issue from #7380 up plus every finding-labelled one — control #7694 present, no prior card).

Red first, on the untouched base a472b071

The pin file packages/types/src/__tests__/chart-series-keys-7546.test.ts was written and run before any schema edit. Predicted 14 red / 13 green; observed Tests 14 failed | 13 passed (27). The reproduction assertion is the card's measurement verbatim: expected { name: 'Revenue' } to deeply equal { name: 'Revenue', …(6) } on success: true. The lit control — declared name / type / color surviving the same query — was green in that run. After the change: Tests 27 passed (27).

Per-key measurement (the ruling's three axes, lit controls on every count)

Producer census = key counts INSIDE series array literals, by file class, with the declared keys measured by the identical query. Controls lit: docs name 6 / dataKey 10 / color 2; fixtures name 2 / dataKey 6 / color 2.

key docs / fixtures / designer inputs in-repo producers of a type: 'chart' node tests reader does real work spec ChartSeriesSchema disposition
label 0 / 0 / 0 DashboardRenderer.tsx:648-652, ObjectView.tsx, ListView.tsx, plugin-view/ObjectView.tsx (4 sites) 27 label() string or locale map; legend name I18nLabelSchema declared string | I18nLabel
variant 0 / 0 / 0 ObjectChart.tsx:852,856, DatasetWidget.tsx:1444,1450 — internal-shape callers that never meet this mirror 1 normalizer honours 3; comparison selects the overlay primary | comparison declared primary | comparison — the spec pair; current is the renderer's internal default, written by nothing an author writes (objectui#7682 owns the normalizer's tolerance)
opacity 0 / 0 / 0 core/utils/chart-presentation.ts:131 (dataset path) 1 num(); stroke/fill opacity — today only on a comparison series (objectui#7698) number 0–1 declared finite number (the spec's bound stated in docs, not enforced — remediation item 3)
dashArray 0 / 0 / 0 chart-presentation.ts:130 1 str(); strokeDasharray — same condition (objectui#7698) string declared
stack 0 / 0 / 0 chart-presentation.ts:128 5 str(); Recharts stackId string declared
yAxis 0 / 0 / 0 chart-presentation.ts:124, DatasetWidget.tsx:1446 10 narrowed to left | right; axis binding left | right declared
chartType 0 / 0 / 0 internal-shape callers only 9 first limb of ?? type, narrowed to 3 not a member — listed in aliases as a spelling of type, refused by name (chart.zod.ts:231) reported, not declared — carrier objectui#7694

Designer: the chart registration's series input is a single code (JSON) field with no per-key inputs, and defaultProps.series authors dataKey only (plugin-charts/src/index.tsx). The six are the spec's canonical members under the same names; this node's series accepts the spec shape by design (ChartRenderer.tsx:55-66, "both shapes"). Declaring them widens the accept set only toward what already renders.

chartType — the seventh, measured and reported

  1. Producers on this face: zero, controls lit. Its only literal producers hand the renderer's INTERNAL shape straight to ChartRenderer, which forwards an all-dataKey array raw (:126-131), so the normalizer's read never carries their value.
  2. The contract of record refuses it by name as an alias of type, and this package's own ChartDataSeries.type docblock (objectui#6121) calls type the author spelling and chartType the internal one.
  3. Liveness ablation of the first limb (normalizeChartSchema.ts:244, restored under trap, blob 05a66dfb moved to bd9e640f and back): with str(raw.chartType) ?? deleted, packages/plugin-charts/ plus the four dashboard/report series tests read 49 files / 470 tests passed — predicted 0 red, observed 0 red. Control with the sibling ?? str(raw.type) deleted instead: predicted at least 2 red in 2 files, observed 2 failed in normalizeChartSchema.test.ts and ChartRenderer.specSeries.test.tsx. The review re-measured the same legs on 197 files / 2380 tests with the same outcome.
  4. Consumer surprise: declaring it would mint a second writable name for one override, against the spec's alias posture (AGENTS.md #0.1).

The shape it should take — a named alias refusal pointing at type, graded as its own narrowing — is objectui#7694's decision. The pin test holds the gap visible and the TS face carries a ts-expect-error ratchet so it cannot be added by hand without re-reading this.

Clause-② — my own determination: yes

The accept set moves both ways on a published mirror: six keys now survive with their values (widening toward what renders), and a series carrying one of them with a value the renderer silently drops — variant: 'bogus' or the renderer-internal variant: 'current', yAxis: 'top', opacity: '0.4' or Infinity, a non-string stack / dashArray, a non-string non-map label — now refuses at its own path. Hence the changeset is minor, the level objectui#6896 and objectui#7113 set for the same transition in this file; the grade did not move with the narrowing. Draft; not flipped ready; not enqueued; needs:contract-review rides on both carriers (re-read 2026-09-05: the card and this PR both carry it).

Ablation A — the six Zod declarations deleted (measured on 593be2ee9; restored under trap, absolute paths)

Mutation proven on disk: anchored counts yAxis: z.enum([left,right]) 0 and label: I18nLabelSchema 0; blob f6769b2c moved to db9ef916. Restore proven: blob equals the HEAD blob and git diff HEAD on the path is empty.

  • Pin file: predicted 14 red / 13 green — observed 14 failed | 13 passed. Matched.
  • zod-mirror-parity under vitest: predicted red — observed 12 passed (12). A miss. The instrument was wrong, not the ratchet: the declared-but-unmirrored half (UnmirroredDeclaredKeys) is a compile-time assertion, and vitest does not typecheck. Re-run as leg A2 with tsc -p tsconfig.test.json under the same mutation (blob movement re-proven): predicted red naming the pair and the six keys — observed exit 2 with zod-mirror-parity.test.ts(1478,14): error TS2322: Type '"data-display.zod.ts#ChartDataSeriesSchema"' is not assignable to type 'never' plus chart-series-keys-7546.test.ts(163,38): TS2339: Property 'label' does not exist. Matched on instrument and pair; partial miss on wording — the error names the pair, not the keys. The review confirmed the channel is live in CI (@object-ui/types:type-check, cache miss, job success).

Reverse verification B — the narrowing reverted (on 493b52eb3; restored under trap, absolute paths)

Mutation: git checkout 71f0c1941 -- packages/types/src/zod/data-display.zod.ts (the merge commit's three-value enum), proven on disk: blob f5c89fe4 moved to f6769b2c; anchored counts 'comparison', 'current' 0 to 1 and z.enum(['primary', 'comparison']) 1 to 0. Predicted exactly 1 red / 26 green, the red being the new current refusal pin — observed Tests 1 failed | 26 passed (27), that test. Restore proven: git hash-object equals the HEAD blob f5c89fe4, git diff HEAD empty, git status empty. No dist/ is involved — the pin imports ../zod/data-display.zod from source — so there is no rebuild leg to skip.

Gates on the pushed head 5f7e032a8 (after the final commit)

Through os-verify-lock.sh (slot objectui-7546), joined with &&, the head echoed inside the hold (union-head=5f7e032a8) — verdict line os-verify-lock: VERDICT command-exit 0 (shared-box seconds):

  • pnpm exec vitest run packages/types/ --maxWorkers=2Test Files 107 passed (107), Tests 1769 passed (1769) (was 106 / 1768 on 493b52eb3; the one new file is main's fix(types): SchemaRegistry's kanban entry stops describing a component it cannot name #7662 pin schema-registry-kanban-honesty-7645.test.ts)
  • pnpm --filter @object-ui/types type-check (three tsc projects, the tests included) — 0 errors
  • pnpm --filter @object-ui/types lint — 0 errors, 269 pre-existing no-explicit-any warnings

Outside the lock, exit codes captured before any pipe, on the same head: check:doc-fences ✅ (227 documents), check:doc-types ✅ (188 doc files, 895 type literals, every documented type registered), check:control-bytes ✅ (6271 tracked files, plus a direct control-byte grep of the three edited files = 0), check-changeset-presence ✅ ("3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)"), check-changeset-no-major ✅, check-changeset-overwrite ✅ ("1 changeset(s) added, 0 modified" — the edited changeset is this branch's own).

The DOM probe itself (9 tests, one untracked file under packages/plugin-charts/src/, removed before the commit) ran through the same lock: Tests 9 passed (9), verdict command-exit 0; it is an instrument, not a pin — pinning "dashArray is dropped on a comparison bar" would fossilise the very gap #7698 exists to close.

NOT MEASURED locally: check:doc-snippets — the same declared narrowing as before (it needs all 26 packages built): population content/docs plus the package READMEs; my one edited fence compiles standalone (tsc --noEmit --strict --ignoreConfig, exit 0); CI's Doc Snippet Type Check is the authority and was green on 593be2ee9. Dependency build: not owed — pnpm --filter '@object-ui/types^...' build answers ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT (the only workspace dependency, @object-ui/test-support, has no build script); the package's runtime dependencies are @objectstack/spec and zod from node_modules. Consumer sweep: no structural consumer of ChartSchema / ChartDataSeries exists outside packages/types (ChartRendererProps types its schema inline), so no downstream type-check is owed.

Docs

content/docs/api/schema-reference.md — the series row, now inside #7679's rewritten chart table (which teaches the rows model and the data tombstone), lists the declared keys with variant as the pair. content/docs/plugins/plugin-charts.mdx — the "Per-series options" table and example: variant is the pair, opacity says any finite number with the spec's bound and that it reaches every mark family on a comparison series, the dashArray row states the full condition (comparison AND a line/area mark), and the example is an area chart on which every key it writes is live (second remediation).

Out of scope — filed, not fixed here

  • objectui#7681 — a declared per-series type override is dropped when every entry is written with dataKey: the fast path forwards the array raw and the renderer reads only chartType. Open (pm:queue).
  • objectui#7682 — series variant carries three different unions across four sites; current is an undocumented synonym of primary that core's seriesPresentation drops. Open (finding, pm:queue); it owns the normalizer's tolerance this PR leaves untouched.
  • objectui#7690 — the chart-level axis-config object dialect (xAxis / yAxis objects) rides through ChartSchema unvalidated; filed by the PM seat from the card's own body. Open (pm:queue).
  • objectui#7694 — series chartType is undeclared and silently stripped; the carrier for the named-alias-refusal shape, graded as its own narrowing. Open (pm:queue).
  • objectui#7698 — series-level opacity / dashArray are honoured only on a variant: 'comparison' series while the spec declares them unconditional; filed from the first remediation. Open (pm:queue, p3, triaged 07:05Z with "do not open this as narrow the mirror").

Dev seat: Claude Code session session_01KbJQ1y1J12nZxYzFWhP8Q3, dispatched by the domain:ui PM seat; contract-review remediation by the same seat.


🤖 Generated with Claude Code

https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3


Generated by Claude Code

…nderer reads (objectui#7546)

`ChartDataSeriesSchema` is a non-strict `z.object`, so `label`, `variant`,
`opacity`, `dashArray`, `stack` and `yAxis` — all read by `normalizeSeries` and
all doing real work in `AdvancedChartImpl` — were stripped in silence while
`safeParse` reported success. Reproduced red on a472b07: the card's fixture
parsed to `{ name }`. Both faces (TS twin and Zod mirror) now declare the six
with the read's own value domains: `label` as the spec's `I18nLabel`,
`variant` as the three values the normalizer honours, `opacity` finite,
`yAxis` left/right.

Measured per key with lit controls, per the PM ruling. `chartType` — the
seventh key the review found — is deliberately NOT declared: it is the
renderer's internal spelling of `type`, the spec lists it as an alias of
`type` and refuses it by name, and nothing on this authoring face writes it.
Reported for its own card; the pin test holds that gap visible.

Docs: the schema-reference `series` row and a per-series options section in
the plugin-charts guide. Changeset graded `minor` — the mirror now refuses a
malformed value the renderer used to ignore.

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

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3180.1 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-CI4V3Zxi.js
Status PASS

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


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.70KB 116.21KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 180.00KB 50.20KB
fields (index.js) 242.27KB 61.22KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
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 (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) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.43KB
plugin-dashboard (index.js) 132.86KB 34.68KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.57KB 11.96KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.58KB 2.23KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Contract review — claude-fable-5-1, adopted verbatim

Isolated review subagent. Tier verified from its transcript: 92/92 assistant turns stamped claude-fable-5-1, 0 fallback notices. Lit control: the same reader returns claude-opus-5 thousands of times across this session's transcripts, so the zero is a reading. Adopted verbatim — not edited, trimmed or paraphrased.


Confirmed: SchemaRenderer imports validateSchema from @object-ui/core (the hand-written validator, schema-validator.ts has no zod import), dev-only. The render path never runs the Zod mirror. No further measurements are needed; the record follows.


Contract review — objectui PR #7684 (593be2ee9, card #7546)

Instruments used (all on a detached scratch worktree at 593be2ee9, removed after; primary checkouts untouched, no GitHub writes): own bracket-matched census of keys inside series: [ … ] literals across docs / fixtures-apps / src / tests; runtime probes of the installed @objectstack/spec@17.2.0 ChartSeriesSchema and of zod@4.4.3; three ablations restored under EXIT INT TERM traps with blob-movement and empty-git diff HEAD proofs (A: six Zod declarations deleted → tsc -p tsconfig.test.json; A′: six TS declarations deleted, Zod kept → same instrument; B0/B1/B2: chartType first limb vs sibling type limb, vitest over packages/plugin-charts + plugin-dashboard/src/__tests__ + plugin-report/src/__tests__ + core/src/utils, 197 files / 2380 tests — 5× the author's population); CI job logs for the Type Check job on this head.

① Derived judgments

# Change Verdict Deciding measurement
1 label: I18nLabelSchema.optional() / TS label?: string | I18nLabel RIGHT Spec member (probe: locale map {en, zh-CN} → OK). Producers: internal-shape { dataKey, label } at plugin-dashboard/src/DashboardRenderer.tsx:648-652, app-shell/src/views/ObjectView.tsx, plugin-list/src/ListView.tsx, plugin-view/src/ObjectView.tsx (census src label 5 in 4 files; control dataKey 13). Consumers: normalizeSeries label() (normalizeChartSchema.ts:242), ChartRenderer.tsx s.label || s.dataKey, AdvancedChartImpl.tsx:1364, 1592. Value domain = the reader's (string, or first string of a map).
2 variant: z.enum(['primary','comparison','current']) / TS three-value union WRONG on the third member (primary/comparison RIGHT) Installed spec refuses it: variant: Invalid option: expected one of "primary"|"comparison". Authoring-face producers of current: docs 0 at base (git show a472b0716 — both doc pages), fixtures 0, designer 0 (the chart registration's series is one code input, plugin-charts/src/index.tsx:114). The only two producers — ObjectChart.tsx:852 s.variant || 'current', DatasetWidget.tsx:1450 s.variant ?? 'current' — write onto internal-shape (dataKey) arrays handed straight to ChartRenderer (ObjectChart.tsx:1118) or to SchemaRenderer as any (DatasetWidget.tsx:1519), whose dev-only validator is core's hand-written validateSchema (schema-validator.ts imports no zod) — they never meet ChartDataSeriesSchema. Core's own authored-series reader seriesPresentation (core/utils/chart-presentation.ts:125) keeps primary | comparison and drops current; ChartRenderer.tsx:65 types current on the internal arm only. current is therefore the renderer's internal default, produced by nothing an author writes — the identical evidence profile the PR uses to exclude chartType. Declaring it on the published Zod face and teaching it in two doc pages while #7682 (the author's own drift card on this exact synonym) is open is the AGENTS.md #0.1 fossilisation: a renderer-side tolerance becoming a second de-facto contract. Narrowing to the spec pair refuses zero live producers (census: the only variant value ever written inside a series literal, in any file class, is 'comparison').
3 opacity: z.number() / TS number RIGHT (one reservation, non-blocking) The read's domain: num() = finite number; zod 4.4.3 z.number() refuses NaN and Infinity (probed: Infinity → false, 7 → true). Producer chart-presentation.ts:131 (dataset/report path, exercised by DatasetReportRenderer.chartChrome.test.tsx); consumer AdvancedChartImpl.tsx:94-95. Reservation: wider than the spec's .min(0).max(1) (probe opacity: 7 → REFUSE Too big) and than the PR's own doc row ("0–1"), so the doc claims a bound the mirror does not enforce. Consistent with the card's "declare the read's domain" rule; noted, not refused.
4 dashArray: z.string() RIGHT Spec member; producer chart-presentation.ts:130; consumer AdvancedChartImpl.tsx:96 (strokeDasharray). Control lit (name/dataKey counted by the same query).
5 stack: z.string() RIGHT Spec member; producer chart-presentation.ts:128; consumer AdvancedChartImpl.tsx:1893, 2023 (stackId); tests 5.
6 yAxis: z.enum(['left','right']) RIGHT Spec union identical (probe yAxis: 'top' → REFUSE). Every series-level yAxis literal in the repo: 'left' 1, 'right' 9 — nothing else; the 'total'/'amount'/'n'/'deals' hits are chart-level bare-string report axes (e.g. normalizeChartSchema.test.ts:42), not series. Producers chart-presentation.ts:124, DatasetWidget.tsx:1446; consumers AdvancedChartImpl.tsx:1887, 2019, 2025. No live producer refused.
7 chartType left undeclared, reported RIGHT as a liveness disposition (carrier defect in ③ Q1) Installed spec refuses it by name: Unrecognized key(s) on this chart series: chartType. Did you mean chartType → type?. Core translates authored type → internal chartType (chart-presentation.ts:122-123), confirming it is the internal spelling. Census of series-level chartType: docs 0, fixtures 0, src literals 0, tests 9 — all nine internal dataKey-shaped, which take ChartRenderer's raw fast path and never reach the normalizer's first limb. Ablation re-measured on 197 files / 2380 tests: B0 baseline 2380 passed; B1 (str(raw.chartType) ?? deleted, blob 05a66dfb → bd9e640f) predicted 0 red → 2380 passed; B2 control (?? str(raw.type) deleted, blob → 1cc8e75a) predicted ≥2 red → 2 failed in normalizeChartSchema.test.ts and ChartRenderer.specSeries.test.tsx. Restore proven (blob = HEAD, diff empty). The author's reading holds on a larger population.
8 TS twin in lockstep; ts-expect-error ratchet on chartType RIGHT A′ (six TS lines deleted, Zod kept, blob 8abbf7fe → cc7c6bfd): the parity guard is silent by design (its header says that direction is "a different class"), but chart-series-keys-7546.test.ts fires under tsc — TS2353 at 174:7, TS2339 at 185:39 and 186:37. Covered.
9 Object stays non-strict; data tombstone and #6939 refinement untouched RIGHT Pin (e) + chart-inline-data-retired.test.ts; baseline 3 files / 54 tests passed.
10 Docs: schema-reference.md series row; plugin-charts.mdx "Per-series options" table + example WRONG insofar as they teach current (rest RIGHT) Both pages publish variant as primary / comparison / current; base had no such teaching anywhere (git grep at a472b0716: 0). See row 2.

Parity ratchet (brief item 4): not dark. Ablation A (six Zod declarations deleted, blob f6769b2c → db9ef916, anchored counts 0/0): tsc -p tsconfig.test.json exit 2, zod-mirror-parity.test.ts(1478,14): error TS2322: Type '"data-display.zod.ts#ChartDataSeriesSchema"' is not assignable to type 'never' plus chart-series-keys-7546.test.ts(163,38): TS2339. The channel is real in CI: ci.yml Type Check → pnpm type-check → turbo → @object-ui/types type-check = tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json; this head's job log shows ##[group]@object-ui/types:type-check / cache miss, executing a834f9af5a52ccd5 (02:25:10Z), job success. The vitest "miss" is exactly what the parity file's own header predicts ("its Tests 12 passed (12) line does not move when this half reddens"). Author's disclosure is accurate. Also closed: check:doc-snippets, NOT MEASURED locally — CI Doc Snippet Type Check (job 101232202884) success; all 32 check runs green.

② Semver grading

③ Boundary-flag disposition

Q1 — chartType shape (A alias-refusal / B fold / C declare). A. It is the contract of record's own posture (the installed spec's alias map already says chartType → type), it makes the strip loud without minting a second writable name, and its narrowing is graded on its own card. Not for this PR. However, it currently has no carrier: Fixes #7546 closes the only open card that names it; #7690 carries only the axis-config half; #7682 carries only variant. After merge the gap survives only as a comment on a closed card and a pin-test comment. That is measured-and-lost. A card must exist and be named in the PR's "Out of scope — filed" list before merge (a PM write; the PR is blocked until the reference exists).

Q2 — variant three-value union (A keep / B narrow now). B, narrow now. (i) No authoring-face producer writes current — docs 0, fixtures 0, designer 0 — and the two internal producers never meet the mirror, so narrowing refuses nothing live; (ii) it is the same instrument reading the PR applied to chartType, and consistency is the finding; (iii) AGENTS.md #0.1; (iv) #7682 remains the right place to decide the renderer's tolerance — the published contract does not need to pre-empt it in the internal direction, and option A would force a second narrowing changeset later.

Q3 — axis-config object dialect (A file / B leave). A — already done. #7690 (02:28:08Z, pm:queue, domain:ui, package: types, priority:p3) quotes the card body's measurement verbatim and names #7684's fence. The closing keyword is correct for the six-key half. Half-state noted in the report — the card lacking needs:contract-review — is resolved: #7546 now carries the label.

Out-of-scope carriers verified: #7681 (per-series type dropped on the dataKey fast path) and #7682 (variant drift) both exist and are open.


VERDICT: REFUSE

What must change:

  1. Narrow variant to the spec pair on both facespackages/types/src/zod/data-display.zod.ts z.enum(['primary', 'comparison']); packages/types/src/data-display.ts variant?: 'primary' | 'comparison' with the docblock's "one wider than the spec" paragraph removed; drop current from content/docs/api/schema-reference.md (series row) and content/docs/plugins/plugin-charts.mdx (table); update the pin test's it.each in block (b) and the Eq<> pin in block (c); rewrite the changeset's FROM→TO block and its "one value wider" paragraph. No reader changes — the normalizer's tolerance for current stays for finding(plugin-charts/core/types): series variant carries three different unions across its declarations and readers — current is an undocumented synonym of primary that core's seriesPresentation drops #7682 to resolve.
  2. Give the chartType gap a carrier before merge — file the card (option A shape: named alias refusal pointing at type, its own narrowing grade) and reference it from the PR body's "Out of scope — filed, not fixed here" list, so Fixes #7546 does not sink a measured finding.

Not blocking, recorded: the plugin-charts.mdx opacity row says "0–1" while the mirror is z.number() (any finite); either state "any finite number (the spec bounds 0–1)" or declare .min(0).max(1).


Generated by Claude Code

Resolves the one conflict, content/docs/api/schema-reference.md: #7679
rewrote the chart node's property table (categories / data / xAxisKey rows)
while this branch rewrote the series row to list the declared per-series
keys. Kept main's table structure and folded the declared keys into its
series row. No lockfile or generated file moved on main since the
merge-base.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
…t is renderer-internal (objectui#7546)

Contract-review remediation on PR #7684 (comment 5548861339).

- variant: z.enum(['primary', 'comparison']) on the Zod mirror and the
  matching TS union. The third value, 'current', is the renderer's internal
  default: written only by ObjectChart.tsx:852 and DatasetWidget.tsx:1450
  onto dataKey-shaped arrays handed straight to ChartRenderer / SchemaRenderer
  (hand-written validateSchema, no zod), so it never meets this mirror; docs,
  fixtures and designer inputs write it 0 times (controls lit); the installed
  spec 17.2.0 refuses it. Declaring it would fossilise a renderer-side
  tolerance into a second contract (AGENTS.md #0.1) — the same reading that
  excluded chartType. No reader changed: the normalizer's tolerance stays for
  objectui#7682.
- Pin test: it.each over the pair, a new refusal pin for 'current', the Eq
  pin narrowed; both doc pages and the changeset drop 'current'.
- opacity stays z.number() (the read's own finite domain; SVG clamps, nothing
  is dropped); the doc row and describe now say "any finite number (the spec
  bounds it to 0–1)" instead of claiming a bound the mirror does not enforce.
- Docs no longer teach opacity on a primary series: the renderer reads
  opacity / dashArray only inside comparisonStyle (objectui#7698, filed).

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

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3180.1 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-CI4V3Zxi.js
Status PASS

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


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.70KB 116.21KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 180.00KB 50.20KB
fields (index.js) 242.27KB 61.22KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
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 (useSpecGesture.js) 4.39KB 1.66KB
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) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.43KB
plugin-dashboard (index.js) 132.86KB 34.68KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.57KB 11.96KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Contract re-review (round 2) — claude-fable-5-1, adopted verbatim

Isolated review subagent. Tier verified from its transcript: 78/78 assistant turns stamped claude-fable-5-1, 0 fallback notices, against a lit control that reads claude-opus-5 in this session's other transcripts. Adopted verbatim — not edited, trimmed or paraphrased.


All measurements are in; the scratch tree is removed and both primary checkouts are untouched (the objectui-review-7700* trees belong to another session and were not touched). Nothing further to request. The report follows.


Contract review — objectui PR #7684 (head 493b52eb3, card #7546) — second round

Instruments (all on a detached scratch worktree at 493b52eb3, pnpm install --offline from the store, removed after; primary checkouts untouched, read-only git on them, no GitHub writes): git diff against the true merge-base 6eebc54b6 (main has since moved one commit, #7691 — hence behind); both-parent diffs of the merge commit and a git merge-tree --write-tree 593be2ee9 6eebc54b6 reproduction of the conflict; blob identity on the reader; a bracket-matched census of keys inside series: [ … ] literals by file class (docs / fixtures / src / tests) with name/dataKey/color as lit controls; runtime probes of the installed @objectstack/spec@17.2.0 ChartSeriesSchema and of the mirror at head; the pin file under vitest and pnpm --filter @object-ui/types type-check (three tsc projects, tests included); reverse-verification of the narrowing under an EXIT INT TERM trap with blob-movement and empty-diff proofs; and a DOM probe through the real ChartRenderer (spec name shape, so the series pass normalizeSeries) reading stroke-opacity / stroke-dasharray / fill-opacity off the rendered recharts marks, with primary-vs-comparison and no-keys controls. CI on the head re-read after the shards finished: 32 check runs, all complete, none failed (Type Check, Test shards 1–4, Doc Snippet Type Check, Changeset Bump Policy, Lint green).

① Derived judgments

# Change Verdict Deciding measurement
1 label: I18nLabelSchema.optional() / TS label?: string | I18nLabel RIGHT Spec member (same schema object imported). Census: src label 5 (the four internal-shape producers), docs 3, tests 16; control dataKey 12/7/22. Card fixture round-trips a locale map; refuses 42 at label.
2 variant: z.enum(['primary', 'comparison']) / TS 'primary' | 'comparison' RIGHT Installed spec refuses 'current' (Invalid option: expected one of "primary"|"comparison") — the mirror now answers identically at path variant. Producers of current on the authoring face: docs 0, fixtures 0 (both spellings, bracket census; controls lit), designer 0 (plugin-charts/src/index.tsx:114series is one code input). The only writers remain ObjectChart.tsx:852 and DatasetWidget.tsx:1450 (internal-shape, never through this mirror). Narrowing refuses zero live producers. DOM: variant: 'current' through the spec path draws exactly as primary — nothing an author loses.
3 opacity: z.number() / TS number RIGHT (reservation, non-blocking — see ② and Q4 below) Read's domain is num() = Number.isFinite; probe: 0.5 OK, 7 OK, -1 OK, Infinity / NaN / '0.4' refused at opacity. Spec refuses 7 (Too big) and -1 (Too small). Census of series-level literals in every file class: 0.4, 0.5, 0.6 only — the out-of-range class is empty.
4 dashArray: z.string() RIGHT as a contract member Spec member, unconditional ("SVG stroke-dasharray override"). Read at normalizeChartSchema.ts:250; honoured on a comparison line/area mark — DOM: comparison line with dashArray: '2 2' renders stroke-dasharray="2 2", with no keys "4 4".
5 stack: z.string() RIGHT stackProps at AdvancedChartImpl.tsx:1893, 2023; docs 2, tests 3; control lit.
6 yAxis: z.enum(['left', 'right']) RIGHT Spec union identical; census of every series-level yAxis value in the repo: right 7, left 1, nothing else.
7 chartType left undeclared, reported RIGHT Not re-litigated — the prior review upheld the ablation on 5× the population. Re-confirmed the two facts that ground it: spec refuses it by name (Unrecognized key(s) … Did you mean chartType → type?), and the mirror still strips it ({ name, chartType } parses to { name }). Carrier now exists: #7694 (open, pm:queue, priority:p3, filed by the PM 02:51:20Z), named in the PR body's out-of-scope list.
8 TS twin in lockstep; Eq pin narrowed; ts-expect-error ratchet on chartType RIGHT tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json exit 0 on the head — the Eq<ChartDataSeries['variant'], 'primary' | 'comparison' | undefined> = true pin compiles, so the TS union is the pair. Pin file 27/27.
9 Narrowing complete on every carrier; no reader changed RIGHT Zod line 409, TS line 1443, docblock's "one wider than the spec" paragraph gone, both doc rows are the pair, it.each over the pair + a current refusal pin, changeset FROM→TO shows the pair. Residual current in the six files is explanatory only; git grep current -- content skills | grep variant = 0. normalizeChartSchema.ts blob 05a66dfb on both sides; git diff 6eebc54b6..HEAD -- packages/plugin-charts packages/core empty. Reverse verification: restoring the merge commit's three-value enum (blob f5c89fe4 → f6769b2c, anchored pair 0 / triple 1), predicted exactly 1 red / 26 green — observed 1 failed | 26 passed, the current pin; restored to f5c89fe4, git diff HEAD 0 bytes.
10 Merge commit 71f0c1941 RIGHT git diff --stat 6eebc54b6 71f0c1941 lists exactly the PR's six files — the merged tree is main's tree plus this PR, nothing else. Branch-side file set is byte-for-byte the set of a472b0716..6eebc54b6 (30 files, 1186+/280−). merge-tree reproduces the single conflict in schema-reference.md; the resolution differs from main's file in one row only — #7679's categories / data / xAxisKey rows, its ChartDataSeries[] type name and its example rewrite are all intact. Lockfile unmoved between merge-base and main.
11 Docs: schema-reference.md series row RIGHT Pair, keys listed, #7679's row semantics preserved.
12 Docs: plugin-charts.mdx "Per-series options" table + example WRONG on dashArray (rest RIGHT) DOM probe of the example's own shape — chartType: 'bar', comparison series { opacity: 0.6, dashArray: '4 4' }: the comparison rects carry fill-opacity="0.6" (the instrument is lit on the same entry) and the surface contains no stroke-dasharray at all. Source: Bar marks pass only fillOpacity (AdvancedChartImpl.tsx:2037, combo :1911), Scatter likewise (:1832), and comparisonStyle itself sets strokeDasharray to undefined for those kinds (:96). So the example teaches dashArray in a position where it is inert — the exact defect class the author removed for opacity in this remediation ("my own doc example had taught … a no-op") — and the dashArray row's stated condition (comparison-only) is incomplete: it is comparison and line/area only. The dashArray TS docblock carries the same incomplete condition.
13 Changeset '@object-ui/types': minor RIGHT on level (one wording gap, ②) See ②.

The sharpest question (Q3), judged. Declaring opacity / dashArray unconditionally is the right application of "declare the read's domain". The two evidence profiles are not the same: chartType was excluded because the name is one the contract of record refuses (an alias hazard, AGENTS.md #0.1) and its read is not load-bearing on any reachable path (0 red on limb ablation). opacity / dashArray are spec members declared unconditional, produced (chart-presentation.ts:130-131, the report test, the docs), and honoured on a reachable authoring path — lit in the DOM: { name, variant: 'comparison', opacity: 0.3, dashArray: '2 2' } through the spec-shape path yields stroke-opacity="0.3" stroke-dasharray="2 2". The conditionality is on a sibling key's value inside the renderer, not on the key's domain — the read's domain (num(), str()) is identical on primary and comparison series. Narrowing the mirror to comparison-only would (a) refuse a document the spec accepts ({ name, opacity: 0.5 }), (b) fossilise a partial renderer implementation into the published contract — the direction #0.1 forbids, and the PM's triage note on #7698 says the same — and (c) have to be undone when #7698 lands option A. The declaration also does not create the silent no-op: the render path never runs the mirror (the prior review confirmed SchemaRenderer uses core's hand-written validator), so { name, opacity } on a primary series drew identically before this PR; what the declaration changes is that parse-output consumers keep the key, and the docs can state the condition. That mitigation is exactly why row 12 is blocking: the PR's answer to "does this publish a surface that silently no-ops?" is "the docs say so" — and for dashArray the docs say it incompletely and the example demonstrates the no-op.

Q4 — opacity domain, graded. The reasoning is consistent with the rule the PR applies to every other key: the mirror mirrors normalizeSeries's read (Number.isFinite), not SVG's clamp and not the spec's bound. The distinction from variant: 'current' holds — an out-of-range number is not a second name for one intent (the N-dialects hazard), the docs do not teach it, and the .describe and both rows state the spec's bound. The weak leg is "would refuse a document class that renders today": that class is measured empty (every series-level literal in the repo is 0.4 / 0.5 / 0.6), which is the same reading that made narrowing variant free. So .min(0).max(1) would refuse zero live producers and would give mirror/spec range parity — a free narrowing available to a follow-up, not owed by this card, and the prior review already ruled it non-blocking. Recorded, not refused.

Q5 — the disclosed control miss. The zero is a reading. "variant": "comparison" over content/examples/apps = 0; the bracket census shows the fixtures population (examples/, apps/) writes no series-level variant in either spelling while name 2 / dataKey 6 / color 2 are counted inside the same series literals by the same instrument; the JSON-spelling "variant": hits in that population (222) are all outside series arrays (button/badge variants). The docs population's variant = 1 (plugin-charts.mdx:208, 'comparison'), matching the author's re-lit control.

② Semver grading

③ Boundary-flag disposition


VERDICT: REFUSE

What must change:

  1. content/docs/plugins/plugin-charts.mdx — stop teaching dashArray where it is inert, and state its full condition. The example is a chartType: 'bar' chart whose comparison series carries dashArray: '4 4'; on a Bar mark that key reaches nothing (measured: fill-opacity="0.6" present from the same entry, no stroke-dasharray in the rendered surface; AdvancedChartImpl.tsx:2037, :1911, :1832, :96). Either move the example to a family whose comparison mark takes a dash — area keeps every key in the example live (stackProps, fillOpacity, strokeOpacity, strokeDasharray at :2056) — or drop dashArray from the bar example. In the same edit, the dashArray row and the ChartDataSeries.dashArray docblock in packages/types/src/data-display.ts must say the condition in full: applied today only on a variant: 'comparison' series and only on line / area marks (Bar and Scatter take fillOpacity only). No contract change, no reader change, no new changeset semantics.

Not blocking, recorded: (a) name variant: 'current' in the changeset's ⚠️ refusal list — it is refused but is not "a value the renderer drops in silence"; (b) the mirror is looser than the spec on opacity's range, and .min(0).max(1) would refuse zero live producers — a free follow-up for mirror/spec range parity if the maintainer wants it; (c) #7698's "all five call sites apply all three" needs the Bar/Scatter correction above, by PM comment on the card.


Generated by Claude Code

…mparison AND a line/area mark (objectui#7546)

Second contract review (PR comment 5550238043) refused on one item: the
"Per-series options" example in plugin-charts.mdx was a chartType 'bar'
chart whose comparison series carried dashArray '4 4' — a position where the
key reaches nothing. Re-measured through the real ChartRenderer under
happy-dom, predictions before probes, a lit control on every probe:

- bar, the old example verbatim: the comparison rects carry
  fill-opacity="0.6" (lit, same entry); the surface has zero
  stroke-dasharray attributes.
- area, the new example: stroke-dasharray="8 4", stroke-opacity="0.6" and
  fill-opacity="0.6" on the comparison mark (both its area and curve paths);
  the primaries keep fill-opacity 1 and no dash; stack is live (the stacked
  cost area's lower edge is the revenue area's upper edge; unstacked both sit
  on the baseline).
- line: stroke-dasharray="2 6" + stroke-opacity="0.6"; scatter:
  fill-opacity="0.6" on every symbol, no dash; a comparison line INSIDE a bar
  chart (per-series type) takes the dash — the condition is the mark, not
  chartType. Primary series of any family: nothing reaches the DOM.

Mechanism (AdvancedChartImpl.tsx, blob 63a5e34 on main and here):
comparisonStyle :96 returns the AUTHORED dashArray for every family — the
undefined half at :96 is only the fallback — and the mark drops it: Scatter
:1832, combo Bar :1911 and Bar :2037 pass fillOpacity only; Line :1898/:2048
pass strokeOpacity + strokeDasharray; Area :1905/:2056 pass all three. So
opacity is live on every family (fill on bar/scatter, stroke on line, both on
area) while dashArray is live on line/area only — the two keys split.

Changes, wording only — no contract, reader or changeset-level change:
- plugin-charts.mdx: the example moves to chartType 'area' (the one family
  where stack, variant, dashArray and opacity are ALL live), with a
  non-default dash '8 4' so the override visibly does work; the dashArray row
  states comparison AND line/area (bar/scatter take opacity only); the
  opacity row says every mark family, fill vs stroke per family.
- data-display.ts: the dashArray docblock carries the full condition with the
  mark sites; the opacity docblock says it reaches every family.
- changeset: the refusal list names variant 'current' — refused, but NOT a
  value the renderer drops (the normalizer keeps it and draws it as primary).

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

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3180.1 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-CI4V3Zxi.js
Status PASS

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


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.70KB 116.21KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 180.00KB 50.20KB
fields (index.js) 242.27KB 61.22KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
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 (useSpecGesture.js) 4.39KB 1.66KB
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) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.43KB
plugin-dashboard (index.js) 132.86KB 34.68KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.57KB 11.96KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Contract re-review (round 3) — claude-fable-5-1, adopted verbatim

Isolated review subagent. Tier verified from its transcript: 88/88 assistant turns stamped claude-fable-5-1, 0 fallback notices, against a lit control that reads claude-opus-5 in this session's other transcripts. Adopted verbatim — not edited, trimmed or paraphrased.


All measurements are in. Both scratch trees are removed, my temporary pr-7684 ref is deleted, and the primary checkout is untouched (claude/pm-dispatch-ui-bbsfro at 77b3cf1f5, clean). No GitHub writes were made. The report follows.


Contract review — objectui PR #7684 (head 5f7e032a8, card #7546) — third round

Instruments. Detached scratch worktrees at 5f7e032a8 and at a dangling merge of it onto the current main (pnpm install --offline --frozen-lockfile, both removed after; primary checkouts read-only; no GitHub writes). Full-tree entry-by-entry comparison of the merge commit against both parents; TypeScript-scanner token streams (trivia dropped) on every TS carrier with a one-token lit control; anchored on-disk counts against the commit blobs; a DOM probe through the real ChartRenderer under happy-dom (spec name shape, ResponsiveContainer sized as in ChartRenderer.specSeries.test.tsx), predictions written before each run, readings persisted to disk; vitest over packages/types/ and the three-project type-check, on the head and on the merged tree; installed @objectstack/spec@17.2.0 read from dist. Every card and PR named below was read in this session between 07:36Z (the PR's last update) and 07:48Z; the two that changed state during the review are dated to the second.

State at the time of writing (07:51Z). origin/main moved from bc640ec56 to 8ad218d58 during this review — #7700 merged at 07:40:44Z. The PR's merge base is bc640ec56; the PR is one commit behind, mergeable_state: clean, still draft, needs:contract-review on both carriers. CI on 5f7e032a8: 32 check runs complete, none failed (Type Check 07:41:53Z, Test shards 1–4 by 07:45:40Z, Doc Snippet Type Check, Changeset Bump Policy, Lint all success; the three skips are coverage ×2 and dependabot).

① Derived judgments

Nothing in the accept set or on any reader moved since the reviewed head 493b52eb3 — proven at token level, not by --stat — so rows 1–9 restate round 2's judgments with this round's re-run as the deciding measurement; rows 10–17 are this round's changes.

# Item at 5f7e032a8 Verdict Deciding measurement
1 Zod mirror data-display.zod.ts (the six declarations, variant pair, opacity finite) RIGHT — unchanged Blob f5c89fe40 at 493b52eb3, 51f2a9634, 5f7e032a8; 3796 tokens, 0 differing. Suite on the head 107 files / 1769 tests green (the author's numbers reproduced exactly); tsc ×3 exit 0.
2 TS twin data-display.ts — contract RIGHT — unchanged 2136 tokens at both 493b52eb3 and 5f7e032a8, 0 differing. Lit control: one keyword mutated on a copy → 1 differing token at index 1558; a comment-only mutation → 0 (the instrument is blind to comments by design, so the 0 above is a reading of code, not of prose).
3 Pin test chart-series-keys-7546.test.ts RIGHT — unchanged Blob 01a1a24a5 on all three; 1474 tokens, 0 differing; 27/27 inside the 1769.
4 Renderer / normalizer — no reader changed RIGHT AdvancedChartImpl.tsx blob 63a5e34c9 on origin/main, 493b52eb3, 5f7e032a8 — 2902 tokens, 0 differing vs main; normalizeChartSchema.ts 05a66dfb5, 2416 tokens, 0; ChartRenderer.tsx b148a5dd4 identical.
5 schema-reference.md series row RIGHT — unchanged Blob 1be46b438 on all three.
6 chartType left undeclared, reported RIGHT — unchanged Mirror unchanged (row 1); carrier #7694 open.
7 Merge commit 51f2a9634 = main's tree + this PR and nothing else RIGHT Parents 493b52eb3 + bc640ec56. Tree entries: merged 6356 = main 6354 + 2 added. Entries in the merged tree not in main's = exactly the six PR blobs, every one present in the PR tree; entries in main's tree not in the merged = exactly the four modified files' main blobs. git diff 493b52eb3 51f2a9634 is byte-identical (md5 bbb761c4…) to git diff 6eebc54b6 bc640ec56: 16 files from #7691 (6e8863093), #7649 (5b5d3ed77), #7662 (bc640ec56). The six PR blobs are identical on both sides of the merge — no conflict, nothing folded.
8 Head against the current main 8ad218d58 (#7700) RIGHT git merge-tree --write-tree 8ad218d58 5f7e032a8 → clean, tree 289de6878; that tree differs from main by exactly the six files; #7700's eight files overlap none of them. On that merged tree: packages/types/ 108 files / 1811 tests green, type-check 0 errors — #7700's zod-mirror-parity.test.ts ledger edit and this PR's six declared keys coexist in the same package. No re-merge is owed; AGENTS.md says the queue rebuilds on current main.
9 Wording-only scope of 493b52eb3..5f7e032a8 RIGHT Files changed in the wording commit: exactly three (.changeset/…7546…md, plugin-charts.mdx, data-display.ts). The mdx has 10 tsx fences; 9 are token-identical and fence #5 differs in exactly two tokens: 'bar'→'area' at [10] and '4 4'→'8 4' at [102]. Changeset frontmatter identical ('@object-ui/types': minor).
10 The blocking item — the example (chartType: 'area', stack ×2, comparison { dashArray: '8 4', opacity: 0.6 }) RIGHT — fixed Rendered verbatim (R1): 3 .recharts-area layers (name binds); legend text CostRevenueRevenue (previous period) and the raw key revenue_prev absent (label reaches the legend and wins over the key); the comparison mark's area-curve and area-area paths carry stroke-dasharray="8 4", stroke-opacity="0.6", fill-opacity="0.6"; the only dash attributes on the whole surface are ["8 4","8 4"] — the default 4 4 appears nowhere; both primaries fill-opacity="1", no dash, no stroke-opacity. stack: the cost area's bottom edge (475,180),(53,145) is the revenue area's top edge (53,145),(475,180). Gate control (R6): the identical third series without variant → no dash, no stroke-opacity, fill-opacity="1" — so variant is live, and it is the gate. Every taught key reaches the surface.
11 The '8 4' choice (⭐) RIGHT Default verified at source :96 (s.dashArray ?? (kind === 'line' || kind === 'area' ? '4 4' : undefined)) and in the DOM: R3 (comparison area, no keys) → stroke-dasharray="4 4", stroke-opacity="0.6", fill-opacity="0.2"; R4 (same series with dashArray: '4 4' written) → the comparison curve's outerHTML is byte-identical to R3's. An example writing '4 4' on a comparison line/area would render indistinguishably from omitting the key — inert in exactly the quieter way the author says. The example's inline comment ('8 4' overrides … '4 4'; 0.6 lifts its 0.2 fill default (its stroke default is already 0.6)) matches R3's three defaults to the digit.
12 dashArray row (comparison and a line/area mark, chart-level or per-series type; bar/scatter take opacity only) RIGHT — complete and correct Line takes an authored dash (R5: 2 6, stroke-opacity 0.3, no fill-opacity); area takes it (R1); bar drops it — old example verbatim (R2): 3 bars, 0 stroke-dasharray on the surface, 2 rects at fill-opacity 0.6; comparison bar with an authored dash and no opacity (R7): 0 dash, rects at the 0.4 default; scatter (S1, numeric x, one series — the only shape the scatter branch mounts): 3 symbols all fill-opacity="0.6", 0 dash, no stroke-opacity; control without variant: no fill-opacity; per-series type override (S2: chartType: 'bar', comparison series type: 'line', dashArray: '2 6'): 1 bar + 1 line, the line curve carries 2 6 and 0.6 — the condition is the mark, not the chart.
13 opacity row (every mark family under comparison — fill on bar/scatter, stroke on line, both on area; unused on primary) RIGHT Bar fill (R2/R7), scatter fill (S1), line stroke only (R5: fo null), area both (R1), primary unused (R6, S1-control).
14 data-display.ts docblocks for opacity / dashArray RIGHT Every cited site checked against the head: comparisonStyle :92-97 with the authored value first at :96; Scatter :1832 and Bar :1911/:2037 pass fillOpacity only; Line :1898/:2048 strokeOpacity+strokeDasharray; Area :1905/:2056 all three. "comparisonStyle returns the authored value for every family, but a Bar or Scatter mark passes fillOpacity only and drops strokeDasharray (and strokeOpacity)" is the mechanism as the source reads. Old phrasings on disk: applied at …:96 0, Same condition as 0, every mark reads 0.
15 Changeset ⚠️ list names variant: 'current' (the non-blocking item taken) RIGHT — exact Text: "and, separately, variant: 'current', which the renderer does NOT drop: the normalizer keeps that renderer-internal spelling and draws it exactly as primary, but it is not a member of the published pair, so it now refuses at variant". Source: normalizeChartSchema.ts:247 keeps current; AdvancedChartImpl.tsx:2010 tests only === 'comparison'; the pin refuses at path ['variant']. The "value the renderer drops" class and the honoured-but-refused value are now stated as two classes, which is the distinction round 2 asked for.
16 .min(0).max(1) declined (the non-blocking item not taken) RIGHT — still defensible Spec 17.2.0: opacity: z.number().min(0).max(1).optional() (dist/ui/index.js ≈2399); mirror z.number(); the row, the describe and the docblock all state the bound and that the mirror does not enforce it. Census on the merged tree of every series-level opacity literal: 0.4 (pin), 0.5 (plugin-report chartChrome test), 0.6 (docs) — the out-of-range class is still empty; main's window added no opacity/dashArray producer (diff grep empty). A free narrowing, not owed by this card; round 2 already ruled it non-blocking; nothing on the current tree changes that.
17 PR-body sentence "the lockfile moved on main, so pnpm install --offline --frozen-lockfile was re-run" WRONG (record), non-blocking pnpm-lock.yaml blob df8fbcd77 at 6eebc54b6, bc640ec56, 493b52eb3, 51f2a9634, 5f7e032a8; git log 6eebc54b6..bc640ec56 -- pnpm-lock.yaml is empty (lit control: registry.ts moved once in the same window). The last lockfile commit on main is 2f61238b9 (2026-09-04T17:10Z, #7670), which predates the PR's original base a472b0716 (git diff a472b0716 origin/main -- pnpm-lock.yaml empty). The lockfile has not moved at any point in this PR's life; the re-install was a no-op. No effect on the tree; a false process statement in the PR body and in dev report 5550321911, and the dispatcher's brief repeated it.

The disclosed control miss (item 7 of the brief), judged. My unstacked control (R1b) puts revenue's top edge at (53,5),(475,75); stacked (R1) it is (53,145),(475,180) — the revenue path does move under stacking, because the y-domain rescales from max 400 to 650 (revenue+cost). The author's first prediction was wrong for exactly the reason stated. The recast control is geometric and domain-independent: unstacked, both primaries' bottom edges sit on one baseline (y=285, one value for both) and cost's bottom ≠ revenue's top; stacked, cost's bottom equals revenue's top point-for-point. That distinguishes the two states without assuming any path is fixed, so it is sound, and the stacking reading is real.

The disclosed instrument fault (item 8), judged. Scratch tree at the head, git status empty, on-disk blobs equal to the commit blobs (9b356dc97, bf2680f6b, 123b480a0). Anchored counts on disk: Narrower condition than 1, EVERY mark family 1, fillOpacity. only and drops 1, read at \AdvancedChartImpl.tsx:96`1,chartType: 'area'2,dashArray: '8 4'1,dashArray: '4 4'0,only on a mark that has a stroke to dash1,on every mark family1,keeps every key in this example live1,variant: 'current'1,renderer does NOT drop1 — every claimed edit is present, every superseded phrasing is 0. The commit carries exactly the three files its message names; the untracked probe is not in it; the Zod.describe` strings are untouched as claimed (row 1). The PR body, read back, carries the round-3 section. No second instance of the "believed applied, not applied" shape exists in this round. The one false statement found (row 17) has the opposite shape — an action reported as necessary that was not — and it changed no bytes.

One instrument note of my own, reported as such. My first scatter leg mounted nothing: the shape used a categorical x and two series, and the scatter branch requires a numeric x and exactly one series (scatter-multi-series refusal otherwise). Re-run in the mounting shape (S1) with a lit control; the null was mine, not the renderer's.

② Semver grading

③ Boundary-flag disposition

Every card and PR below was read in this session; state and updated_at are from those reads.


VERDICT: PASS

Recorded, not blocking — no commit needed for either:

  1. PR body, one sentence (round-3 remediation, last paragraph): "the lockfile moved on main, so pnpm install --offline --frozen-lockfile was re-run in the worktree" is false — pnpm-lock.yaml is blob df8fbcd77 at every commit in this PR's history and last moved on main at 2f61238b9 (feat(scripts,components): gate unreferenced source files, and delete the one standing orphan #7670), before the PR's original base. Strike or correct it when the dispatcher flips the PR ready, so the record does not teach a lockfile movement that did not happen. Dev report 5550321911 carries the same sentence and cannot be edited; this report is its correction.
  2. finding(types): ChartDataSeriesSchema SILENTLY STRIPS six series keys the renderer reads — label, variant, opacity, dashArray, stack, yAxis #7546's and the PR's needs:contract-review labels are the dispatcher's to clear on adoption; the PR is one commit behind main (fix(types): declare the four alert-dialog keys the renderer reads (objectui#7104) #7700) with a clean dry-run merge and a green types suite on the merged tree — no re-merge owed before enqueue.

Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 5, 2026 07:54
@os-sam
os-sam added this pull request to the merge queue Sep 5, 2026
Merged via the queue into main with commit 8fe8e5c Sep 5, 2026
34 checks passed
@os-sam
os-sam deleted the claude/issue-7546-chart-series-silent-strip branch September 5, 2026 08:09
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(types): ChartDataSeriesSchema SILENTLY STRIPS six series keys the renderer reads — label, variant, opacity, dashArray, stack, yAxis

2 participants