Repository navigation
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
Conversation
…raws no Object chip The strip listed `object_name`, the target object's API name. The page names the target object by its label on the Details tab's pointer-pair card instead; `object_name` leaves the object's `highlightFields` with the strip so the Details tab does not draw it as a row. Claude-Session: https://claude.ai/code/session_01LR1cD9JqgkeqY7k9rrZH8s Co-authored-by: Claude <noreply@anthropic.com>
…proval-object-chip
📓 Docs Drift CheckThis PR changes 1 package(s): 21 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 8 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 79ee1bc5cd0fbccfb259d6cea594a70c97d2f065 && git checkout 79ee1bc5cd0fbccfb259d6cea594a70c97d2f065
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 959ee317b13d98c97eba5ef2ac0bb270d8746d93 5f21f062ffdb9c69ea1250078ce1e1cba9419793 && git checkout -B drift-repro 959ee317b13d98c97eba5ef2ac0bb270d8746d93 && git merge --no-ff 5f21f062ffdb9c69ea1250078ce1e1cba9419793
node scripts/docs-audit/affected-docs.mjs --json 959ee317b13d98c97eba5ef2ac0bb270d8746d93
|
席位核对ACCEPT · #24375 ·
Generated by Claude Code |
Fixes #24375
Clause-②: no
The served
sys_approval_request_detailrecord page's highlights strip no longer draws an Object chip. It listedobject_name, the target object's API name as stored, so a request about an announcement readshowcase_announcementthere. The page still names the target object, by its label: the Details tab's card for therecord_id/object_namepointer pair heads the target record with its object's label.object_nameleaves the object'shighlightFieldswith the strip, so the Details tab does not draw it as a row.Responsibility:
plugin-approvals' own declarations (the page's highlightsfieldsand the object'shighlightFields) | 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 declaredreferenceViapair | reached by every user who opens an approval request's served page, live today.Readings are against objectstack
fb9c22aa0(this branch's base;origin/maince2e08ee3is merged in at5f21f062fand touches nothing underpackages/plugins/plugin-approvals) and objectui at the console pin.objectui-sha=0707b4ae37, bygit showandgit grep. The mergedmainmoves the pin to4016370af3c9; between the two pins the files on the chain below differ only in two hunks that retire thepermission-facet-linkwidget (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.
record:detailsnode. objectui'srecord-details.tsxdraws the object'shighlightFieldsminus every field the mounted strip registers and minus the H1 field (objectui#12433's declared fallback). Onmainthat isrecord_idalone.record_iddeclaresreferenceVia: 'object_name'.DetailSection.tsxresolves the pair withresolveRecordPointerand draws it with the renderer registered underRECORD_POINTER_CARD_TYPE, which is app-shell'srecord-pointer-card-renderer.tsx, mountingRecordPreviewCard.RecordPreviewCard, readable state: it reads the target object's definition (useMetadataItem('object', objectName)) and heads the card withobjectLabel({ name, label: def.label })fromuseObjectLabel, which resolves the object's translated label (objects.NAME.labelin the reader's locale) and falls back to the definition's label. The record's title sits under it. objectui pins this inRecordPreviewCard.test.tsx("heads it with the object label",getByText('Invoice')).unreadable(the reader may not read the target, or its definition is unavailable) draws "This record cannot be opened"; a request whosecancel_reasonisrecord_deleteddraws the tombstone, "Related record deleted";loadingdraws a spinner.routeApprovalRecordReads;GET /approvals/requests/IDhas no projection), soobject_namestays 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/distis 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
highlightFieldsminus strip)mainprocess_label,object_name,status,current_step_label,submitter_id,updated_atrecord_id(the card)process_label,status, …object_name,record_id: an "Object: showcase_announcement" row appears in the tabhighlightFields)process_label,status,current_step_label,submitter_id,updated_atrecord_id(the card), unchangedThe page's own rule decides
highlightFields: "The strip and the object's list change together, never the strip alone." The four list views'columnsare not that list. The rule binds the strip tohighlightFieldsbecause the Details tab readshighlightFields; no block on this page reads a list view's columns, so they stay as they are (see Acceptance notes).Four axes
object_labelalready serves the label to the inbox, live.object_labelsnapshot while the get already serves a LIVEobject_labelunder that name (enrichRowssets 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.process_name,current_step,object_name). A stored snapshot beside a live label is a second source an author can read by mistake.acted_as#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 inopenNodeRequest, a backfill leg indisplay-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_labelwould 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
highlightFieldsPR #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 namingsys_approval_requestas itsobjectName, and the eight objects that declare areferenceViapair):ObjectView/ObjectGriddefault columns: only for a list view that declares nocolumns; all four request list views declare them. Not reached.InterfaceListPage,ObjectKanbanandRelatedListdefaults: no interface page, kanban or related list of requests is declared here. Not reached.LookupFieldpreview columns (deriveLookupColumns): the two lookups to a request (sys_approval_action.request_id,sys_approval_approver.request_id) sit on objects whoseenable.apiMethodsis['get', 'list'], so no form opens that picker. Not reached.RecordPreviewCardfor 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.@objectstack/lintfield 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.tsxandrecord-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), underscripts/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.tsgains one case: neither the strip nor the object'shighlightFieldslistsobject_name; the Details tab is a barerecord:detailsnode and the object's list minus the strip is exactlyrecord_id, whosereferenceViaisobject_name; and, as a control,object_namestays a declared, requiredtextfield. The existing flow and step cases still holdprocess_nameandcurrent_stepout 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.mjsputobject_nameback into the page's strip, in its old position. On-disk evidence: anchor x1 → x0, replacement x0 → x1, bloba7d3ac80c4dc→2f28f5526bba. The pin went red,Tests 2 failed | 17 passed (19): the new case failed withexpected { 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 minusrecord_id. The restore leg: blob after restore == HEAD (a7d3ac80c4dc),git diff HEADempty,git statusclean. 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/objectstackat5f21f062fderived 67 commands for this diff (the same 67 as at770c72940). All 67 ran locally at5f21f062f, 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 --ranreconciliation: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:i18nandpnpm check:dual-build-cjs-loads(no full build yet), andnode scripts/check-plugin-teardown-shape.mjs --self-test(its positive control is pinned to commit621a4876, outside this shallow checkout; onegit fetch --depth=1of that commit).The repo-wide scans (
pnpm lintand the rest of the CI battery) and the vitestrepoproject are CI's; their PR check-runs are the reading.check:dev-prereqsandcheck:skill-frame-freshnesswere not run: the diff touches neitherpackages/specnor the four-axis frame files.Acceptance notes
all_requests,my_pending,submitted_by_me,completed) still listobject_name, so their Object column shows the API name. They are outside the page rule above and outside this card; reported to the seat.Generated by Claude Code