Skip to content

refactor(plugin-dashboard): drop the unreachable DatasetWidget arm from DashboardRenderer's self-contained branch - #5604

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-4620-dashboard-unreachable-arm
Aug 21, 2026
Merged

refactor(plugin-dashboard): drop the unreachable DatasetWidget arm from DashboardRenderer's self-contained branch#5604
os-sales merged 1 commit into
mainfrom
claude/issue-4620-dashboard-unreachable-arm

Conversation

@os-sales

Copy link
Copy Markdown
Collaborator

Fixes#4620

Deletion-class change: removes a JSX branch that cannot execute. Behaviour is unchanged.

Reachability, re-derived on current origin/main

Line numbers in the card had drifted; these are the two lines as read at merge-base 7a28e1e3f, in packages/plugin-dashboard/src/DashboardRenderer.tsx:

  • :850const isSelfContained = widget.type === 'metric' && !datasetBound;
  • :888{datasetBound opening the inner fork inside the isSelfContained arm of renderedNode (which begins at :882)

datasetBound is bound exactly once for this scope, at :570 (const datasetBound = !!widget.dataset;), inside renderWidget which opens at :545. It is a const, never shadowed and never reassigned — its only other reads were :842, :850, :888 and :917. So reaching the truthy side of the :888 fork requires datasetBound true, while reaching the enclosing arm at all requires isSelfContained true, which requires datasetBound false. The arm was unreachable by construction.

The guard on isSelfContained still carries its && !datasetBound clause, i.e. the shape has not changed since the card was filed, so this stayed a deletion rather than becoming a behaviour question.

The change

The self-contained arm now renders SchemaRenderer unconditionally, with a comment naming the invariant (isSelfContained implies !datasetBound) so the next person mirroring this shape does not re-add the dead limb — that re-addition is the cost the card documents, already paid once by the PR that mirrored this fork onto DashboardGridLayout.

The reachable fork in the Card branch (now at :917) is untouched. It is correct and it is the one giving a dataset-bound metric its title and border chrome.

DatasetWidget and effectiveWidget both remain referenced by that Card-branch fork, so nothing became unused and the module graph is unchanged.

Evidence: the Card path is pinned by existing coverage, and the pin bites

The card asserted that existing dataset-bound-metric coverage already pins that such a widget takes the Card path. Verified rather than taken on trust — the test is:

packages/plugin-dashboard/src/__tests__/DashboardRenderer.filters.test.tsx at :162, "injects the merged filter into a dataset widget's runtimeFilter". It renders DashboardRenderer with { id: 'w1', type: 'metric', dataset: 'sales', values: ['revenue'], filter: { stage: 'won' } } (:171) and asserts queryDataset was called with the merged runtimeFilter. isSelfContained is false for that widget, so the assertion can only pass by way of the Card-branch fork.

Reverse-verification, to show the pin actually bites rather than merely mentioning a dataset metric — the reachable Card-branch fork was forced to its SchemaRenderer side on disk and the file restored via trap ... EXIT INT TERM:

legresult
baselineTest Files 1 passed (1) · Tests 5 passed (5)
Card fork mutatedTest Files 1 failed (1) · Tests 5 failed (5), the named test among them
restoredgit status --porcelain empty, git diff --stat empty

Both legs were proven on disk with anchored grep -c in both directions (injected marker 1 then 0; the removed guard line 0 then 1) plus git diff --stat. No rebuild was needed for this ablation: the test reaches the subject through the relative source specifier ../DashboardRenderer, not through the package's exports/dist.

No new test is added. A test asserting that deleted code does not run would only re-state the deletion.

Gates — all at 901f369bc, exit codes captured before any pipe

gateexitverdict line
pnpm --filter @object-ui/plugin-dashboard type-check0clean (tsc --noEmit && tsc -p tsconfig.test.json)
pnpm --filter @object-ui/plugin-dashboard lint0365 problems (0 errors, 365 warnings) — warnings all pre-existing
pnpm exec vitest run packages/plugin-dashboard/ (repo root)0Test Files 73 passed (73) · Tests 657 passed (657)
check-control-bytes.mjs0check-control-bytes: OK (scanned 4654 tracked text file(s); skipped 85 binary).
check-changeset-presence.mjs01 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)
check-changeset-no-major.mjs0No changeset declares a major bump.
check-changeset-fixed.mjs0All workspace packages are in the changeset fixed group.
check-type-check-coverage.mjs0type-check coverage: 45/46 via type-check … 0 errors outstanding
check-lint-coverage.mjs0lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).

The dependency closure was built first (pnpm --filter '@object-ui/plugin-dashboard^...' build) — the first type-check run in the fresh worktree failed with module-resolution errors that read exactly like breakage but were only unbuilt dependencies.

