Repository navigation
fix(plugin-approvals): an approval request record title reads the flow label (process_label), falling back to process_name - #24085
Conversation
display_title now reads process_label + ' · ' + record_id, falling back to
process_name when no label is stored, so the record page heading, its
breadcrumb and the action history request cell show the flow's label
instead of flow:NAME. titleFormat keeps {process_name}: a template cannot
fall back, and process_label can be null after the boot fill.
Claude-Session: https://claude.ai/code/session_018VqJPGA1NDNSCZsnecsChP
Co-authored-by: Claude <noreply@anthropic.com>
…proval-request-title-label
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. 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 e03394bb58815105f445bbbab197c414cae6b83e && git checkout e03394bb58815105f445bbbab197c414cae6b83e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8a83cebc15a5efedff290e2c208abfba08fca9d6 ff2cf67c91eda33273d5f33f4d60ae4c01015728 && git checkout -B drift-repro 8a83cebc15a5efedff290e2c208abfba08fca9d6 && git merge --no-ff ff2cf67c91eda33273d5f33f4d60ae4c01015728
node scripts/docs-audit/affected-docs.mjs --json 8a83cebc15a5efedff290e2c208abfba08fca9d6 |
席位核对 · ACCEPT ·
|
Fixes #24007
Clause-②: no
The request's computed record title reads the flow's label in place of
flow:NAME. No export, type, accept set or served column moves.What changed
sys_approval_request.display_title, the object'snameField, is nowrecord.process_label != null ? record.process_label + ' · ' + record.record_id : record.process_name + ' · ' + record.record_id.Discount Sign-off · opp_45.flow:deal_desk · opp_42, nevernull · ….sys-approval-action.object.ts,sys-automation-run.object.ts,sys-member.object.ts).titleFormatkeeps'{process_name} · {record_id}'(see below). The comment abovenameFieldsays why, and names the renderer that reads it first.sys-approval-display-title.test.ts: the request case is now two pins, and the pairing is restated (below).patchchangeset for@objectstack/plugin-approvals.No field description changed, so the translation bundles are not regenerated, and Acceptance 4 does not apply.
check:i18nreading: see Gates.Premise, measured first (Acceptance 2's question)
On
origin/mainb6805ac0f6:display_titlereadrecord.process_name + ' · ' + record.record_id.titleFormatwas'{process_name} · {record_id}'.process_labelisrequired: false.Can
process_labelstill be null after the open write and the boot fill? Yes. Measured on a realObjectQLoverSqlDriver(better-sqlite3, in memory), with a throwaway probe that was not committed:backfillApprovalDisplayLabelsthrows on a failed read. In the probe, afindthat refuses givesthrew: "datasource refused", and the row staysprocess_label: null.approvals-plugin.tslogs that atwarnand retries on the next boot. Its own message says the cells "stay blank until a later boot fills them".if (!svc) return).kernel:ready, so rows are readable before it finishes.process_namehumanizes to nothing (flow:) and whose config snapshot has no__flowLabel. The engine accepts that row:requiredrefuses only''.requestsFilled: 1). Theflow:row keptprocess_label: null.labelOrMachineName(flowLabel, processName) ?? null. That is null only whenflow:plusflowName ?? nodeIdhumanizes to nothing:labelOrMachineName(undefined, 'flow:')isundefined.So
'{process_label} · {record_id}'is not safe, andtitleFormatkeeps{process_name}.Which renderers read
titleFormatfirstsearchAllinpackages/metadata-protocol/src/protocol.ts. ItsrenderTitletriestitleFormatbeforenameField.sys_approval_requestis in its sweep.canServeApiOperation(enable, 'search')is true, and the auto-default search fields includeprocess_nameandrecord_id.searchAllon this branch at1afade209a: a row labelled "Discount Sign-off" gets the palette titleflow:deal_desk · opp_45. ItsnameFieldvalue isDiscount Sign-off · opp_45.mainwith the template swapped to{process_label}: a row with no label gets the palette title· opp_43, with no source at all.nameFieldabovetitleFormat(ADR-0079 order), read at objectuidaeb77f. That covers@object-ui/coregetRecordDisplayName, the record page H1 incontainers.tsx, andLookupField.request_idcell, readdisplay_title.The pairing, restated
sys-approval-display-title.test.tsheld the formula andtitleFormatto the same text. They now name different columns, so the file asserts:{process_name} · {record_id}. This is the old assertion, kept, plus a check thatprocess_labelis null on the row.{process_label} · {record_id}, andresolveRecordTitleagrees.titleFormatdraws{process_name} · {record_id}.Tests
All runs went through
scripts/pm/os-verify-lock.sh. Each verdict below is the lock's ownVERDICT command-exitline.1afade209a:pnpm --filter @objectstack/plugin-approvals exec vitest run --maxWorkers=2 src/sys-approval-display-title.test.tsgivesTests 6 passed (6), exit 0.ff2cf67c91, after mergingorigin/main8133cc7805, which brought fix(plugin-approvals): name the record by object label and id in every approval notification body #24064'sapproval-service.tschange into this package:vitest run --maxWorkers=2givesTest Files 81 passed (81)andTests 1105 passed (1105).pnpm --filter @objectstack/plugin-approvals run typecheckrunstsc --noEmiton both configs, thencheck:test-typecheck: OK, with 27 pinned signatures held.1afade209a: 81 files and 1104 tests passed. Typecheck exit 0.Ablation (one-shot, nothing committed)
The test subject is imported by relative path (
./sys-approval-request.object.js), so vitest readssrc/. Nodist/is involved, and no rebuild or dist preflight applies. Each leg went throughscripts/ablation-replace.mjsin wrap mode, with a shelltraprestore on top. Every restore was proven: the blob equals the HEAD blob8ac394bf6ba2, andgit diff HEADis empty.record.process_name + ' · ' + record.record_id. Anchor 1 → 0, blob8ac394bf6ba2→69cfd16dcea2.expected 'flow:deal_desk · opp_45' to be 'Discount Sign-off · opp_45', 1 failed and 5 passed.record.process_label != null ?becomestrue ?). Anchor 1 → 0, replacement 0 → 1, blob →a5aee4f1c4ed.expected null to be 'flow:deal_desk · opp_42', 1 failed and 5 passed.null · …" framing. CEL refusesnull + string, so the unguarded formula yields a null title, not the textnull · …. Either way the pin catches it.ablation-replacerefused it (replacement count 1 → 1) and restored, with no test run. It was redone with--deleteon the label branch.Gates
Local set.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat the final headff2cf67c91derives 65 commands. All 65 were run there, each exit captured before any pipe, and all 65 exited 0.Reconciliation.
--ran(each line records:: exit N) reports65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN, and the zero is derived from the recorded codes.Verdict lines worth naming:
check-i18n-bundles: OK (10 package(s) — all bundles in sync …), withplugins/plugin-approvals in sync (7 bundle(s)).check-adr-0087-registration: this PR adds no declared-breaking changeset.No empty-frontmatter changeset introduced by this diff.check-nul-bytes: OK.check:type-check-debt … none above its recorded number.check:cross-package-test-inputs OK.check-test-source-alias OK.A first union at
1afade209ahad two exit-3 results.check:i18nandcheck:dual-build-cjs-loadsreportedPREREQUISITE NOT MET, meaning nothing was measured. Both were measured green in the final union, after the prerequisite build.Not run locally (CI's runs):
test:repo, the type-check lanes, the path-scheduled CI jobs and the roster families the derivation names. No exported symbol was renamed or removed, socheck-platform-checklistwas not owed.origin/mainmoved to8a83cebc15after the merge. That is one TSDoc-only commit, inpackages/spec/src/contracts/http-server.ts, touching nothing this PR reads, so it was not merged in.Acceptance notes
titleFormatabovenameField, which the spec's owntitleFormatdescription contradicts ("an explicit nameField now takes precedence",packages/spec/src/data/object.zod.ts). So on this branch the palette still titles a labelled requestflow:deal_desk · opp_45. That is the renderer named above, and the cost of Acceptance 2's second arm. It is reported to the seat as a finding, not fixed here:metadata-protocolis outside this card's surface.''asprocess_label.prettifyMachineNamereturns'', notundefined, for such a name (flow:_);FlowSchema.name's pattern admits_. With an empty authored label, the open write stores'', which the new!= nullguard reads as a label. Measured on the chain function only (labelOrMachineName('', 'flow:_')is''). No real producer names a flow that way. Carrier: none.request_idcell draws this title through the lookup's targetnameField. That was read from the declaration, not measured on screen.Generated by Claude Code