Skip to content

fix(plugin-list,plugin-view): deliver an authored map style to the renderer as mapStyle - #9999

Merged
os-tesla merged 2 commits into
mainfrom
claude/issue-9950-map-style-flatten
Sep 19, 2026
Merged

os-tesla merged 2 commits into
mainfrom
claude/issue-9950-map-style-flatten

Conversation

@os-tesla

Copy link
Copy Markdown
Collaborator

Fixes #9950

ObjectMapConfigSchema declares style — "MapLibre style URL/spec (overrides the public demo default)" — and on the list-view and object-view paths it was dropped before the renderer ever saw it. A view authoring map: { style: 'https://tiles.example.com/style.json' } parsed green, nothing refused it, nothing warned, and the map painted MapLibre's public demo tiles.

Readings re-taken on this branch's base (1ed2e69fa)

site line shape
packages/types/src/zod/objectql.zod.ts 1565 the declaration — style: z.string().optional().describe('MapLibre style URL/spec (overrides the public demo default)')
packages/types/src/zod/objectql.zod.ts 1666 ObjectMapSchema.mapStyle — same describe text, verbatim; the top-level spelling
packages/plugin-list/src/ListView.tsx 67 → 75 FLAT_MAP_CONFIG_KEYS, hand-listed, typed by an Omit of ObjectMapConfig by 'style'
packages/plugin-view/src/ObjectView.tsx 109 → 117 same, hand-listed
packages/plugin-map/src/ObjectMap.tsx 138 FLAT_MAP_CONFIG_KEYS — DERIVED from ObjectMapConfigSchema.shape, then .filter(key !== 'style')

All four reproduce. The card said "both hand-listed flatteners"; there are three flatteners and the third one derives.

⭐ Why both pins were GREEN while the key was dropped

The dispatch turns on this, so here it is first-hand. Each pin derived its comparison set like this — ListView.mapFlatten.test.tsx:155 and ObjectView.mapFlatten.test.tsx:190, identical text in both:

const declared = Object.keys(ObjectMapConfigSchema.shape)
  .filter((key) => key !== 'style')
  .sort();
expect([...FLAT_MAP_CONFIG_KEYS].sort()).toEqual(declared);

declared is not the declaration. It is the declaration with the same key subtracted from it that the whitelist was missing. The type Omit of ObjectMapConfig by 'style' is the compile-time half of that subtraction and .filter((key) => key !== 'style') is the runtime half; the card named the type, and the .filter in the test is what actually kept the assertion green. A pin narrowed to match the bug reports "the list is correct" to the next reader, which is exactly what the card observed happening.

⇒ this is not "a key was forgotten". Fixing the flattener without fixing the pin would have gone green again for the same wrong reason, and the next dropped key would have repeated the card.

The fix

Each flattener's whitelist becomes a total spelling table:

export const FLAT_MAP_CONFIG_SPELLING = {
  latitudeField: 'latitudeField',
  // …
  center: 'center',
  style: 'mapStyle',
} as const satisfies Record[keyof ObjectMapConfig, string];

The satisfies clause is written with SQUARE brackets in this body on purpose: GitHub's body sanitizer eats angle-bracket-shaped fragments, backticks and fences included, so a literal type-argument list here would render as if nothing had been declared. The real source carries the ordinary type-argument brackets.

pickFlatMapConfig walks that table's entries and writes source[declaredName] under flatName.

Why mapStyle and not style. The reason the whitelist exists, in the source file's own words (objectui#5177): "style is ALSO BaseSchema.style (inline CSS, legal on every node), and spreading the raw map block collapsed the two namespaces onto one key." Passing style through unrenamed would re-open precisely that collision. ObjectMap.getMapConfig (ObjectMap.tsx:377) reads

const style: string | undefined = schema.mapStyle || schema.map?.style;

