Uh oh!
There was an error while loading. Please reload this page.
fix(app-shell,react): converge the five three-rung titleField seams on the two-rung shape - #6573
Conversation
… on the two-rung shape
`ObjectView.tsx` resolves a title field for seven view kinds and answered the
same question two ways. Calendar (`viewDef.calendar?.titleField || 'name'`) and
gantt already used two rungs; timeline, kanban, map, gallery and tree carried a
three-rung chain with the object-level `objectDef.titleField` in the middle.
This drops that middle rung, so all seven now read
`viewDef.<kind>?.titleField || 'name'` — a convergence on an existing shape,
not a removal.
No legal metadata could reach the rung: `@objectstack/spec`'s object schema is
a `strictObject`, so `ObjectSchema.safeParse({ …, titleField: 'x' })` is
rejected with `unrecognized_keys` — the same code a nonsense key gets — while
`nameField`, `displayNameField` and `titleFormat` all parse (re-measured here
against `@objectstack/spec@17.2.0`). objectui#6531 established that and dropped
the twin read inside `getRecordDisplayName`.
`useRecordSearch`'s candidate signature appended `o?.titleField ?? ''` to every
entry, a half that could never vary. The signature is now the object name
alone — the only field of an object definition the fanout effect consumes.
`timelineViewOptions` loses its now-dead `objectDef` parameter (root tsconfig
sets `noUnusedParameters`), and three doc comments that credited the object
with supplying the title field are corrected.
Fixture triage: two phantom fixtures spelled `titleField: 'name'`, which is
also the literal floor, so their assertions resolved identically with the rung
deleted. Both retired; the removal is pinned with a distinguishable value in
the new convergence suite instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SfZeFWrhGLHmfq61xbz4qos-support-ai
commented
Aug 26, 2026
ACCEPT — PM review of #6557, done from the tree. The convergenceAll seven seams now read ⭐ A correction to my own dispatch order's arithmetic. My census said five sites, and it was right — but anyone re-checking it with You corrected the prose at three sites; I named one
Dropping ⭐⭐⭐ Two phantom fixtures, and this is the third instance todayBoth were spelled The one in This is the third fixture of this exact shape retired in this lane today (#6531 had VerificationThe new suite reads all seven seams from one render, with a distinct value per kind so a seam reading the wrong kind's config fails instead of passing by coincidence — and the object carries the rejected key in that same case, so it doubles as proof the floor is not hard-coded. That is the both-directions requirement met properly rather than nominally. The ablation reds exactly the three fix-direction assertions with every control green, restored by blob-hash equality and an empty ⭐ And you reported that one annotation in your own ablation script was wrong — the restore-leg check expected Escalation clauseNot triggered, and established rather than assumed: #6572 filed for the Landing on green. Generated by Claude Code |
✅ 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
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#6557
ObjectView.tsxresolves a title field for seven view kinds and answered the same question two different ways. Two of them already used two rungs; five carried a three-rung chain with the object-levelobjectDef.titleFieldin the middle. This drops that middle rung, so all seven now readviewDef.<kind>?.titleField || 'name'. It is a convergence on a shape two sibling sites in the same object literal already had, not a removal.The split, verified independently at
0a2918f3fThe dispatch order asked me to check its line numbers rather than trust them. Every one is exact:
viewDef?.timeline?.titleField || objectDef?.titleField || 'name'viewDef.kanban?.titleField || objectDef.titleField || 'name'viewDef.calendar?.titleField || 'name'— already two-rungviewDef.map?.titleField || objectDef.titleField || 'name'viewDef.gallery?.titleField || objectDef.titleField || 'name'viewDef.gantt?.titleField || 'name'— already two-rung…tree?.labelField || …tree?.titleField || objectDef.titleField || 'name':2217and:2256are untouched: they are the evidence.Why the middle rung could never fire legally
Re-measured here against
@objectstack/spec@17.2.0(the dist this repo installs), usingObjectSchemafrom@objectstack/spec/data:The object schema is a
strictObject, so the key is not merely undeclared — it draws the same issue code a nonsense key gets, while the three declared pointers all parse. objectui#6531 established that measurement and dropped the twin read insidegetRecordDisplayName(merged asc00bf2861).Escalation clause (triage's, still binding): no live legal config depends on the middle rung. A repo-wide grep for object-shaped
titleFieldproducers found none — everytitleFieldinexamples/,packages/*/srcand the JSON schema catalog is a view-level key (calendar/gantt/map/timeline/gallery/kanbanconfig), which is real, declared, and untouched here. The only object-shaped occurrence in the repo was the phantom test fixture retired below.Options 2 and 3 were rejected, with reasons
nameField. It adds a rung calendar and gantt do not have, so it increases the divergence this card exists to close; and unlike option 1 it changes behaviour for legal configs (an object declaringnameField: 'x'would resolvexon five kinds and'name'on the other two).ObjectMap's seam is render time (a title for a record); these five are config time — they decide what a view is configured with, and that value flows onward into published view-config types. A record-title resolver still has to produce a concrete field name, so this relocates the decision rather than removing it.What changed
packages/app-shell/src/views/ObjectView.tsx— the five seams.timelineViewOptionsalso loses its now-deadobjectDefparameter (the root tsconfig setsnoUnusedParameters, and a retained-but-unread parameter would read as "the object still matters here"); its one call site is updated.:152. Two more describe the same deleted rung and would have gone stale in the same edit: the call-site comment at:2222("forwards what the view declared plus the object's title field") and the tree comment at:2261("labelField falls back to the object title"). All three corrected.packages/react/src/hooks/useRecordSearch.ts— the candidate signature, the memo key that decides when the cross-object fanout re-runs, appendedo?.titleField ?? ''to every entry. Because no legal object definition can carry the key, that half was permanently'': a constant suffix in a cache signature, and the last thing in the repo that read like evidence some producer ships the key. The signature is now the object name alone — the only field of an object definition the effect consumes (dataSource.find(obj.name, …)and theobjectswhitelist sent tosearchAll;label/iconare read per hit at render time and never decide re-runs).titleField: 'name', which is also the literal floor, so their assertions resolved identically with the rung deleted: they passed while measuring nothing.useRecordSearch.test.ts:11'saccountfixture (the one the card names), and — found while working — the sharedobjectDefinObjectView.timelineBinding.test.tsx, whose comment credited the object with supplying the title. The removal is pinned with a distinguishable value ('headline') in the new suite instead.@object-ui/app-shell,@object-ui/react, bothpatch).Nothing in
packages/core.Verification — both directions, as the dispatch required
packages/app-shell/src/views/ObjectView.titleFieldConvergence.test.tsxrenders the real page with a capturingListViewand reads all seven resolved seams from one render, so the fix and its controls are measured on the same instrument:titleField: 'headline'is honoured by no kind; all seven answer'name'.'name'(all seven).titleFieldstill wins on every kind, with a distinct value per kind so a seam reading the wrong kind's config fails instead of passing by coincidence. The object also carries the rejected key here, so this doubles as proof the removal did not simply hard-code the floor.tree.titleField) still answers; dropping the object rung did not collapse the two view rungs into one.titleField:/labelField:assignments in the file, that none mentionsobjectDef, and that each is a chain ofviewDefrungs ending|| 'name',. That is the invariant this card really restores, and it is what stops a new view kind copied from a sibling reintroducing the rung. (Structural on purpose: four of the seams are closures insideObjectViewInner, and "these five now have the same shape as those two" is a statement about expressions, not one pair of values — same posture asObjectView.viewConfigGate.test.ts.)useRecordSearch.test.tsgets the same treatment behaviourally: the signature feeds the effect's dependency array, so a rerender with a new array differing only by the rejected key must not re-fire the fanout (the fix), while a changed candidate name still does and an identical-content array still does not (the controls).Reverse verification — direction predicted before running, then observed
Run from the committed fix, restoring both source files from the merge-base
0a2918f3fand putting them back withgit checkout HEAD -- <paths>:Exactly the three fix-direction assertions go red and every control stays green — which is what separates "the fix works" from "the fallback chain is broken". No rebuild was needed for either leg: both suites import the edited modules by relative path, so vitest transforms the source directly.
Gates run locally, at
eb5ddeb26Every verdict below is the gate's own reported result, with the exit code captured before any pipe.
pnpm turbo run build --filter='@object-ui/app-shell...' --filter='@object-ui/react...'29 successful, 29 totalTest Files 107 passed (107)·Tests 1361 passed (1361)pnpm --filter @object-ui/app-shell type-checktsc --noEmit && tsc -p tsconfig.test.json)pnpm --filter @object-ui/react type-checkpnpm --filter @object-ui/app-shell lint✖ 2746 problems (0 errors, 2746 warnings)pnpm --filter @object-ui/react lint✖ 358 problems (0 errors, 358 warnings)pnpm check:control-bytes✅ OK (scanned 5427 tracked text file(s))pnpm check:vi-mock-specifiers✅ OK (3847 tracked source file(s), 459 carry a mock)node scripts/check-changeset-presence.mjs✅ 5 source file(s) of 2 released package(s) changed … declares 1 changeset(s)node scripts/check-changeset-no-major.mjstype-checkreally does cover the new tests. The packagetsconfig.jsonexcludes**/*.test.ts*, so "typecheck is clean" would otherwise say nothing about them.tsc -p tsconfig.test.json --listFilesreports 1 hit each forObjectView.titleFieldConvergence.test.tsx,ObjectView.timelineBinding.test.tsxanduseRecordSearch.test.ts— they are in the program.Declared narrowing
Two repo-wide runs were narrowed, and CI runs both in full regardless:
packages/react/was run entire (60 files, no narrowing — it is the package whose hook changed). Forapp-shell(554 test files) the run set was measured, not guessed: a script computed the reverse-import closure ofviews/ObjectView.tsx,chrome/CommandPalette.tsxandviews/SearchResultsPage.tsxover every relative import inpackages/app-shell/src(31 modules → 24 test files), unioned with every app-shell test that reads the source tree viareadFileSync/readdirSync/globSync(the ratchet-and-guard family, which can see the diff without importing it) and everyviews/ObjectView*.test.*. 47 app-shell files + the react package = the 107 above.turbo run lintfans out to a per-packageeslint .. The two changed packages were linted in full —--format jsonreports 995 files (app-shell) and 127 (react), 0 errors in both. The remaining packages are unaffected by construction:eslint.config.jsconfigures no type-aware linting (noparserOptions.project/projectService), so a file's verdict is a function of that file plus the shared config, and this diff changes neither for any package other than these two.Related, not touched
Filed unassigned while working this card: objectui#6572 — two doc comments in
InterfaceListPage.tsx/InterfaceListPage.mapConfig.test.tsxstill teach the precedence ladder as containing anobjectDef.titleFieldrung. That drift comes from objectui#6531, not from this change, and lives outside this file face, so it is a separate card rather than a rider here.Generated by Claude Code