This repo has two components named `PageHeader`. `@object-ui/layout`'s
converged on `subtitle` in #3789 — the key `@objectstack/spec/ui`'s
`PageHeaderProps` declares. The console's own header in `app-shell` spelled
the same concept `description` and had no `subtitle`, so one idea carried two
key names one package apart.
A plain rename with no alias, because the prop is not on the published
surface: `dist/index.d.ts` exports 226 symbols and `PageHeader` is not one of
them (measured through the TypeScript checker, not by reading the barrel), the
`exports` map declares only `.` and `./styles.css`, and Node refuses every
deep subpath with ERR_PACKAGE_PATH_NOT_EXPORTED. Both in-repo call sites are
in this package. `dist/index.d.ts` and `dist/layout/index.d.ts` are
byte-identical across the change.
`__tests__/PageHeader.subtitle.test.tsx` pins the rendered secondary line,
that `description` now draws nothing and is a compile error, and that both
packages' `PageHeaderComponentProps` declare `subtitle`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012u2pRjcqAYtoEjgr3wwhnK
Fixes#4761
This repository has two components named
PageHeader.@object-ui/layout's renders the authoredpage:header/page-headernode and converged onsubtitlein #3789 (PR #4759), becausesubtitleis the key@objectstack/spec/ui'sPageHeaderPropsdeclares.@object-ui/app-shell's — the console's own title row, drawn byObjectViewandObjectDataPage— spelled the very same conceptdescriptionand had nosubtitleat all. Both rendered correctly; the defect was one concept carrying two key names one package apart, the objectstack#4115 shape moved up a layer.The card was measurement-first, so the measurement comes first.
The consumer surface of app-shell's
PageHeaderrgover the tree, dot-directories included,node_modules/distexcludedviews/ObjectView.tsx:56,views/ObjectDataPage.tsx:62, bothfrom '../layout/PageHeader.js'ObjectView.tsx:2262,ObjectDataPage.tsx:505; both passeddescriptiongetExportsOfModule) onsrc/index.tsand on the builtdist/index.d.ts— never grep, sinceexport *propagates a symbol without naming itPageHeaderandPageHeaderComponentPropsare not among them. Controls:AppShell/ConsoleLayout/PreviewBadgereachable, a nonsense name notapps/console(a real dependent)@object-ui/app-shell/layout,…/dist/layout/PageHeader.js,…/src/layout/PageHeader.js→ERR_PACKAGE_PATH_NOT_EXPORTEDfor all three, while the declared entry resolves. Theexportsmap declares exactly.and./styles.cssrg "from '@object-ui/app-shell'"in../objectstackcontent/docs/**PageHeaderpage documents@object-ui/layout's component / the SDUI block. app-shell's is undocumented, consistent with it not being exported — so no docs edit was neededThe same probe run against
@object-ui/layout's entry is the contrast that makes the call: 33 exports,PageHeaderREACHABLE — through anexport *that a text search of the barrel would have missed. That side's convergence really was a breaking prop removal on a published surface, which is why #3789 shipped it as aminorwith a migration table. This side has no such surface.Out-of-repo consumers cannot be enumerated from this repository, and this PR does not claim they were. What can be enumerated, and is, is the set of import paths through which one could reach this component — and that set is empty. (
filesshipsdist, so the bytes are in the tarball; a consumer that patches theexportsmap or uses a resolver that ignores it could reach them. That is outside the supported contract.)The change
Convergence to
subtitle, as triage's first branch ruled — a plain rename, no deprecation alias. With no supported specifier reaching the prop there was nothing to keep compatible, and a renderer-sidedescriptionread would have been exactly the second dialect AGENTS.md #0.1 forbids, one card after the layout side finished retiring one.Rendered output is unchanged: same element, same classes, same position. Emitted declarations confirm the blast radius —
dist/index.d.ts(8c886251…) anddist/layout/index.d.ts(f9f4862b…) are byte-identical across the change; onlydist/layout/PageHeader.d.tsmoves (8535dd1e…→603c6702…), the one file no supported specifier can reach.Untouched on purpose:
objectDef.description(a metadata key) and the i18n keyconsole.objectData.descriptionare different things that happen to share the word; only the JSX prop was renamed.The pin
packages/app-shell/src/layout/__tests__/PageHeader.subtitle.test.tsx, five cases:subtitledraws as the secondary line — asserted on the DOM a reader gets: a<p>, insidedata-testid="page-header", in the same block as the<h1>and positioned after it (compareDocumentPosition), not merely "the text is somewhere on the page".subtitle→ no secondary line at all.hidden sm:blocksurvives. happy-dom evaluates no Tailwind, so this is pinned as a string and says so in the file: dropping thesm:blockhalf would leave a barehidden— an element that passes every DOM assertion above while being invisible at every width. It is not a claim about computed style, which nothing in this environment can measure.descriptiondraws nothing and is rejected by the compiler (@ts-expect-error— unlike layout's, these props do not extendHTMLAttributes, so the key is a real type error rather than a silently-spread DOM attribute).PageHeaderComponentPropsdeclaresubtitle. This is the assertion that goes red if either side drifts again.Ablation
Directions predicted before running; mutations confirmed on disk by counting the injected and the removed text (an editor's exit code proves nothing —
sed/perl -i/str.replaceall exit 0 on zero matches); restores undertrap … EXIT INT TERMand verified by hash against the committed blobs.No rebuild is required and none was performed, which is a claim about resolution rather than an omission: the pin imports
../PageHeader, a relative specifier that noexportsmap can intercept, so vitest transforms the mutated source directly and bothtscprojects compile that same source. The only dist-resolved import in the file isimport type … from '@object-ui/layout', and no leg mutates that package.PageHeader.tsxTS2322at both call sitessm:blockfrom the subtitle classsubtitleassertTS2344: Type 'false' does not satisfy the constraint 'true'Leg C is the asymmetry worth recording: vitest proves nothing about case 5's assertions, and
tsc -p tsconfig.test.jsonis the only thing that can. Leg A came in slightly narrower than predicted —type-checkistsc --noEmit && tsc -p tsconfig.test.json, so the source project went red first and short-circuited before the test project could report its own errors. Reported as observed rather than as predicted.Verification
All at
0c37726, after the final commit:Gates:
check:control-bytes✅ ·changeset:check✅ ·check:changeset-presence✅ ·check:spec-symbols✅ ·check:self-import✅ ·check:phantom-deps✅.Repo-wide
turbo run lintwas not run locally, and that narrowing is declared rather than implied: this diff touches four files, all in@object-ui/app-shell, whose whole package was linted above; andeslint.config.jsenables no type-aware linting (tseslint.configs.recommended, noparserOptions.project, noprojectService), so a verdict on an untouched file is computed from that file alone and cannot move because of this diff. CI runs the full farm regardless.Generated by Claude Code