Declared narrowing. Gate set re-derived from .github/workflows/ against the actual diff rather than from the dispatch list. Left to CI: the repo-wide pnpm lint and pnpm type-check, pnpm check (CLI self-check), the phantom-deps / self-import / esm-specifiers / spec-symbols / action-forward-parity / i18n gates, the two docs type gates (which carry no path filter and so run on every PR), and Bundle Analysis (triggered by packages/**). The narrowing cannot hide a failure from this diff: it changes one JSX expression inside one function, adds and removes no import, no export, no string literal and no t() call site, so the module graph the bundle and eager-closure gauges measure is byte-for-byte the same set of edges, and the i18n and docs gates read surfaces this diff does not touch. The package's own type-check and full test suite — the two gates that can actually see this edit — were run in full, not narrowed.

Generated by Claude Code


Generated by Claude Code

…om the self-contained branch
`isSelfContained` is `widget.type === 'metric' && !datasetBound`, and the
`isSelfContained` arm of `renderedNode` forked on `datasetBound` a second time.
The `datasetBound` side of that inner fork could never execute: reaching it
required `isSelfContained`, which requires `!datasetBound`.
Render `SchemaRenderer` unconditionally there and name the invariant in a comment
so the arm is not re-added by the next person mirroring this shape. The reachable
fork in the Card branch — the one that gives a dataset-bound metric its title and
border chrome — is untouched, and behaviour is unchanged.
Fixes#4620
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3785.7 KB3867.2 KB
Main entry chunk (gzip)151.6 KB350 KB
Entry fileindex-Chxftl7O.js
StatusPASS

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


📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)10.04KB3.72KB
app-shell (runtime-config.js)8.91KB2.99KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)29.34KB7.05KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)6.35KB2.43KB
auth (index.js)2.77KB1.22KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.02KB0.89KB
auth (useIsWorkspaceAdmin.js)3.04KB1.45KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)506.99KB113.73KB
core (index.js)4.51KB1.80KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)159.80KB44.33KB
fields (index.js)238.85KB60.13KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.22KB3.08KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.35KB3.31KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.42KB1.42KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.81KB0.83KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.72KB18.35KB
plugin-chatbot (index.js)181.21KB43.14KB
plugin-dashboard (index.js)128.36KB32.95KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)242.15KB60.89KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.07KB30.43KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.70KB27.17KB
plugin-map (index.js)20.06KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.68KB7.66KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.50KB20.68KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)43.66KB14.77KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.33KB0.69KB
react (schema-input.js)1.45KB0.83KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (index.js)4.77KB2.16KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)10.76KB3.17KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)6.92KB2.40KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.08KB1.53KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-salesClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — ACCEPT (card #4620)

Gates. 22 named check runs read individually for completed + success: 19 success, 3 skippedTest (coverage), the unexpanded Test (coverage shard …/4) matrix placeholder, and dependabot, the three always-skipped no-ops in this repo's set. All four real shards, Type Check, Lint, Build & E2E, Build Docs, Bundle Analysis, Live E2E, both doc type checks and all three changeset checks are green on 901f369bc.

The reachability proof is a proof, not a restatement

The card's line anchors had drifted, and re-reading them at their new numbers would only have re-asserted what the card already claimed. What makes this sound is the step past that: datasetBound is bound exactly once for the scope (:570, inside renderWidget opening at :545), it is a const, it is never shadowed and never reassigned, and its only reads are :842, :850, :888, :917. With that established, isSelfContained at :850 requiring !datasetBound and the fork at :888 requiring datasetBound truthy cannot both hold — unreachable by construction rather than merely unreachable-looking.

That closure is the difference between a reachability argument and a guess, and it is what I could not have taken on faith from the card.

The dispatch's stop condition was correctly evaluated rather than assumed away: the && !datasetBound clause is unchanged since filing, so this stayed a deletion and did not convert into a behaviour question.

Coverage named, not assumed

I asked for an honest "not found" if the Card-path coverage did not exist. It does, and it is named: DashboardRenderer.filters.test.tsx:162, "injects the merged filter into a dataset widget's runtimeFilter", rendering a type: 'metric' widget with dataset: 'sales' at :171isSelfContained is false for it, so the assertion can only pass through the Card-branch fork.

Better still, that pin was shown to bite: forcing the reachable Card fork to its SchemaRenderer side turned all 5 tests red including the named one, restored byte-identical. Deleting unreachable code is only safe if the code that remains is actually guarded, and this measures that rather than asserting it.

No new test, correctly. A pin asserting that deleted code does not run would only re-state the deletion.

Independent corroboration I did not ask for

CI's own bundle bot settles a question the reachability argument leaves open — whether the arm was emitted at all, or had already been tree-shaken so that deleting it changed nothing. Three PRs of mine ran concurrently off the same merge-base 7a28e1e3f, and only this one touches plugin-dashboard:

PRtouches plugin-dashboardplugin-dashboard (index.js)
#5602no128.53 KB / 32.97 KB gz
#5603no128.53 KB / 32.97 KB gz
#5604yes128.36 KB / 32.95 KB gz

The arm was real emitted code. The deletion is a real deletion.

Fence held exactlyDashboardRenderer.tsx plus the changeset, 2 files, +24/−3. The reachable Card-branch fork is untouched, and DatasetWidget / effectiveWidget stay referenced by it, so nothing became unused.

Not fixed here, recorded elsewhere: the write path ate this PR's report-comment marker and appended a duplicate footer to the body. Both are logged on #5581 with the reasoning for leaving the body alone; neither affects this diff.


Generated by Claude Code

@os-sales
os-sales marked this pull request as ready for review August 21, 2026 17:23
@os-sales
os-sales added this pull request to the merge queueAug 21, 2026
Merged via the queue into main with commit 84d2e98Aug 21, 2026
23 checks passed
@os-sales
os-sales deleted the claude/issue-4620-dashboard-unreachable-arm branch August 21, 2026 17:24
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(plugin-dashboard): DashboardRenderer's self-contained branch carries a DatasetWidget fork that cannot be reached

1 participant

@os-sales