This repository was archived by the owner on Oct 26, 2026. It is now read-only.
Repository navigation
Commit 3409fe8
fix(console): the approvals drawer's record summary shows the record page's labels and value faces (objectui#11677) (#11702)
Fixes #11677
Clause-②: no
## What changed
The approvals inbox's request drawer now shows its record summary the
way the record page shows the record. The only code that changed is the
console page `apps/console/src/pages/system/ApprovalsInboxPage.tsx`. No
published package export, registry input or locale key changes.
- **The card reads the request object's field declarations.** It calls
the same `getObjectSchema(object_name)` that `useHiddenFieldsByObject`
already makes for this object. That read lands on the adapter's
`MetadataCache`, which de-duplicates in-flight reads, so no network
request is added. The new hook is `useRequestFieldDefs`.
- **Labels.** A declared field takes its declared label through
`useSafeFieldLabel().fieldLabel`, the translator the record page uses.
The card no longer uses `payload_labels` or `prettifyKey` for it.
- **Values.** A declared field's value is drawn by `RecordFieldFace`. It
passes the declaration and the stored value to `resolveCellRendererType`
and then `getCellRenderer` from `@object-ui/fields`, the chain
`DetailSection` renders a field through, and translates the options as
`DetailSection` does. This is the existing helper, imported. ⛔ There is
no second formatter. A server-resolved `payload_display` value (a
lookup's title) still wins.
- **Rows.** When the metadata is known, a snapshot key the object does
not declare gets no row, and neither does a reference with no resolved
title. When the metadata is unknown (not answered yet, failed, or a
schema with no fields), the card falls back to the snapshot rendering it
had before. The fallback direction is the same as the `hidden` trim's.
- **The lead amount** (the figure at the top of the card) follows the
same rules. Its candidate selection also skips undeclared keys when the
drawer has the declarations. The queue rows do not pass declarations, so
their choice is unchanged.
- **Two rows never share a label.** The page's private six-name
system-key list is replaced by `@object-ui/types`'
`isSystemManagedField`, the classifier the default list columns, record
pickers and related lists already use. It also drops the injected
`owner_id` column. A label two remaining rows still share is qualified
with the field key (`withDistinctLabels`).
- **The duplicate React key.** The summary grid rows are keyed by field
key. They were keyed by label.
## The measurement, and three premises it corrected
The real stack was booted: a showcase backend from objectstack
`01e0f71a` (`objectstack dev --seed-admin --fresh`) and this branch's
console on its own port, driven with Playwright. The same shapes were
then rendered through the page in jsdom.
**Before** (merge base `57d82cb`), Invoice Dual Sign-off: `Owner: Dev
Admin`, `Invoice Number`, `Owner: linus@example.com`, `Region: amer`,
`Status: sent`, `Issued On: 2026-04-19`, lead `Subtotal: 0`. React
logged "Encountered two children with the same key" twice per drawer
open, live and in jsdom. In jsdom the key argument reads `Owner`.
High-Value Expense: `Approved: 0`, lead `Total: 8,900`.
**After** (this branch, same live stack): Invoice: `Invoice Number:
INV-1012`, `Owner: linus@example.com`, `Region: AMER`, `Status: Sent`,
`Issued On: Apr 19`, `Tax Rate (%): 8`, lead `Total: 0`. These are the
record page's faces for the same record: AMER and Sent badges, `Apr 19`,
`TOTAL 0`. Expense: `Status: Submitted`, `Submitted On: Yesterday`,
`Approved: 0`, `Reimbursable: 8900`, lead `Total: 8900`. No
duplicate-key warning on open, and none after approving the invoice from
the drawer.
Corrected premises:
1. **The duplicate key was in none of the three candidate lists.** It
was the summary grid itself. Its rows were keyed by their label, and two
rows were labelled `Owner`.
2. **`owner_id` is a declared field.** The registry injects it on every
business object as a `system: true` lookup labelled "Owner". The live
metadata read and `payload_labels` both say so. The showcase invoice
also declares its own text `owner` (its row-level-security anchor) with
the same label, so the collision is between two declarations, not
between a declaration and a stray key. Dropping `owner_id` as
bookkeeping is what the platform's other default surfaces already do.
The label qualifier is the backstop for any other pair of fields with
one label.
3. **`Approved 0` is not a boolean.** `showcase_expense_report` declares
no boolean field. "Approved" is the label of `approved_amount`, a
`summary` roll-up, and its value for the demo report is 0. Its
record-page face is the summary cell, which prints the stored number.
The invoice's "Subtotal" is a `summary` too.
## Tests
New pin file `ApprovalsInboxPage.summaryFaces-11677.test.tsx`, 8 tests.
Its fixtures follow the live shapes: the snapshot key order, the nulls,
the hydrated `account`, `payload_display` with `owner_id` only, both
`Owner` labels in `payload_labels`, and the metadata read's `Total`
against the snapshot's `Subtotal`. Each value is compared with the
record page's own dispatch rendered standalone, next to literal anchors
(`EMEA`, `Sent`, raw strings absent). The file covers: both measured
requests, one `Owner`, no duplicate-key warning, the lead's label and
face, undeclared keys, two declared fields sharing a label, and the
fail-open path.
Fixtures updated beside it:
- `hiddenFieldTrim` declares every snapshot key it expects on the card.
Under the new rule an undeclared key takes no row once metadata has
answered. The trim and the promotion it pins are unchanged.
- `characterizationPins` moves its `_id`-trim fixture from `owner_id` to
`vendor_id`, because `owner_id` is now dropped as bookkeeping and never
reaches `prettifyKey`.
- `rawPayloadGate`: a header comment now names the classifier instead of
the removed private list.
Reverse verification, committed fix first, then each leg restored and
proven (blob equal to HEAD, `git diff HEAD` empty):
- the page at the merge base: 8 of 8 red;
- the private six-name list put back in place of `isSystemManagedField`:
3 red (the two `Owner` tests and the fail-open test);
- `withDistinctLabels` removed: 1 red (two declared fields sharing a
label);
- HEAD: 8 of 8 green.
The duplicate-key assertions sit after the label assertions in each
test, so on a defective tree the label assertion goes red first. Their
red direction is the merge-base measurement above, not those assertion
lines themselves.
Gates, local, at the head this body names:
All at head `e4b9a61`. Exit codes were captured before any pipe.
| command | exit | verdict line |
|---|---|---|
| `pnpm exec vitest run apps/console/` (the console project) | 0 | `Test
Files 151 passed (151)`, `Tests 1783 passed (1783)` |
| `pnpm exec vitest run
apps/console/src/pages/system/ApprovalsInboxPage.summaryFaces-11677.test.tsx`
| 0 | `Tests 8 passed (8)` |
| `pnpm --filter @object-ui/console type-check` (after `turbo run build
--filter='@object-ui/console^...'`) | 0 | `tsc --noEmit && tsc -b
tsconfig.node.json --force` clean; `--listFiles` includes the new test
file |
| `pnpm --filter @object-ui/console lint` | 0 | `0 errors`; this page
has the same warnings as the merge base, none new |
| `pnpm check:new-line-citations` | 0 | `0 new citation(s)` |
| `pnpm check:control-bytes` | 0 | `OK` |
| `pnpm check:changeset-claims` | 0 | `No pending changeset names a file
this change touches` |
| `pnpm check:pending-changeset-literals` | 0 | `No test source names a
pending changeset` |
| `pnpm check:handler-key-reads` | 0 | `OK` |
| `node scripts/check-changeset-no-major.mjs` | 0 | `No changeset
declares a major bump` |
| `node scripts/check-changeset-presence.mjs` | 0 | `5 source file(s) of
1 released package(s) changed, and this change declares 1 changeset(s)`
|
| `node scripts/check-changeset-fixed.mjs` | 0 | `All workspace packages
are in the changeset fixed group` |
| `node scripts/check-changeset-overwrite.mjs` | 0 | `No pre-existing
changeset was modified or deleted` |
| `pnpm check:vi-mock-override-shape` | 0 | `OK` |
| `pnpm check:test-path-roots` | 0 | `OK` |
| `pnpm check:phantom-deps` | 0 | `Every in-scope import is declared by
the package that publishes it` |
| `pnpm check:self-import` | 0 | `No package names itself inside its own
src/` |
| `pnpm check:i18n-keys` | 0 | every in-scope call-site key resolves |
Not run locally, declared to CI: the repository-wide `pnpm lint` / `pnpm
test` / `pnpm type-check` farm. The local runs are the console package's
own lint, type-check and test project. `turbo` affected-set reasoning
was not used.
## Acceptance notes
- **Summary roll-ups print ungrouped.** The two measured leads are
`summary` fields. Through the record page's summary cell
(`FormulaCellRenderer`) they print the stored number in mono, without
digit grouping or currency: `8900` where the old card printed `8,900`.
The record page draws them the same way. The gap belongs to that cell in
`@object-ui/fields` and is outside this card's file surface. Carrier:
none.
- **Two label sources disagree in the showcase.** The showcase
translation bundle labels `showcase_invoice.total` "Total" and the
object declares "Subtotal". The metadata read serves the bundle's label,
which the record page shows and this card now shows. `payload_labels`
carries the object's own label. The spec documents `payload_labels` as
localized only for a single-locale project. Observation only.
- objectui#2763 (parked for v18) plans to rebuild this surface. This
change does not start that rebuild.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent ce464d9 commit 3409fe8
6 files changed
Lines changed: 783 additions & 57 deletions
File tree
- .changeset
- apps/console/src/pages/system
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
| 21 | + | |
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
44 | | - | |
| 44 | + | |
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
| |||
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
74 | | - | |
75 | | - | |
76 | | - | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
77 | 77 | | |
78 | 78 | | |
79 | 79 | | |
| |||
100 | 100 | | |
101 | 101 | | |
102 | 102 | | |
103 | | - | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
104 | 107 | | |
105 | 108 | | |
106 | 109 | | |
107 | | - | |
| 110 | + | |
108 | 111 | | |
109 | 112 | | |
110 | 113 | | |
| |||
129 | 132 | | |
130 | 133 | | |
131 | 134 | | |
132 | | - | |
| 135 | + | |
133 | 136 | | |
134 | 137 | | |
135 | 138 | | |
| |||
228 | 231 | | |
229 | 232 | | |
230 | 233 | | |
231 | | - | |
| 234 | + | |
232 | 235 | | |
233 | 236 | | |
234 | 237 | | |
235 | | - | |
236 | | - | |
237 | | - | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
238 | 241 | | |
239 | 242 | | |
240 | 243 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
194 | 194 | | |
195 | 195 | | |
196 | 196 | | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
197 | 206 | | |
198 | 207 | | |
199 | 208 | | |
| |||
216 | 225 | | |
217 | 226 | | |
218 | 227 | | |
219 | | - | |
220 | | - | |
221 | | - | |
222 | | - | |
223 | | - | |
| 228 | + | |
224 | 229 | | |
225 | 230 | | |
226 | 231 | | |
| |||
238 | 243 | | |
239 | 244 | | |
240 | 245 | | |
241 | | - | |
242 | | - | |
243 | | - | |
| 246 | + | |
244 | 247 | | |
245 | 248 | | |
246 | 249 | | |
| |||
277 | 280 | | |
278 | 281 | | |
279 | 282 | | |
280 | | - | |
| 283 | + | |
281 | 284 | | |
282 | 285 | | |
283 | 286 | | |
| |||
290 | 293 | | |
291 | 294 | | |
292 | 295 | | |
293 | | - | |
| 296 | + | |
294 | 297 | | |
295 | 298 | | |
296 | 299 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
30 | | - | |
| 30 | + | |
| 31 | + | |
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
| |||
0 commit comments