Repository navigation
fix(platform-objects,plugin-approvals): invite_user and approval_approve declare a description; Invite User names the invitee - #22230
Conversation
…ove declare a description; Invite User names the invitee
invite_user (sys_user, sys_member, sys_invitation) and approval_approve
declared no `description`, so their parameter dialogs showed the generic
subtitle. invite_user's success toast now interpolates the invitee from
the invite-member answer (`${result.email}`).
Claude-Session: https://claude.ai/code/session_01WkL6Eijt432S1Y7ekb6ovQ
Co-authored-by: Claude <noreply@anthropic.com>
…ndles, translate the new copy, pin it The extractor seeds the new `description` leaves with the English source and keeps the old translated success message; both are translated here (zh-CN, ja-JP, es-ES) and the bundles regenerated so the provenance companions drop the copied-from-source entries. Pins cover the declarations, the bundles and the served object metadata under zh-CN. Claude-Session: https://claude.ai/code/session_01WkL6Eijt432S1Y7ekb6ovQ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 6 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 8 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 55bbc88be844bc774ef546d8d3b31856d0b02d7d && git checkout 55bbc88be844bc774ef546d8d3b31856d0b02d7d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7b926f76007316ec13d2b17ec4b0a316b94e4d7e 4ccbef7b22e6af0672180e3d7bc2c096fb52efc7 && git checkout -B drift-repro 7b926f76007316ec13d2b17ec4b0a316b94e4d7e && git merge --no-ff 4ccbef7b22e6af0672180e3d7bc2c096fb52efc7
node scripts/docs-audit/affected-docs.mjs --json 7b926f76007316ec13d2b17ec4b0a316b94e4d7e
|
Fixes #22182
Clause-②: no
What changes
invite_useris declared three times, onsys_user,sys_invitationandsys_member. Each declaration now has adescription: "Invite someone by email address. They join this organization with the chosen role when they accept the invitation." The console shows it as the parameter dialog's subtitle. Before, the dialog showed the generic "Please provide the required information to continue."invite_user'ssuccessMessageis nowInvitation sent to ${result.email}. Before, it was "Invitation sent".approval_approvedeclares adescriptionthe wayapproval_rejectbeside it does: "Approve this request? Your approval is recorded, and the request moves on once this step has the approvals it requires." The wording says "once this step has the approvals it requires" because one approval finalizes a step only underfirst_responseor an override. Underunanimous,quorumandper_groupit does not.node scripts/check-i18n-bundles.mjs --write, per package. The extractor seeds each newdescriptionleaf with the English source in zh-CN, ja-JP and es-ES. It also keeps the old translated success message, because merge mode keeps any non-empty translated value. I translated those leaves by hand and ran--writea second time, so the provenance companions (*.source-hashes.generated.ts) dropped the copied-from-source entries the first run had recorded. Net: those companions are byte-unchanged against main. Every translated success message keeps the${result.email}token.packages/platform-objects/src/identity/invite-user-action-copy.test.ts(34 cases): the three declarations, their parity, the four bundles, and the served object metadata throughtranslateMetadataDocument('object', …)overSetupAppTranslations.packages/plugins/plugin-approvals/src/translations/approve-decision-question.test.ts(6 cases): the same shape overApprovalsTranslations.patcheach for@objectstack/platform-objectsand@objectstack/plugin-approvals.The
${result.email}choice was measuredThe card did not measure what
invite-memberanswers, so I booted the door in-repo and read the live answer. I usedpnpm dev:crm -- --fresh(before the change) andpnpm dev -- --fresh(showcase, after the change), signed in as the seeded admin, and sent the body the console's api handler sends:POST /api/v1/auth/organization/invite-memberwithemail,roleandorganizationId. Both runs answered HTTP 200 with the bare invitation row:The answer has no
datakey, and its top-level keys are not the legacy action envelope. So, at the objectui pina58626c8:useConsoleActionRuntime's api handler passes the body through asresult.data.readActionPayloadreturns it unchanged.composeSuccessMessagefills${result.email}from it. Its scope is{ result: payload }only. The runner has no submitted-parameter scope for success copy, so${result.*}is the only route.The address comes back lowercased: better-auth stores it that way.
Served metadata, before and after
GET /api/v1/meta/objects/NAMEwithAccept-Languageis the object-metadata read the console uses. These are theitem.actions[]entries.7d7943dd)I ran the console's success-copy composition step for step over the captured answer and the served
successMessage:ja-JP and es-ES are pinned at the bundle level only. Showcase declares
supportedLocales: ['en', 'zh-CN'], so those two locales serve English on every action there. The control isban_user's label, which reads "Ban User" under ja-JP.Tests and gates (all at HEAD
4ccbef7b22)pnpm --filter @objectstack/platform-objects test: 65 files, 1062 tests, all passed.pnpm --filter @objectstack/plugin-approvals test: 62 files, 905 tests, all passed.pnpm --filter … typecheckexited 0 for both packages. Each run includescheck:test-typecheck, and--listFilesshows both new test files are in the testtscprogram.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 65 commands, and I ran all 65, pluscheck:i18n-coverageandcheck:i18n-walk-parity. All 67 exited 0.--ranprinted "65 derived famil(ies) accounted for — 65 run, 0 NOT-MEASURED". The verdict lines:check:i18n: "OK (9 package(s) — all bundles in sync, no undeclared authoring keys)"check:i18n-stale-fill: "OK (10 bundle set(s) — no new stale fills, 0 baselined)"check:i18n-coverage: "OK (13 config(s), 621 baselined untranslated string(s), none new)"check:i18n-walk-parity: "11 declared group(s), 9 walked, 2 exempted"check:nul-bytes: OKeslint --no-inline-config --format jsonover the 14 touched.tsfiles, with 0 ignored by the config's own matching. Result: 0 errors and 0 warnings.eslint.config.mjsnever enables type-aware linting, so this diff cannot move the verdict on any untouched file. The fullpnpm lintis left to CI.Ablation (fix committed first; every leg through
scripts/ablation-replace.mjsWRAP mode)The subjects resolve to
src/through relative imports, so no rebuild was involved. Each anchor hit as declared, and the blob changed on disk. Each restore was proven against the HEAD blob withgit diff HEADempty, and once more for all four paths at the end.invite_user.descriptionset toundefinedinvite_user.successMessagereverted to the extractor-kept "邀请已发送" (3 hits)approval_approve.descriptionset toundefinedapproval_approve.descriptionset to the English source the extractor seedsAcceptance notes
docs/qa/platform-checklist/areas/ux-conventions.json(lines 27, 39 and 71) anddocs/qa/platform-checklist/FOLLOW-UPS.md(line 686) quote the oldsuccessMessage 'Invitation sent'. They describe the toast that setup: Invitations grid nests <a> inside <a>, Invite User gives no success feedback, and param dialogs share a generic subtitle objectui#11817 measured, and they are now one literal behind. I left them alone (outside this card's file fence).platform-objects' generated bundles, for other objects. Both are generator-owned (merge=os-regen). Whichever lands second mergesmainand regenerates.Generated by Claude Code