Skip to content

fix(app-shell,plugin-list): retire the invented gallery cover binding, and draw the switcher only when it can switch (objectui#7547) - #8188

Merged
os-justin merged 4 commits into
mainfrom
claude/issue-7547-forced-path-invented-bindings
Sep 7, 2026
Merged

os-justin merged 4 commits into
mainfrom
claude/issue-7547-forced-path-invented-bindings

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Part of #7547.

⚠️ This PR delivers two of the card's three in-scope items — the gallery cover binding and the switcher predicate. The third, the map / chart invented-axis pair, was measured, found to have no refusal path below it, and split into two sub-issues rather than decided here. The reasoning is in "What is deliberately NOT here" below; it is the substance of this PR as much as the diff is, and a clean stop on a falsified route is the point rather than an apology for one.

The class

objectui#7029 (calendar), #7070 (gantt dates) and #7500 (timeline) each removed one member of a family: a relay that invents a binding name the author never wrote, which downstream is indistinguishable from a real one. #7547 is the survivors. They live only on the FORCED path — an authored type: or an interface page's leading whitelist entry — because the switcher path already builds its offer from PRESENT bindings.

Commit 1 — the switcher is drawn for the RESOLVED list, not the whitelist

showViewSwitcher reaches ListView already decided, and every face that decides it counts the LENGTH of appearance.allowedVisualizations. That is the whitelist BEFORE availableViews intersects it with the ADR-0047 capability gate. A view whitelisting ['grid', 'timeline'] with no timeline block whitelists two and resolves one, so the toolbar drew the collapsed switcher affordance, its separator and the border cluster around a single Grid entry that could not switch to anything.

Neither face can compute the right number — the gate lives in ListView — so the predicate is applied at the one site holding both halves (viewSwitcherOffered). One change, every door, and it cannot drift from the list the dropdown is handed. The relay sites keep their length test as the author's INTENT and each gains a note saying the whitelist is not the offer.

⭐ There are THREE faces carrying that length count, not the two the card names. Found by enumerating every ListView render site rather than grepping for the spelling — a population defined partly by omission (a site that passes nothing takes the default) cannot be measured by grepping the explicit form. The six real render sites:

render site opts in?
app-shell/src/views/ObjectView.tsx allowedVisualizations?.length ?? 0) > 1
app-shell/src/views/InterfaceListPage.tsx allowed.length > 1
app-shell/src/views/ObjectDataPage.tsx allowedVisualizations.length > 1 — the third, unlisted anywhere
app-shell/src/views/studio-design/StudioDesignSurface.tsx omits it; spreads an authored slot schema that may carry it
plugin-list/src/ListViewBlock.tsx omits it; forwards whatever the SDUI schema carries
apps/console/src/sdui-workbench-preview.tsx omits it — takes the default

All three are corrected by the single ListView-side predicate; ObjectDataPage gets the same INTENT note as its twins (comment only, no behaviour).

Commit 2 — the object page forwards a gallery cover binding, never invents one

options.gallery.imageField was floored at 'image' for every object view, declared or not.

⭐ What that literal actually broke is the GATE, not the screen. ObjectGallery resolves the cover per record and collapses the cover area when none resolves, so the invented name degraded to a gallery of coverless cards — which is why it survived three cards of this family. But ListView.availableViews asks schema.options?.gallery?.imageField, and a key this relay always supplied made that check answer YES for every object view whitelisting gallery. Whitelist ∩ resolvable — the mechanism that exists to stop a visualization being offered with nothing behind it — was answering about a name this file wrote rather than one the author did.

galleryViewOptions now mirrors ganttViewOptions next door: the view's own block spread whole, the two legacy cover spellings cross-filling each other only when one was declared, and titleField keeping its 'name' display floor (a display default is not a binding — the same rung objectui#3129 and #6557 established and #7070 kept).

⚠️ User-visible: a view that whitelisted gallery without a gallery: block loses the Gallery toggle. That is ADR-0047 working; it was only ever offered because this relay answered the gate on the author's behalf.

⚠️ The premise was measured BEFORE the deletion, which is the discipline objectui#7070 wrote down ("#7029's mechanic is only correct where a refusal path exists"). ObjectGallery keeps its own cover guess and that is correct there: it is the component's decision about an unconfigured record, and it is honest because the cover area collapses when the guess yields nothing.

Fixture triage — two positive controls RE-ANCHORED, not deleted

The source scans in ObjectView.calendarBinding-7029.test.tsx and ObjectView.ganttBinding-7070.test.tsx each carried a same-class positive control anchored on this PR's 'image' literal, with an explicit instruction to whoever retired it: re-anchor, do not delete, do not weaken to the machinery control. Both are re-anchored onto the chart branch's || 'value' measure floor — still present, same one-rung class, and deliberately left standing here. Both instructions are carried forward verbatim to whoever retires 'value'.

