fix(dashboard): retire the dashboard-root title read on all five surfaces - #7622
Merged
Merged
Conversation
…rfaces The five dashboard-ROOT `title` read arms retire together under ADR-0049 (objectui#7509, maintainer ruling 2026-09-04, decision batch #29, option C). `label` — REQUIRED on `@objectstack/spec`'s `DashboardSchema` — becomes the only header source, then the raw `name`. `DashboardSchema` refuses a root `title` BY NAME (`unrecognized_keys(title)`) and the save route answers 422, so no authored document can acquire the key; what retires is compatibility with documents stored before that refusal. Five surfaces read the legacy spelling independently, so one stored document could show one header in the console and a different one in the designer — which is why the ruling refused retiring any single arm. Widget-level `widget.title` (`DashboardWidget.title`, the spec's `I18nLabel`) is a different DECLARED key and is untouched; root and widget arms were separated by receiver, not by grep, and every pin carries a widget control. Each arm gets a refusal-shaped pin in the #5830 / #5852 form. Two existing pins asserted the retired behaviour and were rewritten rather than deleted: `dashboardAuthoredInputs`' "the legacy `title` read stays" block (its #5742 exclusion of `title` from published inputs is unchanged) and `DashboardGridLayout.inlineLocaleLabel`'s precedence case. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Contributor
✅ Console Performance Budget
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
Size Limits
|
os-sam
marked this pull request as ready for review
September 4, 2026 08:16
This was referenced Sep 4, 2026
os-sam
pushed a commit
that referenced
this pull request
Sep 4, 2026
…boards The six `filtered-*` entries under `examples/schema-catalog/src/schemas/plugin-dashboard/` author a root `title` that no arm reads. objectui#7509 (PR #7622) retired the last root `title` reader across all five dashboard surfaces; `DashboardRenderer` reads `schema.label` and gates the whole header section on `header` being declared (objectui#5812). None of the six declares `header`, so they rendered no header title before that retirement and render none after — the key was already inert, and this makes the inertness total. Renaming it to `label` was rejected: it would give six shipped examples a header they have never had, which is a feature, not a cleanup. Adds a pin, because nothing else in the repo can notice the key coming back: no renderer reads it, `@object-ui/types` still declares `title?: string`, and `@objectstack/spec` — which does refuse it by name — is not this corpus's validator. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7509
Retires the five dashboard-root
titleread arms as one class under ADR-0049, per the maintainer ruling of 2026-09-04 (decision batch #29, option C, verbatim 「同意」 — recorded on comment 5536191958).label— REQUIRED on@objectstack/spec'sDashboardSchema— becomes the only header source, then the rawname.The five root arms, and how each receiver was established
The whole difficulty of this card is that raw
.titleoccurrence counts in these files are 1 · 5 · 5 · 9 · 1 and only one per file is the dashboard-root arm. Arms were separated by receiver, never by grep. Counts reproduced onorigin/main1bae75bbwith a lit control (.labeloccurrences: 1 · 7 · 2 · 3 · 1).app-shell/src/views/DashboardView.tsx:191resolveKeyedI18nLabel(headerSrc.title, t)headerSrc = (previewSchema as any) || dashboard;previewSchemais auseMemolocal to this view (:129) returning a widget-pruned copy ofdashboard, anddashboard = preferLocal(dashboards, dashboardName, …)offuseMetadata().dashboards. Both arms therefore resolve to the storedsys_dashboarddocument ROOT.plugin-dashboard/src/DashboardRenderer.tsx:947const headerTitle = schema.title || schema.labelschema: DashboardComponentSchema— the component's own prop (:185), i.e. the dashboard root. The file's four other.titleoccurrences all name the WIDGET key: one code read (:360,resolveLabel(widget.title)) and three comments about it (:348,:671,:672).plugin-dashboard/src/DashboardGridLayout.tsx:394schema.title || pickLocalized(schema.label, language) || 'Dashboard'schema: DashboardComponentSchemaprop (:39). The other four all name the WIDGET key: two code reads (:272,:500) and two comments (:452,:494).plugin-designer/src/DashboardEditor.tsx:467schema.title || t('appDesigner.dashboardPreview')function DashboardPreview({ schema }: { schema: DashboardComponentSchema })(:462) — the root. Eight of this file's nine.titleoccurrences are the widget key, reached throughresolveWidgetTitle/writeWidgetTitle(:149,:197,:240,:322,:364,:482, …) and are untouched.plugin-designer/src/pages/DashboardDesignPage.tsx:138(dashboard as any).label || (dashboard as any).title || dashboardNamedashboard = dashboards?.find((d) => d.name === dashboardName)(:25) — the stored document root.⛔ Widget-level
widget.titleis a DIFFERENT, DECLARED key —DashboardWidget.title, the spec'sI18nLabel— and is untouched. Every pin below carries an explicit widget-level control for that reason; onDashboardEditorthe control covers both the display read and the authoring write, since a grep-driven sweep there would have removed the only way to name a widget in the designer.One extra edit in the declared file surface, named explicitly
DashboardDesignPage.tsx:42seededtitle: dashboardName ?? ''in the not-found literal — the only dashboard-document literal this repo authors. It now spellslabel. Evidence that this moves no behaviour: the seed is reachable only on the!dashboardbranch, which early-returns "not found" (:120-125) and never reacheshandleChange/saveSchema, so it cannot persist; and the same literal already carriesname: dashboardName ?? ''. Pinned by the second describe block ofDashboardDesignPage.rootTitleRetired.test.tsx.Pins — refusal-shaped, one per arm
New, in the #5830 / #5852 form (what a document carrying the retired key RENDERS, never that it compiles):
packages/app-shell/src/views/DashboardView.rootTitleRetired.test.tsxpackages/plugin-dashboard/src/__tests__/DashboardRenderer.rootTitleRetired.test.tsxpackages/plugin-dashboard/src/__tests__/DashboardGridLayout.rootTitleRetired.test.tsxpackages/plugin-designer/src/__tests__/DashboardEditor.rootTitleRetired.test.tsxpackages/plugin-designer/src/__tests__/DashboardDesignPage.rootTitleRetired.test.tsxEach asserts: a document carrying both renders the
label; a document carrying only the retired key falls through toname/ the generic heading; alabel-only control (without which "the title is absent" would also be satisfied by a header that renders nothing); and a widget-level control.One non-obvious precondition, stated in the renderer pin:
DashboardRenderer's header wrapper is gated onheaderbeing declared (objectui#5812), so every fixture there declares it. A fixture withoutheaderwould have asserted the absence of a title the wrapper gate had already removed — and would have passed with the retired arm fully intact.Both were found by running the suites, not by grep, and both are the "this fixture pins exactly the branch you deleted" case:
dashboardAuthoredInputs.test.tsx— the block headed "the legacytitleread stays — non-author surface, still honoured (objectui#5742)". Itstitle-stays leg is superseded by this ruling; the block is inverted (a wildtitle-only document now gets no header title, a document with both shows thelabel) and the file's header prose carries adashboardpublishes only columns/gap/className as inputs —widgets,title,header,globalFilters,dateRange,refreshIntervalall drawunknown-propwhile the renderer honours them #5742's actual subject is unchanged:titlestays out of the publishedinputs, and its reason only got stronger — the key is now neither authorable nor read.DashboardGridLayout.inlineLocaleLabel.test.tsx— the case "keepstitleahead oflabelin the precedence chain", inverted to "resolves thelabelmap even when a legacy roottitleis also present". This is now the strongest non-vacuity control in that file: before the retirement, a document carrying both never exercisedpickLocalizedat all.Rewritten rather than deleted because each file makes a claim about this renderer's behaviour on this key; a file that dropped the case would leave its own claim unmade.
Fixture sweep — by the rule's consuming radius, not by edited package
title(control: 21 carryinglabel). Exactly one asserted its root title value —DashboardRenderer.headerActions.test.tsx'stextHeaderDashboard('Executive Dashboard', asserted 4×), renamed tolabelwith every assertion unchanged (it pins the header wrapper's geometry, not the spelling). Its sibling helper in the same file was renamed for coherence; the remaining 7 literals never assert their root title and their verdicts do not move.title, alltype: 'dashboard'catalog examples. None declaresheader, andDashboardRenderergates its header section onheader &&, so those six rendered no header title before this change either: zero visible change, nothing to migrate. Recorded as an out-of-scope finding rather than migrated.Clause-②: no — re-judged from this diff
No published name moves.
DashboardComponentSchema.titlestays declared in@object-ui/types(untouched here),BaseSchemacarries an index signature so an authoredtitlestill type-checks, and the Zod twin is not.strict(), so no accept set moves in either direction. What changes is renderer behaviour on a key@objectstack/specalready refuses by name (unrecognized_keys(title), 422 at the save route). The published SDUI manifest is unchanged —dashboardAuthoredInputsstill asserts the exact ten-entryinputslist and passes.Verification
Heavy runs went through
objectstack/scripts/pm/os-verify-lock.shwithOS_VERIFY_LOCK_SLOT=issue-7509-objectuiset before the first attempt; every exit code was captured by redirecting to a file before reading, never through a pipe, and each result is quoted from the gate's own verdict line.pnpm --workspace-concurrency=2 --filter 'PKGNAME^...' buildfor all three packages,VERDICT command-exit 0. Not optional: the firsttype-checkattempt failed withTS2307 Cannot find module '@object-ui/types'until it ran.3fb4a23f—pnpm exec vitest run packages/plugin-dashboard/ packages/plugin-designer/ packages/app-shell/from the repo root (never a package cwd, never a path behind a bare double-dash): Test Files 724 passed (724), Tests 6870 passed, 1 skipped (6871),VERDICT command-exit 0. The union is quoted from the run on the FINAL commit, not an earlier tree.pnpm --filter … run type-checkfor all three packages, exit 0, each script name echoed (so not a zero-match no-op). Non-vacuity is measured, not asserted: each package'stype-checkistsc --noEmit && tsc -p tsconfig.test.json, and an earlier iteration failed onTS2550inside one of the new pin files — direct proof the new tests are in the checked set.origin/main; the mutation was confirmed on disk by five distinct markers (eachgrep -c= 1) plus a non-emptygit diff HEAD. Result: 13 of 24 red, 11 green — every one of the five pin files went red. The 11 green are informative rather than noise:DashboardDesignPage's "carrying BOTH" case stays green because the old chain was alreadylabel || title || name(only itstitle-only case moves), andDashboardEditor's widget-level display/authoring controls stay green, which is exactly the asymmetry that shows the root arm was measured and the widget arm was not touched. No rebuild was needed — every subject is imported by relative path from its own package source, so nodist/participates. Restore leg proven byte-exact:git diff HEADempty and all five worktree blob hashes equal to theirHEADblobs.check:*scripts and.github/workflows/(scripts/pm/dispatch-gates.mjslives in objectstack and answers only about its own tree):check:control-bytes✅ "OK (scanned 6228 tracked text file(s); skipped 85 binary)" ·check:spec-symbols✅ ·check:vi-mock-specifiers✅ ·check:vi-mock-inherit✅ ·check:i18n-keysexit 0 ·check:lint-coverage✅ "46/46 packages linted, 0 with outstanding errors" ·check-changeset-presence✅ ·check-changeset-no-major✅ ·check-changeset-overwrite✅ ·check-changeset-fixed✅. Plus a manual control-byte self-scan over all 14 changed/added files: no match.check:sdui-registration-pinsis NOT MEASURED, not red: it exits 2 with "No console build to weigh atapps/console/dist/assets" — an unmet prerequisite, left to CI, which builds it. The registration surface it guards is nonetheless covered here bydashboardAuthoredInputs' exact-inputsassertion.pnpm lintwas narrowed to the three affected packages, with all three pieces of evidence: (i) the checked population comes from eslint's own config resolution, not my guess; (ii) the count 1241 files is read from--format jsonoutput; (iii)eslint.config.jsenables no type-aware linting (0 occurrences ofparserOptions.project/projectService; control:rulesoccurs 10× in that file, so the grep shape is live), so every rule is per-file and this diff cannot move the verdict on an untouched file. Result: exit 0, 0 errors, 1241 files. CI runs the full farm regardless.Serial constraint, re-measured at claim time
Six open PRs; each one's file list was read rather than its title. #7608 (4 files), #7606 (1 file), #5400 (200 files, both pages) and three dependabot manifest bumps — none touches any of the five files.
🤖 Generated with Claude Code
https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Generated by Claude Code