and deliberately reads no top-level style (objectui#5017 — warnOnTopLevelStyleUrl exists to say so out loud). mapStyle is a declared member of ObjectMapSchema, so the flat product stays inside the declaration at both ends. Honouring the declaration and keeping the collision shut were never in tension — the old flattener just did neither for this key.

Why not the derived shape ObjectMap.tsx:138 uses (the dispatch asks me to say why if I don't): both ListView.tsx and ObjectView.tsx are reachable from examples/console-starter's own src/, so they are in the import graph examples/console-starter/test/vite-alias-closure.test.ts walks. That walker resolves a bare @object-ui/* specifier with plain index.EXT conventions and cannot find @object-ui/types/zod's actual barrel (zod/index.zod.ts, a non-standard name) — a real runtime import there reproducibly fails that gate (PR #5231, CI run 32160288416). ObjectMap.tsx gets away with it only because nothing in console-starter's graph reaches @object-ui/plugin-map. I re-read the walker (resolveModule, which tries the target, then extensions, then index.EXT) and the constraint still holds, so the table stays hand-written — but it is now total, which the old list was not.

The pin, rebuilt to measure a relation

Both suites now assert, over Object.keys(ObjectMapConfigSchema.shape) read whole: every declared key is delivered by the flatten under its flat spelling. There is no literal key list left to narrow. The probe config is built by asking the declaration itself which value shapes each member accepts (safeParse against three candidates), so a key added later with an unfamiliar shape fails loudly instead of being skipped, and the config is asserted to parse green first — the card's own shape.

Four controls travel in the same body:

  • instrument-can-fail — the pre-fix whitelist (the same key !== 'style' subtraction) is fed to the same assertion, and the undelivered set comes back as exactly ['style']; the assertion itself is shown to throw.
  • the narrowed set — reproduced from the pin being replaced, and shown to be the set against which the pre-fix list was a perfect match.
  • single-key case — a config authoring only style still delivers it. That is how a whitelist drops one key silently.
  • no undeclared key is forwarded — measured as the set of keys the map block contributes (a fully authored block's product minus an empty block's product), asserted equal to the image of the spelling table: nothing undeclared added, nothing declared missing, both directions in one assertion. totallyUndeclaredKey, style2 and mapStyle-written-inside-the-block are all confirmed dropped — the whitelist is keyed on declared source names, so only style produces the mapStyle output.

Coverage note: cleanup() between the two renders in that last test is load-bearing. Without it the spy collects from both mounted trees and the baseline read is the other config's product — measured: the contributed set came back empty and the assertion silently compared a product to itself.

Ablation — red leg and restore

Run from the committed fix (c20cbd03d), with the mutation planted and restored by scripts/ablation-replace.mjs, which verifies on disk rather than by exit code.

Mutation: delete the single line style: 'mapStyle', from both tables. Anchor hit exactly once per file, and the blob moved:

ListView.tsx    anchor x1 -> x0   blob 85b21312fc53 -> e5609e747c7d
ObjectView.tsx  anchor x1 -> x0   blob 35c52ff09623 -> 680a55855271
on-disk occurrences of `style: 'mapStyle',` under mutation: 0 and 0

RED leg 1 — vitest, exit 1, 12 failures across the two files, including:

every declared ObjectMapConfigSchema key is delivered by the flatten (objectui#9950)
  > delivers every declared key, from a config carrying every declared key
    AssertionError: expected [ 'style' ] to deeply equal []
  > a config authoring ONLY `style` still delivers it
    AssertionError: expected undefined to be 'https://tiles.example.com/only.json'
FLAT_MAP_CONFIG_SPELLING pins against ObjectMapConfigSchema
  > covers the declaration in full
    AssertionError: expected [ …(5) ] to deeply equal [ …(6) ]

RED leg 2 — type-check, exit 2. The table's type is total, so the missing entry is a compile error, not just a test failure:

packages/plugin-view type-check: src/ObjectView.tsx(138,12): error TS1360:
Type '{ readonly latitudeField: "latitudeField"; … readonly center: "center"; }'
does not satisfy the expected type 'Record[keyof ObjectMapConfig, string]'.

(the TS1360 text is quoted with square brackets in place of the type-argument brackets, same sanitizer reason as above)

Restore — proven by blob hash and an empty diff, ⛔ not by an exit code:

ObjectView.tsx  blob after restore 35c52ff09623… == blob at HEAD 35c52ff09623…   git diff HEAD: empty
ListView.tsx    blob after restore 85b21312fc53… == blob at HEAD 85b21312fc53…   git diff HEAD: empty

Re-checked in a separate shell afterwards: git status --porcelain empty, git diff HEAD --stat empty, and the anchor back at 1 occurrence in each file.

Stop conditions, answered on the record

  • "style already reaches the renderer on either path" — rejected. On the base, neither flattener emits style or mapStyle; the pre-fix probe above shows schema.mapStyle arriving as undefined. The card's own carve-out stands: a hand-written object-map node with a map block does deliver map.style, because getMapConfig reads that block directly. The flattened path is the broken one, and only it is changed here.
  • "forwarding style requires editing the schema or the map renderer" — it does not. mapStyle is already declared (objectql.zod.ts:1666) and already read (ObjectMap.tsx:377). Both files stay read-only in this PR; the diff is four files plus the changeset.
  • "the Omit by 'style' is load-bearing for a reason the card does not know" — it is load-bearing, and the card half-knew: it asked "by what route, since it is not a flat field like the others". The reason is the BaseSchema.style namespace collision, and the answer is the rename, not a widened whitelist. The exclusion survives in substance — the flatten still never writes a top-level style, pinned in both suites — it just no longer means "discard".

Tests run

  • pnpm exec vitest run packages/plugin-list/src/__tests__/ListView.mapFlatten.test.tsx packages/plugin-view/src/__tests__/ObjectView.mapFlatten.test.tsx — 24 passed (12 on the base, before the new coverage).
  • pnpm --filter @object-ui/plugin-list --filter @object-ui/plugin-view run type-check — both Done (each runs tsc --noEmit plus tsc -p tsconfig.test.json), after building the dependency closure.
  • pnpm --filter '@object-ui/plugin-list^...' --filter '@object-ui/plugin-view^...' run build — exit 0.
  • node scripts/check-changeset-presence.mjs, check:control-bytes, check:changeset-claims, check:pending-changeset-literals, check:new-line-citations, check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:shell-escape-residue — all exit 0.

Acceptance notes

Two things measured beside this card, reported rather than ridden on. Both live in packages/plugin-map/src/ObjectMap.tsx, which this dispatch declares read-only, so neither is touched here.

  1. warnOnTopLevelStyleUrl's remedy text points at a spelling that is silently dropped. The warning ends: "On a view, note that options.map is FLATTENED into the top level, so its style lands here as this same top-level key — spell it map: { mapStyle } there." But mapStyle is not a member of ObjectMapConfigSchema, so it is not in either flattener's whitelist (before or after this PR) and getMapConfig reads schema.map?.style, never schema.map?.mapStyle. An author who follows that sentence writes a key nothing reads. After this PR the correct advice for a view is the plain declared spelling, map: { style: 'URL' }, which now works.
  2. ObjectMap.tsx:133-136 states that ObjectView / ListView "derive their own flatten whitelist from this exact schema (imported from @object-ui/types/zod)". They do not and deliberately must not — see the alias-closure constraint above. Probe: occurrences of @object-ui/types/zod in either file, on this branch, is 0; the two files' own docblocks say the opposite of ObjectMap.tsx's claim.

Authored by the domain:ui#2 execution seat in session session_018HrVaotisyhgmot9o2MLRq (written into the prose as well as the footer, because a PATCH of this body would downgrade the footer's session reference).


Generated by Claude Code

…Style`

`ObjectMapConfigSchema` declares `style`, and both view flatteners dropped it
before the renderer saw it: a view authoring `map: { style: '<url>' }` parsed
green and the map painted MapLibre's public demo tiles (objectui#9950).

Each flattener's whitelist becomes a TOTAL spelling table,
`FLAT_MAP_CONFIG_SPELLING`, mapping every key the declaration carries to the
name the internal flat form uses. Identity everywhere except `style`, which
travels as `mapStyle` — the spelling `getMapConfig` reads first
(`schema.mapStyle || schema.map?.style`) and a declared member of
`ObjectMapSchema`. The top-level `style` namespace stays the base face's
inline CSS, so the collision objectui#5177 closed stays closed, and an
undeclared key in the block still never reaches the product.

Both anti-drift pins were green while the key was dropped: each compared its
hand list against `Object.keys(ObjectMapConfigSchema.shape).filter((key) =>
key !== 'style')`, a comparison set narrowed by the same subtraction the
defect was made of. They now assert the relation "every declared key is
delivered under its flat spelling" against the declaration read whole, with a
control in the same body that feeds the pre-fix whitelist to that assertion
and shows it rejected. The table's `satisfies Record<keyof ObjectMapConfig,
string>` makes the coverage a typecheck failure as well.

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

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 2 address(es) in this pull request's own prose name a tree it replaced

Each was read from a tree this change itself moves, so a reader who follows it lands somewhere else. ⛔ Nothing here blocks and nothing here says the sentence is false — the question asked is arithmetic: does this diff move the line that number points at?

  • in this body, ListView.mapFlatten.test.tsx:155 — this change rewrites packages/plugin-list/src/__tests__/ListView.mapFlatten.test.tsx:155

    Each pin derived its comparison set like this — ListView.mapFlatten.test.tsx:155 and ObjectView.mapFlatten.test.tsx:190, identical text in both:

  • in this body, ObjectView.mapFlatten.test.tsx:190 — this change rewrites packages/plugin-view/src/__tests__/ObjectView.mapFlatten.test.tsx:190

    Each pin derived its comparison set like this — ListView.mapFlatten.test.tsx:155 and ObjectView.mapFlatten.test.tsx:190, identical text in both:

The repair is not to correct the number. Changing :246 to :274 is true today and born false again on the next insertion — objectui#9509 states that before anything else. Bind the number to the tree it was read from (`:246` at `b8a006883d`, `:274` at this head), which cannot re-stale because each number names its own tree; or state a rule instead of a coordinate, the way objectui#9495 replaced a file count with "every file in git diff --name-only against the merge base".

⚠️ 10 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/6726-find-envelope-records-arms.md

  • names plugin-view/src/ObjectView.tsxpackages/plugin-view/src/ObjectView.tsx — edited by this change

    | module | what it does | | --- | --- | | components/src/hooks/related-count-store.ts | related-list tab badge count | | components/src/renderers/basic/data-list.tsx | element:repeater rows | | components/src/renderers/basic/elements.tsx | element:number client-side aggregate | | components/src/renderers/basic/record-picker.tsx | element:record_picker options | | plugin-detail/src/renderers/record-activity.tsx | record:activity self-fetch | | plugin-detail/src/renderers/record-history.tsx | record:history self-fetch | | plugin-view/src/ObjectView.tsx | non-grid (kanban / calendar / gallery / timeline) fetch |

.changeset/7070-no-invented-gantt-date-fields.md

  • names plugin-list/src/ListView.tsxpackages/plugin-list/src/ListView.tsx — edited by this change

    • app-shell/src/views/ObjectView.tsx — the console object page. The inline branch becomes ganttViewOptions, the sibling of calendarViewOptions and timelineViewOptions: the declared block spread whole, title floored at 'name', no date field invented. - plugin-list/src/ListView.tsx — the render branch AND the capability gate. - plugin-view/src/ObjectView.tsxgenerateViewSchema, the authored object-view element route, which bypasses ListView entirely.
  • names plugin-view/src/ObjectView.tsxpackages/plugin-view/src/ObjectView.tsx — edited by this change

    • app-shell/src/views/ObjectView.tsx — the console object page. The inline branch becomes ganttViewOptions, the sibling of calendarViewOptions and timelineViewOptions: the declared block spread whole, title floored at 'name', no date field invented. - plugin-list/src/ListView.tsx — the render branch AND the capability gate. - plugin-view/src/ObjectView.tsxgenerateViewSchema, the authored object-view element route, which bypasses ListView entirely.

.changeset/7499-gantt-non-axis-floors-omitted.md

  • names plugin-list/src/ListView.tsxpackages/plugin-list/src/ListView.tsx — edited by this change

    • plugin-list/src/ListView.tsx — the object-gantt render branch. - plugin-view/src/ObjectView.tsxgenerateViewSchema, the authored object-view element route, which bypasses ListView entirely.
  • names plugin-view/src/ObjectView.tsxpackages/plugin-view/src/ObjectView.tsx — edited by this change

    • plugin-list/src/ListView.tsx — the object-gantt render branch. - plugin-view/src/ObjectView.tsxgenerateViewSchema, the authored object-view element route, which bypasses ListView entirely.

.changeset/7773-kanban-adapter-groupfield-write.md

  • names ListView.tsxpackages/plugin-list/src/ListView.tsx — edited by this change

    Who is NOT affected — the boundary is node-local. Every VIEW-LEVEL groupField read is untouched and still live: it is a legacy alias of the spec's groupByField on the kanban view config, mapped by normalize-list-view.ts, and both adapters still resolve lanes through it (ObjectView.tsx's kanbanCfg.groupField ||, ListView.tsx's groupByField || groupField). Authoring options.kanban.groupField on a list-view or object-view keeps working exactly as documented in packages/plugin-list/README.md. groupField is dead only on the generated object-kanban NODE.

.changeset/7780-object-kanban-record-source.md

.changeset/8653-listview-title-retired-rowactiondefs-pinned.md

  • names packages/plugin-view/src/ObjectView.tsxpackages/plugin-view/src/ObjectView.tsx — edited by this change

    titleretired. ListView resolved its export filename through schema.label || (schema as any).title. @objectstack/spec/ui's ListViewSchema refuses title by name (unrecognized_keys: ['title']) while ObjectGridPropsSchema accepts it; packages/types mirrors the platform contract rather than ruling over it, so declaring title on ListViewSchema would have made this repo accept what the platform save gate rejects. That asymmetry is also why objectui#6639 could take the declare branch for ObjectGridSchema.title one package over and this site could not. A parse-based census of apps/ examples/ content/ and packages/ found zero list-view nodes authoring title, so the retirement costs no author a filename. Over that same corpus the instrument reports three object-grid nodes carrying the key: two authored ones, both in content/docs/api/schema-reference.md, plus one that is not authored at all — packages/plugin-view/src/ObjectView.tsx composes title: schema.table?.title onto a grid node it builds, so it is a producer writing the key rather than an author declaring it. ObjectGrid's own title reads are untouched — they remain declared, ruled and read.

.changeset/8990-object-kanban-groupby-optional.md

  • names packages/plugin-list/src/ListView.tsxpackages/plugin-list/src/ListView.tsx — edited by this change

    • packages/plugin-list/src/ListView.tsx generates the node as groupBy: laneField. objectDef loads asynchronously, so laneField is undefined on every load until it lands, and stays undefined whenever the object offers no stageField hint and none of status / stage / state / phase. The renderer serves that node; both published faces refused it. - content/docs/utilities/data-objectstack.mdx documents an object-kanban node that is exactly { type, dataSource }, with no groupBy. ⚠️ This one is weaker and is cited for what it is: that fragment is still refused after this change, at RECORD_SOURCE_REQUIRED, because dataSource is not a rung of the record-source ladder. It shows a lane-less board is a documented authoring; it is not a document this change admits.

.changeset/list-user-actions-collision-5398.md

  • names ListView.tsxpackages/plugin-list/src/ListView.tsx — edited by this change

    The harvest now reads the object block only. Both userActions read sites in ListView.tsx carry a comment naming the collision, and __tests__/ListView.userActionsCollision.test.tsx pins each clause of it: the two shapes, a producer that manufactures the view one, the harvest's blindness to it, and the projection that must keep the object's operand with a toolbar block — or an empty block — present on the view.

.changeset/listview-comment-pair-4559.md

  • names ListView.tsxpackages/plugin-list/src/ListView.tsx — edited by this change

    Two comment corrections in ListView.tsx (objectui#4559, objectui#4966). No runtime behaviour changes and the emitted bundle is byte-identical; the published .d.ts does change, which is why this is a patch rather than an empty frontmatter.

.changeset/object-view-unmirrored-keys-7779.md

  • names packages/plugin-view/src/ObjectView.tsxpackages/plugin-view/src/ObjectView.tsx — edited by this change

    What was measured. Every reading was taken on the object-view node renderer (packages/plugin-view/src/ObjectView.tsx, registered by plugin-view/src/index.tsx) with schema.objectName / schema.layout as the positive controls of the same schema.KEY query, so each zero is a reading; the repo-wide census of viewTabBar finds the key in no source file outside @object-ui/types (two doc tables listed it as authorable and are corrected here). The spec side was read through the installed pin (@objectstack/spec@17.2.0, ui entry, 117 exported object schemas walked; control keys objectName / columns / navigation / listViews hit): the three spec-modelled keys are optional slots on ListViewSchema and ObjectListViewSchema; the six local keys have no spec slot anywhere.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 76f1543ab (merge-base with origin/main): 4 file(s) changed outside .changeset/, read against 1206 pending declaration(s) that publish a body (1765 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3054.5 KB 3104.5 KB
Main entry chunk (gzip) 145.7 KB 350 KB
Entry file index-DAy7rgSO.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) 16.69KB 6.21KB
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) 11.08KB 4.58KB
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) 545.92KB 130.72KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 216.90KB 60.15KB
fields (index.js) 249.62KB 63.02KB
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) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
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.83KB 10.95KB
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) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
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) 8.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.92KB 14.22KB
plugin-charts (index.js) 71.73KB 20.08KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 132.96KB 35.17KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 254.30KB 66.21KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 137.49KB 34.64KB
plugin-gantt (index.js) 167.62KB 41.26KB
plugin-grid (index.js) 213.44KB 58.21KB
plugin-kanban (index.js) 48.10KB 14.94KB
plugin-list (index.js) 113.55KB 27.99KB
plugin-map (index.js) 21.48KB 6.99KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.18KB 21.05KB
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) 109.04KB 36.08KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.38KB 1.98KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.74KB 2.54KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 15.71KB 5.30KB
types (ai.js) 4.11KB 2.06KB
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.93KB 1.49KB
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 (expression.js) 0.20KB 0.18KB
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 (strict-authoring-face.js) 14.04KB 5.36KB
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

@os-tesla
os-tesla marked this pull request as ready for review September 19, 2026 08:05
@os-tesla
os-tesla added this pull request to the merge queue Sep 19, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3055.1 KB 3104.5 KB
Main entry chunk (gzip) 145.7 KB 350 KB
Entry file index-Lzd7vY0S.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) 16.69KB 6.21KB
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) 11.08KB 4.58KB
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) 545.92KB 130.72KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 216.90KB 60.15KB
fields (index.js) 249.62KB 63.02KB
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) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
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.83KB 10.95KB
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) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
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) 8.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.92KB 14.22KB
plugin-charts (index.js) 71.73KB 20.08KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 132.96KB 35.17KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 254.30KB 66.21KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 138.95KB 35.18KB
plugin-gantt (index.js) 167.62KB 41.26KB
plugin-grid (index.js) 213.44KB 58.21KB
plugin-kanban (index.js) 48.10KB 14.94KB
plugin-list (index.js) 113.55KB 27.99KB
plugin-map (index.js) 21.48KB 6.99KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.18KB 21.05KB
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) 109.04KB 36.08KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.38KB 1.98KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.74KB 2.54KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 15.71KB 5.30KB
types (ai.js) 4.11KB 2.06KB
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.93KB 1.49KB
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 (expression.js) 0.20KB 0.18KB
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 (strict-authoring-face.js) 14.04KB 5.36KB
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

Copy link
Copy Markdown
Collaborator Author

Test (shard 1/4) is red, and it is ⛔ not this PR's — root-caused and filed as objectui#10006

domain:ui#2 seat, 2026-09-19T09:12Z. Recorded here rather than acted on silently, because this PR was
ejected from the merge queue over it.

What happened

event read 2026-09-19T09:12Z
queued 2026-09-19T08:05:27Z
ejected 2026-09-19T08:26:16Z — removed_from_merge_queue with ⛔ no merged beside it
cause the batch branch gh-readonly-queue/main/pr-10000-12b466ae5… (this PR + objectui#10000) failed Test (shard 1/4), job 105866539312
the queue then re-formed with objectui#10000 alone on 1ed2e69fa, which passed and merged

This branch has since been updated onto current main (76f1543ab) and re-run alone at head
8defe49cb: shards 2, 3 and 4 pass, shard 1 fails again — on a different file and a different
divergence.

run failing assertion, both upstream-port-parity-wiring.test.ts:280
batch, job 105866539312 no listing line for **worktree-rule-card-ref** under **.claude/hooks/guard-main-checkout-bash.sh**
this PR alone, job 105871105635 no listing line for **incident-first-person** under **.claude/hooks/guard-tree-enum.selftest.sh**

Why it is not this PR's

  • This PR's whole diff is five files under packages/plugin-list and packages/plugin-view plus
    one changeset. It touches ⛔ neither .claude/hooks/** nor scripts/**.
  • main CI run 35432852669 on 76f1543ab is completed success — this same test passes there.
  • objectui#10000 alone and objectui#10001 both passed the same test on the same base.
  • ⭐ The two failures name different (file, divergence) pairs. A real pin-vs-tree mismatch names
    the same pair every time; two different pairs is the reading being unstable, ⛔ not the tree.

Root cause, located — ⛔ filed, not called a flake

scripts/check-upstream-port-parity.mjs:903-:912 runs process.exit(list()). With stdout on a
pipe — which is exactly how the test captures it, execFileSync('node', [GATE, '--list'], …) at
upstream-port-parity-wiring.test.ts:244 — Node's buffered stdout writes are not flushed by
process.exit, so the listing is truncated at a point that depends on runner load. list() at
:556 is a plain deterministic loop, so the gate's DECISION is stable and only its DELIVERY is not.
Full evidence and a suggested direction: objectui#10006. ⛔ Not this lane's surface to fix.

What this PR does next

Spending the one permitted re-run on the failed job. Green ⇒ re-queue. Red again on the same test
⇒ ⛔ no second re-run and ⛔ no blind re-queue: it goes back to objectui#10006 as a blocker.
⛔ No test is skipped, disabled or quarantined here, and nothing in this PR changes to route around it.


Generated by Claude Code

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

Projects

None yet

2 participants