Repository navigation
fix(plugin-approvals): an approval request is titled by its flow's label, never the raw record id or the flow's machine name - #24708
Conversation
…bel, never the raw record id
WIP: title pointer moves to process_label (served by the approvals get),
titleFormat drops {record_id}, display_title drops its record-id half.
Claude-Session: https://claude.ai/code/session_011TdZ8GjixNZFLz1xJD9zzK
Co-authored-by: Claude <noreply@anthropic.com>
…ew help text Claude-Session: https://claude.ai/code/session_011TdZ8GjixNZFLz1xJD9zzK Co-authored-by: Claude <noreply@anthropic.com>
…proval-request-title
…tract Claude-Session: https://claude.ai/code/session_011TdZ8GjixNZFLz1xJD9zzK Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 10 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
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 0bd2399c1bdaa16bfe0ba4b1604618031c4e127a && git checkout 0bd2399c1bdaa16bfe0ba4b1604618031c4e127a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8e14ce057415992cb045fe712860f21cf29c3036 1b47ec0748a12fbf8a8ad312e80f3178aef3f35c && git checkout -B drift-repro 8e14ce057415992cb045fe712860f21cf29c3036 && git merge --no-ff 1b47ec0748a12fbf8a8ad312e80f3178aef3f35c
node scripts/docs-audit/affected-docs.mjs --json 8e14ce057415992cb045fe712860f21cf29c3036
|
席位核对 · ACCEPT ·
|
Fixes #24665
Clause-②: no
objectstack-ai/objectui#12484 remains blocked until the console pin bump.
What changes
The served approval request page (
sys_approval_request_detail) authors no header, so the console titles it from the object's declaration over the row the approvals get serves: thenameFieldvalue, then the deprecatedtitleFormatonly where that value is empty (ADR-0079). The pointer wasdisplay_title, a formula. A formula has no stored column androwFromRequestserves none, so every page fell through to{process_name} · {record_id}:flow:NAME · RECORD_IDas its H1.packages/plugins/plugin-approvals/src/sys-approval-request.object.ts:nameFieldanddisplayNameFieldbecomeprocess_label, the stored flow-label column. The approvals get already serves it (rowFromRequest, through the display-label chain: the column, else the config snapshot's__flowLabel, else the humanized machine name). So the page's H1 is the flow's label in every subject state, and the get serves the declarednameFieldvalue. No change toapproval-service.tsand no new key onApprovalRequestRow: the route needs no spec change.titleFormatbecomes{process_name}, without{record_id}. A renderer reaches it only whereprocess_labelis empty (a row written before the column, until the boot backfill labels it). The approvals get never reaches it.display_titlestays declared. Its expression drops the· record_idhalf:process_label, elseprocess_name. That is the text the title ladder draws on the data door for every row the engine writes. Its help text and the three translated locales say so.enwas regenerated with the package's documentedos i18n extractcommand (viapackages/cli/bin/run-dev.js), and--checkreports 7 bundles in sync.The subject record stays named by the Details tab's reference card (
record_id/object_name). That card handles the readable, deleted and unresolvable states, which a title cannot.Acceptance
nameFieldvalue, and a pin holds it. Newsys-approval-request-title.integration.test.tsuses the realApprovalServiceon realObjectQLoverSqlDriver. The declaration'snameFieldisprocess_label, atextfield with a real DDL column, andgetRequestandlistRequestsserve the same non-empty value under it.flow:machine name, in each state, one pin per state. Readable (Rd4bl3Rec24665QQ, opened by the service), deleted (subject deleted, requestcancelled/record_deleted) and unresolvable (object_namenames an object the registry does not hold). In each, the served title equals the flow label, is non-blank, does not contain the record id and does not matchflow:. A fourth case: a row with no stored label and no snapshot label is served the humanized machine name (Invoice Approval), so the template is never reached on this door.#24087(PR fix(metadata-protocol): the search palette titles a hit by nameField before the deprecated titleFormat #24202) madesearchAll'srenderTitleread thenameFieldvalue first (packages/metadata-protocol/src/protocol.ts,titlePointerthentitleFormatSource), pinned byprotocol.search-title-namefield.test.ts("THE PIN — an object declaring both keys is titled by its nameField value"). The palette'sengine.findrow carries the storedprocess_label, so a request is listed under its flow label. On an unlabelled pre-backfill row it draws{process_name}, the same fallback as the data door. No new palette pin:packages/metadata-protocolis outside this claim's file surface, and the order is already pinned there.sys-approval-display-title.test.tsflips. The request now resolves toprocess_label. A labelled row's H1,display_titleandresolveRecordTitleare allDiscount Sign-off, with no· opp_45. An unlabelled row's pointer value is empty, and the template anddisplay_titleboth drawflow:deal_desk(nevernull, never the id).resolveRecordTitleanswersundefinedthere: the accessor reads the pointer and never invents a title.The open choice inside A1, read on the four axes
display_title: retire or keep? The dispatch said to retire it if nothing but the title ladder reads it. Something else does read it. The activity mirror (plugin-auditaudit-writers.ts) stores the title field's name as the provenance of each activity row'ssummaryandrecord_label(text_sources, fromtitleFieldOf). The read side (activity-field-redaction.tssourcesServed) serves those texts only while every named field is in the reader's served set. So retiring the field would withhold the summary and label of everysys_approval_requestactivity row written while it was the title. The 18.0 upgrade fixture'sdatabase.sqlcarries thattext_sourcesshape on other objects'display_title. This is a code reading. Activity redaction was not run for this object.minor, an ADR-0087 marker, a contract review) whose cost falls on stored history. Keeping it adds nothing and declares nothing new.Recommendation taken: keep it, with the reason in the field's comment. This PR therefore narrows nothing and carries
Clause-②: nowith apatchchangeset (.changeset/24665-approval-request-title.md). That follows the newest plugin-approvals convention onmain(.changeset/24375-approval-object-chip.md,24501-list-object-column.md: apatchfor a shipped object's display change, with no key added or removed).titleFormat: drop or keep as{process_name}? Kept, at{process_name}. Dropping it would make the unlabelled-row fallback differ by consumer. objectui's derivation would land on a_title/_nameaffix field, which is the machine name again.searchAll's candidate list holds none of this object's fields, so it would fall toString(row.id), the request's own id. Keeping it gives every door the same machine-name fallback, whichdisplay_titlealso draws. The key is deprecated and lint-advisory platform-wide: 66*.object.tsfiles inpackages/declare it. Retiring it is not this card's subject.Verification
All on head
1b47ec0748, worktree offorigin/main, merged withorigin/mainat9e2722a92.pnpm --workspace-concurrency=2 --filter '@objectstack/plugin-approvals^...' build, which reportedVERDICT command-exit 0.pnpm --filter @objectstack/plugin-approvals test:Test Files 85 passed (85),Tests 1146 passed (1146), which reportedVERDICT command-exit 0.pnpm --filter @objectstack/plugin-approvals typecheck:check:test-typecheck: OK, which reportedVERDICT command-exit 0. The first run caught 3 TS2345 errors in the new test's helper typing, fixed intest(plugin-approvals): type the served-title helper over the row contract.scripts/ablation-replace.mjs(anchor hit 1 to 0, blob changed) with a trap restore. Each restore was proven: the blob equals the HEAD blob andgit diff HEADis empty. The subject is imported relatively fromsrc/, so nodistleg applies.display_title: 7 failed of 11. The served title isundefined(expected 'undefined' to be 'string'), which is the defect's own mechanism.· record_id: 2 failed of 11.{process_name} · {record_id}: 3 failed of 11.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat1b47ec0748. They ran locally, with each exit code captured before any pipe: 65 exited 0 and 2 exited 3.pnpm check:dual-build-cjs-loadsneeds every package'sdist/.pnpm check:i18nneeds the built CLI; the package's documented extract command, run throughrun-dev.jswith--check, reports 7 bundles in sync. CI runs both.node scripts/check-plugin-teardown-shape.mjs --self-testfirst exited 3, because its pinned positive control was unreachable on the shallow clone. After fetching that commit it exited 0.--ranreconciliation: 67 derived, 65 run, 2 NOT-MEASURED (derived from the recorded exit 3), 0 UNRUN.repoproject (test:repo). This diff touches none of its tests, and CI'sTest Coreruns it.Acceptance notes
apps/console/src/services/approvalRequestsDataSource.servedPage*.test.tsx) copy the request definition withnameField: 'display_title'. They are copies, not readers, and they change with the console pin bump that approvals B1 closing card (ruling C on objectui#12438): the inbox opens a request on the served sys_approval_request page where the side sheet is, deletes the sheet, and lists each retired pin against its user report objectui#12484 waits on. Carrier: that card's dev.sys_approval_approver's title is{approver} · {request_id}(role:finance · areq_…), which also prints a raw id. Observation only: the reach was not measured, and that index object is not on the request page. Carrier: none.resolveRecordTitle(SysApprovalRequest, row)on a row with no stored label now answersundefined, where it answeredflow:NAME · RECORD_IDbefore. Server-side consumers (the activityrecord_label, lookup titles) see no title for such a row until the boot backfill labels it, which it does for every request.packages/metadata-protocol/src/protocol.search-title-namefield.test.ts:185describessys_approval_request's old declaration as the defect it pinned. That is historical and accurate, with its own inline fixture. Left as is (outside the file surface).Generated by Claude Code