Repository navigation
fix(plugin-approvals): store the admin_rescue timeline note as a code and render it in the reader's language - #25627
Conversation
… and render it in the reader's language Claude-Session: https://claude.ai/code/session_01QeMwrPsf2zjo2pQLBzdWFV Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QeMwrPsf2zjo2pQLBzdWFV Co-authored-by: Claude <noreply@anthropic.com>
…sence read Claude-Session: https://claude.ai/code/session_01QeMwrPsf2zjo2pQLBzdWFV Co-authored-by: Claude <noreply@anthropic.com>
…min-rescue-comment-i18n
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 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 07fff77241d60d94ee031f0ec77b6fdd74d03465 && git checkout 07fff77241d60d94ee031f0ec77b6fdd74d03465
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 481ac6211641ccb7292508ed423f02e432322e4a b9c264fd8f5762d79743035b225ff618184ae653 && git checkout -B drift-repro 481ac6211641ccb7292508ed423f02e432322e4a && git merge --no-ff b9c264fd8f5762d79743035b225ff618184ae653
node scripts/docs-audit/affected-docs.mjs --json 481ac6211641ccb7292508ed423f02e432322e4a
|
席位核对ACCEPT · Checklist conclusions (read on GitHub, not from the report):
Deviations accepted: the worktree built on the platform-created branch; one Landing: Generated by Claude Code |
Fixes #25576
Clause-②: no
Metadata-protocol: none
What changed
The system note an
admin_rescuerequest writes on its own timeline ("Routed to this organization's administrators because the approver slate named no one: position 'X' has no holder.") was composed server-side as one English sentence and stored as thecommentof asys_approval_actionrow, andlistActionsserved it as stored, so a zh-CN reader read English.Now the note is stored as a code plus its parameters, and the approvals door renders it in the reader's language:
openNodeRequestrecords the note on the request's node-config snapshot as__adminRescueNote: { action_id, abandoned, fallback? }: the id of the timeline row it explains, the abandoned approver slots (type:value), and, when a declaredonEmptyApprovers: 'fallback'did not help, why (not_declared/resolved_to_no_one). The snapshot is where a request already keeps what it resolved at open time (__flowLabel,__round,__resolvedFrom; the open path's own comment: "Stamped on the snapshot (precedent: __flowLabel), so no schema migration"), so no column, object schema or spec contract moves. The row'scommentis still the English sentence, byte-identical to what it stored before.messagesface (translations/messages.ts,approvalAdminRescue*), inen,zh-CN,ja-JPandes-ES. The stored English is thoseenentries interpolated, never a second spelling (admin-rescue-note.ts). Theadmin_rescue:reasonaudit string and the log lines are built from the same clauses (the module-privateemptySlateReasonis replaced byadminRescueReason).listActions(GET /api/v1/approvals/requests/:id/actions) finds the row the snapshot names and overlays itscomment, in place, with the note rendered at the caller'sExecutionContext.locale. A read, never a write. The servedApprovalActionRowshape is unchanged.Precedent reused (no second mechanism)
reader-locale-labels.ts(Approvals show their flow and step names in the authoring language only: the request snapshots Flow.label / node.label at open, and the translation contract has no key for an approval step #24863) is how the approvals door already serves a request's flow, step and object labels in the reader's language:ExecutionContext.locale, negotiated byresolveBundleLocale, rendered byII18nService.t(), with a miss leaving the stored text standing. This PR adds one member,ReaderLabels.sentence(key, params), on the samet()and the same negotiated locale.approval-notification-text.ts+translations/messages.ts(plugin-approvals: the approval notifications (approval.requested, reminder, sla_breached, approved/rejected, out-of-office) reach every approver as literal English sentences built at emit; no catalog holds them, so no recipient locale can apply #24978). Its{{hole}}interpolation is now exported and used for the stored English.Who renders it, and the objectui half
The server half is complete on the approvals door: any reader of
GET …/approvals/requests/:id/actionswith a locale gets the localized note. Not verified from this session: which door the console's record Approvals tab reads its timeline through (objectui is not attached here). IfRecordApprovalsPanelreadssys_approval_actionthrough the generic data door rather than the approvals actions route, it is served the stored English, and the objectui half is to read the timeline through the approvals door.Rows already stored
A request opened before this change has no
__adminRescueNoteon its snapshot, so its note is served as stored, in English. No migration: the stored text stays correct English, it is display-only audit text, and its clauses survive only inside that prose. Parsing prose back into a code is the defect this replaces, so no backfill reconstructs them. Pinned by theCONTROLcase below.Serial constraint
PR #25600 (SLA reassign) landed on
mainwhile this was in flight, so this branch mergedorigin/main(b9c264fd8, clean, no conflict). The diff stays out of the SLA-reassign bodies.Verification (head
b9c264fd8)pnpm --filter @objectstack/plugin-approvals typecheck: exit 0.pnpm --filter @objectstack/plugin-approvals test: Test Files 96 passed (96), Tests 1259 passed (1259).admin-rescue-note.test.ts(the English is the sentence the row always stored; zh-CN / ja-JP render every clause and the separator; a clause the pack lacks falls back to English; a missing note sentence renders nothing; the snapshot reader accepts only the shape it writes).admin-rescue-named-slate.integration.test.tsadds five cases on a realObjectQL+SqlDriverwith a real in-memoryi18nservice loaded with the plugin's ownApprovalsTranslations. They pin: the snapshot stores the code and names the row; a zh-CN reader oflistActionsreads审批人名单未指向任何人,因此已转交本组织管理员处理:岗位「sales_manager」无人任职。; the fallback case renders both clauses joined the zh way; en and no-locale readers get the stored English; and a CONTROL where a pre-change request and a neighbouring system comment stay as stored.localizeAdminRescueNotecall inlistActions, throughscripts/ablation-replace.mjs): 3 failed / 13 passed in the integration file, with the zh-CN, fallback and CONTROL cases red. Restore verified byte-identical: blob after restore3527886551f8= HEAD,git diff HEADempty.approver-address-readers.test.ts: the newactor_idpresence read is classified inPERSON_SITES(roledisplay, count 2), as that pin requires of every read of the column.node scripts/pm/dispatch-gates.mjs --commands): all 72 run onb9c264fd8, every one exit 0.--ranreconciliation: "72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN".check:i18n: "all bundles in sync, no undeclared authoring keys". eslint--no-inline-configon the 8 touched.tsfiles: exit 0.Acceptance notes
step_name/step_labelhalf of the original card is not in this PR: triage dropped it as objectui#13248 (closed as completed).Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01QeMwrPsf2zjo2pQLBzdWFV