Repository navigation
fix(app-shell): object list pages and their breadcrumb read the plural label (objectui#11696) - #11728
Conversation
…th the plural label useObjectLabel gains objectPluralLabel: the translated plural, else the declared pluralLabel, else the singular as objectLabel resolves it. The object list page's PageHeader title and the AppHeader object crumbs (the segment, its Switch Object siblings, and the ancestor trail's object crumbs) use it; record-scoped surfaces keep objectLabel. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
…chor The objectui#5564 identity pin anchors the hook's member count to an absolute; a new resolver moves it, as objectui#11344 did. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
…harness Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
Contract reviewServed-tier: Inputs read: card objectui#11696 (body; comments 6011190894 triage, 6018512840 claim, 6019999259 os-dev-report); PR objectui#11728 (body, 8-file list, net diff against Gate verdicts (the check-runs on the head)42 check-runs on ① Derived judgments
② Semver level
③ Boundary flags
Route to PASSEither, on a new head (a new record follows it):
Everything else on this head stands as judged: #1–#7 right, the changeset level right, every dev flag answered or escalated. Implemented-by: VERDICT: FAIL Generated by Claude Code |
ObjectDataPage lists the object's records under the object crumb that now reads the plural; its PageHeader title read the singular. It uses objectPluralLabel; the record drawer it opens keeps objectLabel. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
Contract reviewServed-tier: Inputs read: card objectui#11696 (body; comments 6011190894 triage, 6018512840 claim, 6019999259 first os-dev-report, 6020306654 claim amendment, 6020698264 second os-dev-report); PR objectui#11728 (body, 10-file list, net diff against Gate verdicts (the check-runs on the head)43 check-runs on ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #11696
Clause-②: yes
What changed
Object list pages, and the breadcrumb segment that links to them, read the object's plural label. The record page keeps the singular.
@object-ui/i18n:useObjectLabel()gainsobjectPluralLabel(objectDef)besideobjectLabel. It returns the translated plural ({ns}.objects.{objectName}.pluralLabel), else the declaredobjectDef.pluralLabel, else the singular asobjectLabelresolves it. An object that declares no plural keeps its label.@object-ui/app-shellObjectView: the list page'sPageHeadertitle uses it. That is the only line changed in this file besides the hook destructure.@object-ui/app-shellObjectDataPage: the/datapage (the URL-defined data slice badged "Data") lists the object's records under the same object crumb, so itsPageHeadertitle uses it too. Only the title span and the hook destructure changed; the record drawer it opens keeps the singular.@object-ui/app-shellAppHeader: the object breadcrumb segment, its "Switch Object" entries, and the object crumbs of a record's ancestor trail use it.ObjectViewand inObjectDataPage), the import wizard's object name, the favorite entry, "New" actions, and the record form page's own breadcrumb link (see Acceptance notes).Where it landed, and why that is not only where the card pointed
The claim's file surface named
ObjectView.tsxfor both the title and the breadcrumb. Read onorigin/mainat7300fca:PageHeaderinObjectView. Changed.ObjectView.AppHeaderbuilds it: it is the object segment, and the ▾ is that segment's "Switch Object" dropdown. Changed there. This is the same package, andAppHeaderis where the crumb is produced.ObjectViewsites named in hypothesis 1 of the dispatch (viewOriginLabel(…) || objectLabel(objectDef)) buildlocation.state.from.label. That is the record page's link back to the list, not the list title or the breadcrumb. Not changed, because it is a record-page surface. It is listed under Acceptance notes.The object crumb is the same element on every route under the object (list, view, record), and it always links to the list. It reads the plural on all of those routes, so it does not change its own name when the trail continues into a record ("Projects › Apollo"). The alternative was a crumb that depends on the route. That would show "Projects ▾" on the list and "Project ▾" one click later, on the same link.
Overlap: seat 1's objectui#11685 may edit
ObjectView.tsxin the record drawer's chrome. This PR touches that file only at the hook destructure and the listPageHeadertitle span. Whichever PR lands later merges the other.The nav entry
The sidebar entry "Projects" in the card takes its text from its own authored
label(showcasenav_projects, Setupnav_users), not from object metadata. There is no shared source to reuse, and after this PR both read the plural. Measured once at objectstack6befe19c: every first-partyobjectnav entry in objectstack's examples, platform objects and plugins carries alabel, and so do both entries in objectui's examples. A nav entry with no label inherits its text throughuseNavTargetLabel, which callsobjectLabel. That follows step 3 of the spec's nav-label rule ("the object's / dashboard's label"). It is not changed here: an object entry inheriting the plural is a reading of the spec contract, and it goes back to the PM as an open question.Judgment: a bundle that translates the singular but not the plural
This answers the question the dispatch left open in Zone 3. When the bundle has no
pluralLabelbut the object declares one,objectPluralLabeldoes not fall back to the translated singular. It shows the declared plural. The reason:translateObjectin@objectstack/specservespluralLabelfrom/metaas the catalog entry, else the authored value, and has no step down to the translated singular. So this matches what every other consumer shows. A client-only step would be the second dialect that theviewSuffixesdocblock already refuses. Exposure, measured once at objectstack6befe19c: the platform zh-CN object bundle carriespluralLabelfor every object it translates, and so does every showcase object with a zh-CN entry.os i18n extractwritespluralLabelwherever an object declares one, so a missing plural is a gap in the bundle and gets fixed there.Tests
Three new pins, each named after this issue:
packages/i18n/src/__tests__/useObjectLabel-pluralLabel-11696.test.tsxcovers the resolution order. Cases: en declared plural; zh-CN translated plural over the declared one; a namespaced object resolved by its short name; singular fallback, translated and untranslated; an empty declared plural; and the Zone 3 case above.packages/app-shell/src/views/ObjectView.listTitlePlural-11696.test.tsxmounts a realObjectViewinside a real i18next instance. "Projects" titles the projects list in en, the translated plural titles it in zh-CN, and an object with no plural is titled with its label.packages/app-shell/src/layout/__tests__/AppHeader.objectCrumbPlural-11696.test.tsxuses theAppHeader.servedLabels-11295harness with the realuseObjectLabel. It checks the list crumb in en and zh-CN, the "Switch Object" siblings (plural, falling back to the singular), the crumb staying plural on a record route, and a control.packages/app-shell/src/views/ObjectDataPage.listTitlePlural-11696.test.tsxmounts a realObjectDataPageinside a real i18next instance (theObjectDataPage.filterChipI18n-9159mocks). "Projects" titles the projects/datapage in en, the translated plural titles it in zh-CN, and an object with no plural is titled with its label. It reads the title's ownspan.truncate, because the heading also holds the "Data" badge.In the zh-CN fixtures
pluralLabeldiffers fromlabelon purpose. Shipped zh-CN bundles often use one word for both (项目/项目), and with equal strings an assertion cannot tell which key was read.One existing pin moved:
useObjectLabel-identity-5564anchors the hook's member count to an absolute number, and a new resolver changes it (27 to 28), as objectui#11344 did before.Reverse verification (on
29cba5f, the implementation commit)The mutation, applied after the fix was committed: in
AppHeader, the hook destructure was changed to aliasobjectLabelasobjectPluralLabel, so every object crumb reads the singular. InObjectView, the title span was put back toobjectLabel(objectDef). Both edits went through objectstack'sscripts/ablation-replace.mjs. Each anchor hit exactly once and each file's blob changed. The restore was proven by the blob matchingHEADand by an emptygit diff HEAD, not by an exit code.AppHeader4 red and 1 green (the control);ObjectView2 red and 1 green (the control).Tests 6 failed | 2 passed (8). The failures readexpected 'Project' to be 'Projects',expected '项目' to be '项目清单',expected [ 'Task' ] to deeply equal [ 'Tasks' ], and the crumb text not found. Both controls stayed green.Gates (every reading on head
07abde3unless noted)Patch round (head
87c14b9, after contract review6020276603): the readings for87c14b9are in the card's secondos-dev-report(comment6020698264); CI on87c14b9: 43 checks, 40 success, 3 skipped. The table below describes07abde3.pnpm exec vitest run packages/i18n/Test Files 81 passed (81)·Tests 1310 passed, 13 skipped (1323)pnpm exec vitest runover 208 app-shell test files (declared narrowing, below)Test Files 208 passed (208)·Tests 2259 passed (2259)pnpm exec vitest runover the 7apps/consoletest files that mount AppContent / ConsoleLayout / ObjectView (on29cba5f; the two later commits change test files only)Test Files 7 passed (7)·Tests 75 passed (75)pnpm --filter @object-ui/i18n type-checktsc --noEmit && tsc -p tsconfig.test.json; the new test file is in its file listpnpm --filter @object-ui/app-shell type-check(after building its dependency closure)pnpm exec eslinton the 7 touched files0 errors. The warnings areno-explicit-any/no-unused-varson lines that already had them, or carried over in the copied 11295 harnesspnpm check:control-bytescheck-control-bytes: OKpnpm check:test-path-rootscheck-test-path-roots: OKpnpm check:changeset-claimsNo pending changeset names a file this change touches.pnpm check:pending-changeset-literalsNo test source names a pending changeset.pnpm check:vi-mock-specifiers/vi-mock-inherit/vi-mock-override-shapeOKpnpm check:new-line-citations0 new citation(s)node scripts/check-changeset-presence.mjs7 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)node scripts/check-changeset-no-major.mjsNo changeset declares a major bump.NOT MEASURED:
pnpm check:readme-exports. It exited 1 withtype entry ./dist/index.d.ts is not on disk -- run pnpm build first, which is a missing prerequisite, not a judgment. It was not owed either, because no package'sexportschanged. The i18n key gates were not run because no locale pack changed.Declared narrowing. I started a whole-package
packages/app-shell/run on29cba5f. It held the lock for 28 minutes with no output (vitest buffers it here) while two other agents queued behind it, so I stopped it (my own PIDs only). In its place I ran the 208 app-shell test files that nameAppHeader,ObjectView,ConsoleLayout,AppContent,PageHeader, the label hooks, or breadcrumbs. The selection isgit grep -lover app-shell's tracked test files. The other 826 app-shell files are left to CI's fullpnpm test. Blind spot: a test that renders these components through a component whose name matches none of those words.Acceptance notes (observations, not filed; no carrier)
objectnav entries inherit the singular (useNavTargetLabelcallsobjectLabel), following step 3 of the spec ("the object's / dashboard's label"). After this PR such an entry would read "Project" next to a "Projects" title. No first-party producer writes one (measured above). The objectstack CLI scaffolder already writes the plural as a nav entry's label (objectPluralLabelingenerate.ts). The seat answered A: no change here. If it is ever pulled, the spec amends step 3 first and objectui follows.viewOriginLabel(…) || objectLabel(objectDef)inObjectView) names the list with the singular when the active view has no label. It is a record-page surface, so it is left alone.RecordDetailView's related lists, and the fallback inrecord-related-list.tsx) title a list of child records with the child object's singular label. This is the same family as this card and was not touched.ObjectView's favorite toggle; theobjectkind inuseRecentItemLabelthroughuseNavTargetLabel). Same family, not touched.RecordFormPage) links to the object list with the singularlabelthat it shares with its "New …" / "Edit …" title, under theAppHeadercrumb that now reads the plural. It is a record-scoped page, so it is left alone; changing it means splitting that shared variable.objectLabel, andobjectPluralLabelbecause it mirrors it, consult the client bundle before the served value. The server lets a tenant's explicit override beat the catalog (the objectstack#8284 ruling), and objectui#11295 already made views and dashboards draw a served label as given. Whether an object rename shows in the console was not measured. This is a read-only inference.Session:
https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8Generated by Claude Code