Repository navigation
Commit 666283a
fix(plugin-approvals): the approval request page's highlights strip draws no Object chip; the Details tab's record card names the target object by its label (#24500)
Fixes #24375
Clause-②: no
The served `sys_approval_request_detail` record page's highlights strip
no longer draws an Object chip. It listed `object_name`, the target
object's API name as stored, so a request about an announcement read
`showcase_announcement` there. The page still names the target object,
by its label: the Details tab's card for the `record_id` / `object_name`
pointer pair heads the target record with its object's label.
`object_name` leaves the object's `highlightFields` with the strip, so
the Details tab does not draw it as a row.
Responsibility: `plugin-approvals`' own declarations (the page's
highlights `fields` and the object's `highlightFields`) | the platform
path that already names the target by its label is the console's
pointer-pair card in the Details tab, fed by the declared `referenceVia`
pair | reached by every user who opens an approval request's served
page, live today.
Readings are against objectstack `fb9c22aa0` (this branch's base;
`origin/main` `ce2e08ee3` is merged in at `5f21f062f` and touches
nothing under `packages/plugins/plugin-approvals`) and objectui at the
console pin `.objectui-sha` = `0707b4ae37`, by `git show` and `git
grep`. The merged `main` moves the pin to `4016370af3c9`; between the
two pins the files on the chain below differ only in two hunks that
retire the `permission-facet-link` widget (`DetailSection.tsx`,
`fields/src/index.tsx`), not the pointer pair. The page was not driven
in a browser; the section below says why and what stands in for it.
## The measurement the route turns on: what the Details tab's card draws
The card's two routes split on one question: does the Details tab's
record card already name the target object by its label? Statically, at
the pin, it does, in the state where the reader can read the target.
- The Details tab is a bare `record:details` node. objectui's
`record-details.tsx` draws the object's `highlightFields` minus every
field the mounted strip registers and minus the H1 field
(objectui#12433's declared fallback). On `main` that is `record_id`
alone.
- `record_id` declares `referenceVia: 'object_name'`.
`DetailSection.tsx` resolves the pair with `resolveRecordPointer` and
draws it with the renderer registered under `RECORD_POINTER_CARD_TYPE`,
which is app-shell's `record-pointer-card-renderer.tsx`, mounting
`RecordPreviewCard`.
- `RecordPreviewCard`, readable state: it reads the target object's
definition (`useMetadataItem('object', objectName)`) and heads the card
with `objectLabel({ name, label: def.label })` from `useObjectLabel`,
which resolves the object's translated label (`objects.NAME.label` in
the reader's locale) and falls back to the definition's label. The
record's title sits under it. objectui pins this in
`RecordPreviewCard.test.tsx` ("heads it with the object label",
`getByText('Invoice')`).
- The other states name no object: `unreadable` (the reader may not read
the target, or its definition is unavailable) draws "This record cannot
be opened"; a request whose `cancel_reason` is `record_deleted` draws
the tombstone, "Related record deleted"; `loading` draws a spinner.
- The console reads the request through the approvals get
(`routeApprovalRecordReads`; `GET /approvals/requests/ID` has no
projection), so `object_name` stays in the row the card resolves whether
or not any block lists it.
Why not a browser reading: there is no console build in this container
(`packages/console/dist` is absent), so a screen reading needs
objectui's console built at the pin plus the showcase closure. The
static chain above and objectui's own pin on the card's heading stand in
for it.
## The route: no Object chip
| | strip | Details tab (`highlightFields` minus strip) |
|---|---|---|
| `main` | `process_label`, `object_name`, `status`,
`current_step_label`, `submitter_id`, `updated_at` | `record_id` (the
card) |
| strip only | `process_label`, `status`, … | `object_name`,
`record_id`: an "Object: showcase_announcement" row appears in the tab |
| this PR (strip and `highlightFields`) | `process_label`, `status`,
`current_step_label`, `submitter_id`, `updated_at` | `record_id` (the
card), unchanged |
The page's own rule decides `highlightFields`: "The strip and the
object's list change together, never the strip alone." The four list
views' `columns` are not that list. The rule binds the strip to
`highlightFields` because the Details tab reads `highlightFields`; no
block on this page reads a list view's columns, so they stay as they are
(see Acceptance notes).
### Four axes
- **Real business need.** The reader is an approver on the request page.
Measured: the same page's Details tab already heads the target record
with its object's label, translated. No producer writes an object-label
column today, and no reader other than this chip would read one. The
approvals get's `object_label` already serves the label to the inbox,
live.
- **Long-term soundness.** No new column, writer, backfill or snapshot.
The label route would store an `object_label` snapshot while the get
already serves a LIVE `object_label` under that name (`enrichRows` sets
it from the registered schema on every read): the approvals get would
answer the live label and the generic data door the stored one, two
answers for one field. A second name for the same fact would be a second
spelling instead.
- **Making AI mistakes harder.** The page lists only declared fields the
get serves, and the pin now holds the strip and the object's list free
of the three machine-name fields of this family (`process_name`,
`current_step`, `object_name`). A stored snapshot beside a live label is
a second source an author can read by mistake.
- **Startup focus.** This route removes one entry from two lists (4
files, under 70 lines). The label route, measured against #23889's
footprint for the same kind of column (`b74434c58`: object, writer,
backfill, four generated bundles, two integration tests), adds a
declared column, its writer in `openNodeRequest`, a backfill leg in
`display-label-backfill.ts`, four bundle entries and their tests, for a
fact the page already shows.
### A relabelled object
The card reads the object's definition when the page draws, so a
relabelled object shows its new label on every request, old ones
included. Under the label route a stored snapshot would keep the old
label on requests opened before the rename, while the approvals get's
live `object_label` would show the new one.
### What this route gives up
Where the card cannot read the target (the reader may not read it, or
the platform deleted it), the page names no object at all. Today the
strip names it there by its API name. The card allows this ("The strip
names the target object by its label, or does not name it at all").
## Other readers of the object's `highlightFields`
PR #24279 listed them at its own pin; the halves this repository decides
are re-checked here at `5f21f062f` (the list views' `columns`, the two
lookups' `apiMethods`, no page or related list naming
`sys_approval_request` as its `objectName`, and the eight objects that
declare a `referenceVia` pair):
- `ObjectView` / `ObjectGrid` default columns: only for a list view that
declares no `columns`; all four request list views declare them. Not
reached.
- `InterfaceListPage`, `ObjectKanban` and `RelatedList` defaults: no
interface page, kanban or related list of requests is declared here. Not
reached.
- `LookupField` preview columns (`deriveLookupColumns`): the two lookups
to a request (`sys_approval_action.request_id`,
`sys_approval_approver.request_id`) sit on objects whose
`enable.apiMethods` is `['get', 'list']`, so no form opens that picker.
Not reached.
- `RecordPreviewCard` for a pointer pair that names a request row (an
audit or activity row about a request): its strip loses the Object chip,
the same change. Whether such rows exist is NOT MEASURED.
- Backend readers (`@objectstack/lint` field references and semantic
roles, `metadata-core`'s field-security fold, `metadata-protocol`'s
build probes): no behaviour change for a list with one existing field
fewer.
objectui's served-page pin copies
(`apps/console/src/services/approvalRequestsDataSource.servedPage*.test.tsx`
and `record-details.highlightFieldsFallback-12433.test.tsx`) copy the
strip by value, and still copy it as it stood before #24264
(`process_name` … `current_step`); objectstack-ai/objectui#12484
rewrites them. Nothing in this repository reads them, and none of them
imports this package.
## Tests
At `5f21f062f` (the final head), under `scripts/pm/os-verify-lock.sh`
(`VERDICT command-exit 0`):
- `pnpm turbo run build --filter='!@objectstack/docs' --concurrency=2`:
exit 0, `76 successful, 76 total`.
- `pnpm --filter @objectstack/plugin-approvals exec vitest run
--maxWorkers=2 src/sys-approval-request-page.test.ts`: `Tests 19 passed
(19)` (18 before, plus the new case).
- `pnpm --filter @objectstack/plugin-approvals test`: `Test Files 81
passed (81)`, `Tests 1112 passed (1112)`.
- `pnpm --filter @objectstack/plugin-approvals typecheck`: exit 0,
`check:test-typecheck: OK`.
The same four passed at `770c72940`, before the merge.
The pin `sys-approval-request-page.test.ts` gains one case: neither the
strip nor the object's `highlightFields` lists `object_name`; the
Details tab is a bare `record:details` node and the object's list minus
the strip is exactly `record_id`, whose `referenceVia` is `object_name`;
and, as a control, `object_name` stays a declared, required `text`
field. The existing flow and step cases still hold `process_name` and
`current_step` out of both lists, so the strip and the object's list
carry none of the family's three machine-name fields.
**Ablation (one run, then restored), at `770c72940`.** `node
scripts/ablation-replace.mjs` put `object_name` back into the page's
strip, in its old position. On-disk evidence: anchor x1 → x0,
replacement x0 → x1, blob `a7d3ac80c4dc` → `2f28f5526bba`. The pin went
red, `Tests 2 failed | 17 passed (19)`: the new case failed with
`expected { strip: true, declared: false } to deeply equal { strip:
false, declared: false }`, and the existing case failed on the strip no
longer equalling the object's list minus `record_id`. The restore leg:
blob after restore == HEAD (`a7d3ac80c4dc`), `git diff HEAD` empty, `git
status` clean. The test imports the page from source, so no build sits
between the mutation and the run. No file is left behind by the
ablation.
## Gates
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `5f21f062f` derived 67 commands for this
diff (the same 67 as at `770c72940`). All 67 ran locally at `5f21f062f`,
after the full build above, each exit code captured before any pipe:
**67 exited 0.** Among them, as the claim named them:
- `pnpm check:page-declaration-shape`: `OK — 34 page entries across 3540
sources … all reach the kernel through a discoverable declaration`.
- `pnpm check:i18n`: `check-i18n-bundles: OK (10 package(s) — all
bundles in sync, no undeclared authoring keys)`. `pnpm
check:i18n-stale-fill`: `OK (11 bundle set(s) — no new stale fills, 0
baselined)`.
- `pnpm check:nul-bytes`: `OK (scanned 12325 text file(s) … no raw ASCII
control bytes)`.
- `pnpm check:test-source-alias`: `OK — 76 packages with tests scanned`.
- `node scripts/check-platform-object-tenancy-census.mjs` (and its
`--self-test`): `✓ platform-object tenancy census matches the tree: 83
platform-namespace objects`.
- `pnpm check:published-files`: `✓ … 73 publishable package(s)`. `pnpm
check:dual-build-cjs-loads`: `✓ … 111 published require entry point(s)
across 69 package(s) load`.
`node scripts/pm/dispatch-gates.mjs --ran` reconciliation: `67 derived,
67 run, 0 NOT-MEASURED, 0 UNRUN`.
On the first pass (at `770c72940`) three refused with exit 3,
PREREQUISITE NOT MET, and passed after their prerequisite: `pnpm
check:i18n` and `pnpm check:dual-build-cjs-loads` (no full build yet),
and `node scripts/check-plugin-teardown-shape.mjs --self-test` (its
positive control is pinned to commit `621a4876`, outside this shallow
checkout; one `git fetch --depth=1` of that commit).
The repo-wide scans (`pnpm lint` and the rest of the CI battery) and the
vitest `repo` project are CI's; their PR check-runs are the reading.
`check:dev-prereqs` and `check:skill-frame-freshness` were not run: the
diff touches neither `packages/spec` nor the four-axis frame files.
## Acceptance notes
- The four request list views (`all_requests`, `my_pending`,
`submitted_by_me`, `completed`) still list `object_name`, so their
Object column shows the API name. They are outside the page rule above
and outside this card; reported to the seat.
- The served page's H1 is unchanged by this PR.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01LR1cD9JqgkeqY7k9rrZH8s)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent f72ce93 commit 666283a
4 files changed
Lines changed: 57 additions & 10 deletions
File tree
- .changeset
- packages/plugins/plugin-approvals/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
| 18 | + | |
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| |||
183 | 183 | | |
184 | 184 | | |
185 | 185 | | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
186 | 210 | | |
187 | 211 | | |
188 | 212 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
64 | 64 | | |
65 | 65 | | |
66 | 66 | | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | | - | |
71 | | - | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
72 | 74 | | |
73 | 75 | | |
74 | 76 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
52 | | - | |
53 | | - | |
54 | | - | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
55 | 62 | | |
56 | 63 | | |
57 | 64 | | |
| |||
88 | 95 | | |
89 | 96 | | |
90 | 97 | | |
91 | | - | |
| 98 | + | |
92 | 99 | | |
93 | 100 | | |
94 | 101 | | |
| |||
0 commit comments