What is deliberately NOT here, and why

Census re-measured on this branch

Re-measured independently rather than trusted from the card or triage; every line number in both had moved. Three corrections to the card's accounting:

  • the chart class exists in three files (triage found the app-shell one), and the app-shell chart site carries both 'name' and 'value' — neither the card nor triage recorded the second;
  • the showViewSwitcher length count has three faces, not two (table above);
  • ⭐ the gallery cover invention has exactly ONE site, not three. The two sibling faces build their gallery block through defaultGalleryFromObject, which returns a cover only when the OBJECT declares an image-typed field and undefined otherwise — a derivation from the object definition, never a fabricated name, the same shape as detectStatusField under ADR-0085. Only ObjectView's || 'image' was inventing.

titleField … || 'name' is deliberately NOT in this class anywhere: it is the display-name floor objectui#3129 / #6557 established and #7070 explicitly kept ("a display-name default, not a date axis").

Tests

Everything turbo ls --affected names against 20316bac3, in one run: 845 test files, 10256 tests, 1 skipped, 0 failures. The affected set is 8 packages; the 5 that carry tests are @object-ui/app-shell (636), @object-ui/console (89), @object-ui/plugin-list (70), @object-ui/example-schema-catalog (29), @object-ui/plugin-map (20) and @object-ui/example-console-starter (1) — 845, which is the run's file count exactly. @object-ui/example-byo-backend-console and @object-ui/site carry none. Nothing in the affected set went unrun.

