Repository navigation
Commit 885a96f
fix(app-shell,plugin-detail): titles and links that name a list of records read the plural label (objectui#11733) (#11746)
Fixes #11733
Clause-②: no
## What changed
PR objectui#11728 (objectui#11696) gave `useObjectLabel()` an
`objectPluralLabel(objectDef)` resolver (the translated plural, else the
declared `pluralLabel`, else the singular) and used it for the object
list page title, the `/data` title and the object breadcrumb. Every
other place that names a LIST of an object's records now reads it too.
Record-scoped text keeps `objectLabel`.
| Card location | Producer | Change |
|---|---|---|
| 1. Related-list section titles | `RecordDetailView`, the
single-relationship title | `objectPluralLabel` over the child object
("Tasks") |
| 1. Related-list section titles | `deriveRelatedLists`, the title it
composes when a child points at the parent through more than one field |
new `listLabel` option; `RecordDetailView` passes `objectPluralLabel`,
so the title reads "Opportunities · Partner Project". It was built from
the child's raw, untranslated label before |
| 1. Renderer fallback | `record:related_list` with no `title`
(`record-related-list.tsx`) | the bundle's `objects.NAME.pluralLabel`,
else the translated label, else the humanized name |
| same family | `record:reference_rail` entries with no `title`: each
card title and the "+ N empty (…)" line (`record-reference-rail.tsx`) |
the same as the row above |
| 2. Record form page breadcrumb link | `RecordFormPage` | the shared
`label` is split: the link reads `listLabel` (plural); the "Create …" /
"Edit …" title and the save toast keep `label` (singular) |
| 3. Favorites | `ObjectView`'s star button | the saved favorite's
`label` is the plural |
| 3. Recent items | `useRecentItemLabel`, `object` kind | calls
`objectPluralLabel` at the call site; `useNavTargetLabel` is not touched
|
| 4. Record back-link fallback | `ObjectView`, both builders (the list
schema's `onNavigate` and the record-link bridge's `openRecord`) |
`viewOriginLabel(…) \|\| objectPluralLabel(objectDef)` |
| 4. Record back-link fallback | `AppContent`, a create that lands on
the new record's page | the handed-over `originLabel` is the plural |
## Where it landed beyond the claim's file surface, and why
The claim named `RecordDetailView`, `record-related-list.tsx`,
`RecordFormPage`, `ObjectView` and the recent-item label. Three more
files, each the producer of a surface the card's acceptance covers:
- `packages/app-shell/src/utils/deriveRelatedLists.ts`: it produces the
multi-relationship related-list title (card location 1). Left alone, the
record page would read "Tasks" next to "Opportunity · Partner Project".
The new option is internal: `deriveRelatedLists` is not re-exported from
app-shell's package entry (`exports` is `.` only, and `src/index.ts`
names no `deriveRelatedLists`), so nothing published widens.
- `packages/app-shell/src/console/AppContent.tsx`: the second producer
of the record page's `location.state.from.label` (card location 4), on
the create path. The enumeration pin covers it.
- `packages/plugin-detail/src/renderers/record-reference-rail.tsx`: two
`objectLabel(` hits under the enumeration pin's
`packages/plugin-detail/src`, and both name lists.
`Clause-②: no` still holds: no new export and no new locale key.
`plugin-detail` reaches `objectPluralLabel` through the hook it already
calls (`useSafeFieldLabel()` returns `useObjectLabel()`).
`record-related-list.tsx` narrows with `'objectPluralLabel' in i18n`
instead of the `as any` cast its singular read used.
## The nav entry, unchanged by ruling
`useNavTargetLabel`'s `object` rung still calls `objectLabel`, per step
3 of the spec's nav-label rule, as the seat ruled on objectui#11696. The
recent-item `object` kind no longer asks it, and calls
`objectPluralLabel` itself. The recent-item pin's last case asserts that
`useNavTargetLabel` still answers "Project" / "项目" for the same object.
## Enumeration pin
`git grep -n 'objectLabel(' -- packages/app-shell/src
packages/plugin-detail/src`, tests excluded, at head `50e8f98`: 12 hits
(20 at base `17acfbb`), 0 of them in `plugin-detail`. `apps/console/src`
has 0. Each remaining hit:
| File | Symbol | Classification |
|---|---|---|
| `AppContent.tsx` | `handleCrudSuccess`'s `label` (save toasts
`form.updateSuccess` / `form.createSuccess`) | record-scoped: the toast
for the one saved record |
| `AppContent.tsx` | the child-task branch of `handleRecordFormSuccess`
(the same toasts) | record-scoped |
| `AppContent.tsx` | the global record-form modal's `title`
(`form.editTitle` / `form.createTitle`), 2 hits | record-scoped: "Edit
…" / "Create …" |
| `useNavTargetLabel.ts` | `case 'object'` | the nav-entry inheritance
(spec step 3), unchanged by the seat's ruling on objectui#11696 |
| `ObjectDataPage.tsx` | `NavigationOverlay` `title` | record-scoped:
the record drawer title |
| `ObjectView.tsx` | `useObjectActions({ objectLabel })` |
record-scoped: the delete confirmation and toasts. The label is
attributive to "record(s)": "Delete 3 Project records?", "Project
deleted successfully" |
| `ObjectView.tsx` | the `ImportWizard` `objectLabel` prop | an
object-kind action phrase, the "New …" class: the "Import {{object}}"
title and the template file name. Kept singular, as PR objectui#11728's
contract review classified it. It is a one-line change if the seat reads
it as naming a list |
| `ObjectView.tsx` | two `NavigationOverlay` `title`s | record-scoped:
the record drawer title |
| `RecordDetailView.tsx` | `recordDelete.confirmCopy({ label })` in the
header's delete action | record-scoped: names the one record |
| `RecordFormPage.tsx` | `const label` | record-scoped: it now feeds
only the title and the save toast |
## Tests
Seven pins, each named after this issue. In every `zh-CN` fixture
`pluralLabel` is a different word from `label` (`项目` / `项目清单`), so an
assertion can tell which key was read. Each has a no-plural control.
- `ObjectView.listLinksPlural-11733.test.tsx`: a real `ObjectView` in a
real i18next instance (the 11696 harness). It reads the favorite the
star button saves, and the way-back label each builder hands the record
route (a list probe calls `schema.onNavigate` and the bridge's
`openRecord`). The fixture's list view has no label, which is when the
fallback applies.
- `RecordFormPage.listLinkPlural-11733.test.tsx`: the link reads the
plural; the title and toast read the singular. Cells: en create, en
edit, zh-CN, control.
- `RecordDetailView.relatedListTitlePlural-11733.test.tsx`: single- and
multi-relationship titles, en and zh-CN, control.
- `useRecentItemLabel.objectPlural-11733.test.tsx`: en, zh-CN, control,
the machine-name floor, and the unchanged nav-entry inheritance.
- `AppContent.createLandingOriginPlural-11733.test.tsx`: the
`AppContent.declaredVisibilityKeys` harness with a real i18next
instance. The modal's `onSuccess` is called with a saved id for a
page-surface object, and the record route's probe shows the state it
received. The save runs outside `act`: an awaited React 19 `act` there
did not return before the test timeout.
- `record-related-list.titlePlural-11733.test.tsx` and
`record-reference-rail.titlePlural-11733.test.tsx`: plurals from the
bundle only (these renderers hold the object's name, not its
definition), en and zh-CN, with controls.
## Reverse verification (on `a46d4a9`, the implementation commit)
Mutations went through objectstack's `scripts/ablation-replace.mjs`.
Each anchor's declared hit count was met and each file's blob changed. A
script-held trap restored every path, proven by each blob equal to
`HEAD` and an empty `git diff HEAD`. The direction was written down
before each run.
- **Leg A**: every list-naming site back to the singular (11 anchors in
8 files). Predicted 19 red and 12 green. Observed `Tests 19 failed | 12
passed (31)`. The failures read `expected 'Project' to be 'Projects'`,
`expected '项目' to be '项目清单'`, and the form link `'Project'` / `'项目'`.
Every control stayed green.
- **Leg B**: only `deriveRelatedLists` back to the raw label. Predicted
2 red and 1 green on the RecordDetailView pin. Observed `Tests 2 failed
| 1 passed (3)`: "Unable to find an element with the text: Opportunities
· Primary Project" and the same for "商机列表 · Primary Project".
`50e8f98` changes only the types of the AppContent pin's modal probe.
## Gates
| Command | Head | Exit | The gate's own verdict |
|---|---|---|---|
| `pnpm exec vitest run` over the 7 new pins | `50e8f98` | 0 | `Test
Files 7 passed (7)` · `Tests 31 passed (31)` |
| `pnpm exec vitest run packages/plugin-detail/` | `a46d4a9` | 0 | `Test
Files 243 passed \| 1 skipped (244)` · `Tests 2401 passed \| 8 skipped
(2409)` |
| `pnpm exec vitest run` over 242 app-shell test files (declared
narrowing, below) | `a46d4a9` | 0 | `Test Files 242 passed (242)` ·
`Tests 2141 passed (2141)` |
| `pnpm exec vitest run` over 24 `apps/console` test files that name
AppContent, ObjectView, RecordDetailView, RecordFormPage,
useRecentItemLabel, ConsoleLayout or the related-list blocks | `a46d4a9`
| 0 | `Test Files 24 passed (24)` · `Tests 603 passed (603)` |
| `pnpm --filter @object-ui/app-shell type-check` (dependency closure
built first: turbo, 28 tasks) | `50e8f98` | 0 | script echoed (`tsc
--noEmit && tsc -p tsconfig.test.json`); the five app-shell pins are in
its file list |
| `pnpm --filter @object-ui/plugin-detail type-check` | `a46d4a9` (no
plugin-detail file changed since) | 0 | script echoed; both
plugin-detail pins are in its file list |
| `pnpm exec eslint` on the 15 touched `.ts` / `.tsx` files | `50e8f98`
| 0 | `0 errors`. No warning falls on a line this branch added except
the existing `react-hooks/exhaustive-deps` warning on the `synthParts`
deps line in `RecordDetailView`, which base `17acfbb` carries too (it
names other members) |
| `pnpm check:control-bytes` | `50e8f98` | 0 | `check-control-bytes: OK`
|
| `pnpm check:test-path-roots` | `50e8f98` | 0 | `check-test-path-roots:
OK` |
| `pnpm check:changeset-claims` | `50e8f98` | 0 | `No pending changeset
names a file this change touches.` |
| `pnpm check:pending-changeset-literals` | `50e8f98` | 0 | `No test
source names a pending changeset.` |
| `pnpm check:vi-mock-specifiers` / `vi-mock-inherit` /
`vi-mock-override-shape` | `50e8f98` | 0 / 0 / 0 | each `OK` |
| `pnpm check:new-line-citations` | `50e8f98` | 0 | `0 new citation(s)`
|
| `node scripts/check-changeset-presence.mjs` | `50e8f98` | 0 | `15
source file(s) of 2 released package(s) changed, and this change
declares 1 changeset(s)` |
| `node scripts/check-changeset-no-major.mjs` | `50e8f98` | 0 | `No
changeset declares a major bump.` |
**Declared narrowing.** The app-shell run covers the 242 of its 1043
tracked test files that name a touched module or one of its consumers.
The selection is `git grep -l` over app-shell's test files for
RecordDetailView, RecordFormPage, ObjectView, AppContent,
useRecentItemLabel, deriveRelatedLists, useNavTargetLabel, RecentApps,
HomeRail, CommandPalette, UnifiedSidebar, StudioHomePage, useFavorites,
FavoritesProvider and the related-list / reference-rail blocks. The
other files are left to CI's full `pnpm test`. Blind spot: a test that
renders these modules through a component whose name matches none of
those words. The narrowed runs are on `a46d4a9`; `50e8f98` changes one
pin's types only, and that pin re-ran on `50e8f98`.
## Acceptance notes (observations, not filed; no carrier)
- A favorite saved before this change keeps the label it was stored
with. Favorites store their text, and object favorites have no refresh
path (`refreshLabel` is called for record favorites only). Starring
again saves the plural. Read from source, not measured.
- `record:related_list` and `record:reference_rail` never read the
related object's declared `label` or `pluralLabel`, because they hold
its name only. Their fallback was bundle-then-humanized before and stays
that way. With no bundle entry, an object with a declared plural is
still headed by its humanized name there.
- The relationship-field half of the multi-relationship title ("·
Partner Project") is the field's authored `label`, untranslated, as
before.
- `ImportWizard` keeps the singular (see the enumeration table).
Session: `https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8`
---
_Generated by [Claude
Code](https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9fc68aa commit 885a96f
16 files changed
Lines changed: 1284 additions & 42 deletions
File tree
- .changeset
- packages
- app-shell/src
- console
- __tests__
- hooks
- __tests__
- utils
- views
- plugin-detail/src/renderers
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
181 | 181 | | |
182 | 182 | | |
183 | 183 | | |
184 | | - | |
| 184 | + | |
185 | 185 | | |
186 | 186 | | |
187 | 187 | | |
| |||
635 | 635 | | |
636 | 636 | | |
637 | 637 | | |
638 | | - | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
639 | 641 | | |
640 | 642 | | |
641 | 643 | | |
642 | 644 | | |
643 | 645 | | |
644 | 646 | | |
645 | 647 | | |
646 | | - | |
| 648 | + | |
647 | 649 | | |
648 | 650 | | |
649 | 651 | | |
| |||
Lines changed: 194 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
Lines changed: 108 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
0 commit comments