⚠️ One real failure was found and corrected along the way, not explained away. ObjectView.titleFieldConvergence.test.tsx (objectui#6557) pins all six titleField / labelField seams to ONE expression shape, using viewDef as the mechanical proxy for "view-declared". galleryViewOptions first read the rung off a local alias — same value, but it blinds that scan to exactly what it hunts. The pin was right and the code was wrong, so the code changed back to the family spelling; weakening the regex would have been the other way round.

New pins: ListView.switcherChrome-7547.test.tsx (8 cases) and ObjectView.galleryBinding-7547.test.tsx (13 cases). Every absence arm is paired with a control that whitelists the SAME types or declares the SAME block, so a fix that simply emitted nothing cannot pass both halves.

Reverse verification — direction predicted before running, then observed. Each mutation applied after committing; the anchor line printed before and after (not merely counted, since a no-op edit exits 0 and would read as a run); restore proved BY STATE, git hash-object equal to git rev-parse HEAD:PATH, plus git diff HEAD at 0 lines.

  • Restore {viewSwitcherOffered && ( to {showViewSwitcher && ( — anchor 1 before, 1 injected, 0 left. 4 failed / 4 passed: the reds are exactly the four arms asserting no chrome, the greens exactly the four controls.
  • Restore the || 'image' tail on the declaredImage rung — anchor 1 before, 1 injected. 6 failed / 7 passed: the reds are exactly the four "invents no cover field" arms plus the two source-scan cases, the greens exactly the declared-config controls, which are unobservable in either world. That asymmetry is the point.

Type-check — Done for @object-ui/app-shell, @object-ui/plugin-list, @object-ui/plugin-map (rc 0), and for @object-ui/console once its dependency closure is built. The script is tsc --noEmit && tsc -p tsconfig.test.json: tsconfig.json EXCLUDES tests and a dedicated test project includes them, chained from type-check and enforced by scripts/check-type-check-coverage.mjs. Proved by --listFiles rather than assumed: all three app-shell pins appear in the 4425-file test program and the gallery pin appears 0 times in the 3685-file source program; the switcher pin appears in plugin-list's 1495-file test program.

Lint — eslint --no-inline-config --format json over the 7 changed source files: array length 7, 0 errors, 400 warnings, all pre-existing classes. Measured per-rule against the same files at 20316bac3 via --stdin-filename: ListView.tsx and InterfaceListPage.tsx have a zero delta; ObjectView.tsx is +2 (no-explicit-any 143 to 144 and react-refresh/only-export-components 13 to 14), both from the new galleryViewOptions export — exactly the two warnings each of its three sibling helpers already contributes. No new rule appears anywhere. The narrowing to changed files is a measurement, not a sample: eslint.config.js extends tseslint.configs.recommended, NOT the type-checked preset, and declares no parserOptions.project / projectService, so no rule reads cross-file type information and this diff cannot move the verdict on a file it does not contain.

Gates — check-changeset-presence (8 source files of 2 released packages, 1 changeset), check-changeset-no-major, check:control-bytes, check:self-import, check:phantom-deps, check:vi-mock-specifiers, check:vi-mock-inherit, check:unreferenced-sources, and check-governed-queue-guard --test over all 9 changed paths (NOT GOVERNED) — all green. The repo-wide pnpm lint is CI's.

Clause-② — assessed NO, and both traps checked explicitly

No declared type or schema member moves and no @objectstack/spec change; what changes is that a renderer stops inventing a binding name and a switcher predicate narrows. Nothing that was accepted is now rejected at a validation boundary — the same metadata still validates and still renders; it is simply no longer offered a toggle it had no binding for. Behaviour change on a published renderer, not an accept-set change.

  • (a) name already declared elsewhere? git grep galleryViewOptions over the whole repo: the only non-test hits are its definition and its single call site, both in this file. No collision.
  • (b) export * widening the public face? packages/app-shell/src/index.ts contains zero export * statements — every export is an explicit named list, and none of the four *ViewOptions helpers appears in it. The package's only entry is ./dist/index.js from that file, and the sole export * from anywhere in the package's src is inside a test file. galleryViewOptions is module-scoped for its co-located regression suite and does not reach the published face — the same standing as ganttViewOptions, calendarViewOptions and timelineViewOptions.

No needs:contract-review label, on that measurement. ⚠️ Say so if you read it differently and I will attach it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S


Generated by Claude Code

…he whitelist

`showViewSwitcher` reaches `ListView` already decided, and both faces that
decide it — `app-shell/ObjectView` and `app-shell/InterfaceListPage` — count
the LENGTH of `appearance.allowedVisualizations`. That is the whitelist BEFORE
`availableViews` intersects it with the ADR-0047 capability gate, so a view
whitelisting `['grid', 'timeline']` with no timeline block whitelisted two,
resolved one, and drew switcher chrome (a collapsed "List" dropdown, its
separator and the border cluster) around a single Grid entry that could not
switch to anything.

Neither face can compute the right number: the gate that answers "does this
type resolve?" lives in `ListView`. So the predicate is applied at the one site
holding both halves (`viewSwitcherOffered`), which is also why one change
covers both doors and cannot drift from the list the dropdown is handed.

The two relay sites keep their length test — it is the author's INTENT — and
each gains a note saying the whitelist is not the offer.

Part of objectui#7547.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
…er invents one

`options.gallery.imageField` was floored at `'image'` for every object view,
declared or not — the last member on this face of the class objectui#7029
(calendar) and objectui#7070 (gantt dates) removed.

What it actually broke is the GATE, not the screen. `ObjectGallery` resolves the
cover per record and collapses the cover area when none resolves, so the
invented name degraded to a gallery of coverless cards. But `ListView`'s
ADR-0047 capability gate asks `schema.options?.gallery?.imageField`, and a key
this relay always supplied made that check answer YES for every object view
whitelisting `gallery`: whitelist AND resolvable — the mechanism that exists to
stop a visualization being offered with nothing behind it — was answering about
a name this file wrote rather than one the author did.

`galleryViewOptions` now mirrors `ganttViewOptions` next door: the view's own
block spread whole, the two legacy cover spellings cross-filling each other ONLY
when one was declared, and `titleField` keeping its `'name'` display floor.

The premise was measured before the deletion, as objectui#7070 requires:
`ObjectGallery` keeps its own `coverField ?? 'image'` rung and that is correct
there — it is the component's decision about an unconfigured record, and it is
honest because the cover area collapses when the guess yields nothing.

The `'image'` positive controls in the objectui#7029 and objectui#7070 source
scans are RE-ANCHORED onto the chart branch's `|| 'value'` measure floor, per
the instruction each of them carries. They are not deleted and not weakened.

Part of objectui#7547.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
…ly spelling

`ObjectView.titleFieldConvergence.test.tsx` pins all six `titleField` /
`labelField` seams to ONE expression shape — a chain of VIEW-declared rungs
floored at 'name', with `viewDef` as the mechanical proxy for "view-declared".
`galleryViewOptions` read the rung off its local `gallery` alias, which resolves
the same value but blinds that scan to exactly what it hunts.

The pin is right and the code was wrong: restoring `viewDef?.gallery?.titleField`
costs nothing and keeps the scan making its claim. Weakening the regex to accept
an arbitrary local would have been the other way round.

Part of objectui#7547.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
…DataPage)

Comment only, no behaviour. objectui#7547 named two faces computing
`showViewSwitcher` from the whitelist LENGTH; enumerating every `ListView`
render site instead of grepping the spelling turns up a third,
`ObjectDataPage.tsx`, with the identical expression.

All three are already corrected by the `viewSwitcherOffered` predicate in
`ListView` — that is the point of fixing it at the site that holds both halves —
so this adds the same INTENT note its two twins carry rather than a fix.

The same enumeration cleared the gallery axis on both sibling faces:
`defaultGalleryFromObject` returns a cover only when the OBJECT declares an
image-typed field and `undefined` otherwise, so neither invents a name. Only
`ObjectView`'s `|| 'image'` was fabricating.

Part of objectui#7547.

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

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3189.7 KB 3191.4 KB
Main entry chunk (gzip) 144.0 KB 350 KB
Entry file index-D7JFqAqe.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) 498.05KB 113.95KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 187.85KB 52.13KB
fields (index.js) 243.04KB 61.36KB
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) 6.57KB 2.76KB
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.84KB 10.94KB
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.16KB 3.68KB
plugin-calendar (index.js) 47.29KB 13.18KB
plugin-charts (index.js) 70.62KB 19.71KB
plugin-chatbot (index.js) 193.54KB 46.04KB
plugin-dashboard (index.js) 131.41KB 34.43KB
plugin-designer (index.js) 211.51KB 43.01KB
plugin-detail (index.js) 247.68KB 63.49KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 52.46KB 14.57KB
plugin-list (index.js) 113.37KB 27.73KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.48KB 20.81KB
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) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
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) 13.64KB 4.59KB
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 (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 (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

Contract review — PASS. Clause-② does not bind. Held on CI, and on being a partial delivery by design.

PM seat, session session_01YBWFb5YgMU5dw8p2VKj16S. Tier note: the designated contract-review tier is quota-exhausted this session (rate_limit, HTTP 429); this ran at the default tier under the standing exemption. ⛔ Not claiming otherwise.

Clause-② — re-measured rather than accepted

The diff adds exactly one export, galleryViewOptions. Verified independently:

packages/app-shell/src/index.ts  ·  `export *` count : 0
galleryViewOptions on origin/main                    : absent
  └ control fires — defaultGalleryFromObject exists in 3 files on main
index.ts in the changed-file list                    : not present

With zero star re-exports and no touch to index.ts, a new module-level export cannot reach the published face. Both traps the dispatch named were checked, and the no-prior-declaration claim carries a lit control rather than a bare zero. needs:contract-review correctly absent.

⭐ The best thing in this run: it broke a pin and fixed the code

The app-shell suite failed on ObjectView.titleFieldConvergence.test.tsx (objectui#6557) — a real failure this work introduced. That pin holds all six titleField/labelField seams to one expression shape, using viewDef as the proxy for "view-declared"; the new helper read the rung off a local alias — same value, but it blinded the scan.

The pin was right and the code was wrong, so the code went back to the family spelling (f05901278). As the report puts it: "Weakening the regex would have been the other way round." That is the correct instinct and the one most often got backwards — a pin that catches your own change is doing its job, not obstructing it.

⭐ The dispatch's omission warning found a third defect site

I have been propagating one method rule all session: a grep for an explicit spelling cannot measure a population defined by omission — enumerate the sites, then ask which opt in. Applied here to the six ListView render sites, it turned up ObjectDataPage.tsx:339 carrying the identical length count — listed by neither the card, nor triage, nor my dispatch. Already corrected by the ListView-side predicate.

The same enumeration also cleared two sibling faces on the gallery axis, which derive covers via defaultGalleryFromObject and never name a field the object lacks. A census that only finds problems is not a census; this one discriminated in both directions.

⛔ The card's prescribed route for the map axis is FALSIFIED — and refusing it was right

ObjectMap.getMapConfig carries a documented default branch guessing latitude / longitude / location, kept deliberately by objectui#5953. The relay floor forces the flat branch and suppresses the lat/lng half — so deleting it, as the card directed, would make the tree invent three names where it currently invents one.

Shipping the prescribed route would have made this defect worse. Filing #8169 for a ruling instead of executing it is exactly the behaviour the dispatch asked for when it said the premise might be wrong and I wanted to hear that rather than a patch. Graded needs-user-decision / p2.

Verification worth noting

  • 845 test files / 10 256 tests passed in one run over the full affected set, with the arithmetic shown (app-shell 636 + console 89 + plugin-list 70 + schema-catalog 29 + plugin-map 20 + console-starter 1) so "nothing unrun" is checkable rather than asserted.
  • Test type-checking proven, not claimed: --listFiles shows all three app-shell pins in the 4425-file test program and the gallery pin zero times in the 3685-file source program. That is the right way to answer "are the tests actually type-checked".
  • Lint reported as a per-rule delta against base — zero for two files, +2 for ObjectView.tsx, both from the new export and both matching what each sibling helper already contributes.

Status: partial by design

This card is rework, not done. Two of four axes ship here (switcher, gallery); the remaining two are #8168 (chart, graded p2) and #8169 (map, needs-user-decision), with #8170 as the calendar rider (p3). #7547 stays open until #8168 and #8169 are resolved — ⛔ do not close it on this merge.

Held on CI convergence: at review time Lint, Type Check, all four shards and Live E2E were still in_progress.


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

Development

Successfully merging this pull request may close these issues.

2 participants