Repository navigation
fix(console): show the producer’s localized userMessage on metadata-save advisories and refusals - #33576
fix(console): show the producer’s localized userMessage on metadata-save advisories and refusals#33576objectstack-fleet[bot] wants to merge 1 commit into
Conversation
…-save advisories and refusals The post-save advisory toast, the metadata issue line (formatMetadataIssue, behind formatMetadataError and the publish-failure list) and Studio's refusal sentence now show an advisory's or a 422 issue's userMessage, the sentence the server composes in the caller's language, in place of the developer-facing English message. Findings and issues without one keep today's face. MetadataValidationIssue gains the optional userMessage, typed from the spec's RuntimeAuthoringIssue. Adds rendered pins for the advisory toast and for the record create/edit dialog's marked-refusal reading. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5m1xDT63aCaQJNjwXD
|
domain:ui#2 · os-project-manager · session_01FEkQ5oF8GgTHN2jBZoDwz8 Contract reviewServed-tier: Inputs: card #32657 (body, all 4 comments, including claim amendment 6886423383 that sets ① Derived judgmentsThe diff makes five accept-set or public-surface changes. Each is judged below.
No must-fix and no should-fix finding in the diff. ② Semver levelThe changeset
③ Boundary flags
Implemented-by: VERDICT: PASS |
Requested by Leehom · project thread
Fixes #32657
Refs #33524 (console half pinned, no console change; the card stays open for its server half)
Clause-②: yes (widening)
Metadata-protocol: none
维护者速读
userMessage),不再显示给开发者看的英文message/hint;Studio 被拒保存的提示句也直接用这句话,不再由控制台自己翻译服务端英文。服务端没写这句话的条目保持原样。另外给新建/编辑记录弹窗加了钉子测试:带userMessage的 403(中英文)显示该原因,不带的仍显示通用的「你没有权限保存此记录」。userMessage,HotCRM 看到通用句是因为 plugin-security 的「需要主记录编辑权限」拒绝根本没带userMessage,那是服务端的缺口,本 PR 不改服务端。userMessage的条目逐字不变。@object-ui/data-objectstack的MetadataValidationIssue多一个可选字段(只加不减)。回滚即 revert。ui/packages/app-shell/src/providers/saveAdvisoryToast.ts的formatFinding;ui/packages/data-objectstack/src/metadata-error.ts的formatMetadataIssue;ui/packages/app-shell/src/views/studio-design/metadataError.ts的issueRefusal。pnpm --dir ui exec vitest run packages/app-shell/src/providers/AdapterProvider.advisoryUserMessage-32657.test.tsx,预期 3 个用例全绿,其中中文用例在真实 sonner 弹出的提醒里读到「权限集“sales_rep”的持有者只能看到……」,读不到英文 message。What changes
Layer 3 of tracking #32654 (ruling C on #32440, 「32440 选C,32043 一起按C处理」): the console renders the user-facing text the producer composed in the request's language, with no per-code key table of its own.
saveAdvisoryToast.ts,formatFinding,@object-ui/app-shell). An advisory finding that carriesuserMessage(RuntimeAuthoringIssueSchema.userMessage, which the save and publish doors compose since feat(metadata-protocol): a metadata write's advisories and refusals come back in the caller's language (#32656, 1/2) #33276) renders as[rule] where — USER_MESSAGE, in place of the developer-facing Englishmessageandhint. The rule id andwhereare locators and frame both faces. A finding without one keeps[rule] where — message hint. The module header now says which prose is shown.metadata-error.ts,formatMetadataIssue,@object-ui/data-objectstack). A 422 issue withuserMessagerenders• path — USER_MESSAGE; one without keeps• path — message.formatMetadataErrorand app-shell'sformatPublishFailuresboth write issues through it, so the banners, the field designer's refusal and the publish-failure list follow.metadataError.ts,issueRefusal). When the issue carriesuserMessage, the sentence's problem part is that text verbatim; only an issue without one has its Englishmessageread throughtranslateValidationMessage, the console's own table. This is the console-side translation of server prose the ruling retires where the producer speaks.metadata-client.ts).MetadataValidationIssuegains optionaluserMessage, typedRuntimeAuthoringIssue['userMessage']from@objectstack/spec/api, so a rename in the spec stops it compiling. Published interface, optional member:Clause-②: yes (widening)..changeset/32657-console-localized-user-text.md:@object-ui/app-shell,@object-ui/console,@object-ui/data-objectstackpatch, with both declaration lines.The presence test is the same predicate in all three readers (a non-empty string after trimming, the rule
@objectstack/clientapplies when it liftsuserMessage).declaredUserMessage(@object-ui/react) is the one reader of an API ERROR's marking; it was not reused here because a finding element is not an error,@object-ui/data-objectstacksits below@object-ui/react(no import that way), andsaveAdvisoryToast.tsimports nothing that renders by design.Why
origin/maine3f0cfd4: the toast never readadvisories[].userMessage,formatMetadataIssuenever readissues[].userMessage, andissueRefusaltranslated the Englishmessageconsole-side.ModalForm(app-shellconsole/AppContent.tsx), which rethrows a refused write into the components form renderer; its refusal reader takesdeclaredUserMessage(error)before the fixedform.noPermissionToSaveline, as doFormPage.tsx'srefusalTextand the edit OCC path (occSave.tsx). The master-edit refusal reaches the console with nouserMessage: plugin-security'smasterEditDenied(security-plugin-cbp-write-gate.ts) throws a barePermissionDeniedErrorwhosemessageis the developer sentence "requires edit access to its master record", and fix(plugin-security): permission refusals speak the caller's language, falling back to the workspace default (#32656, 2/2) #33290 localized other refusals, not this one. So the generic line is the correct reading of what the server sends, and acceptance 1 and 2 of console: the record create/edit dialog shows only "You don't have permission to save this record." on a controlled_by_parent master-edit refusal, dropping the API's reason ("requires edit access to its master record"); #7366 regression on this path #33524 need the producer to compose a localizeduserMessagenaming the master. That is a server change this PR does not make; the card stays open for it (seat ruling in the card thread).Tests
AdapterProvider.advisoryUserMessage-32657.test.tsx(rendered end to end): the realAdapterProviderbuilds the real adapter, the real SDK client parses a save response from afetchdouble, and the real sonnerToasterdraws the toast; the assertions read the toast's text on screen.userMessage(thesecurity-private-no-readscopecatalog sentence for these params) shows, with the rule id, and neither the Englishmessagenorhintshows;userMessageshows the same way;message+hint+ rule.saveAdvisoryToast.test.ts: two cases added (the exact line with auserMessage; CONTROL: a blank one is no sentence).metadataError.issueUserMessage-32657.test.ts:formatMetadataIssuewith and withoutuserMessage;formatMetadataErrorandformatPublishFailurescarry it;flowSaveRefusal(throughissueRefusal) carries a zhuserMessageverbatim and not the console's translation, and CONTROL: an issue without one keeps the console's reading.ModalForm.markedRefusal-33524.test.tsx(rendered; the dialog the console's New and Edit buttons open): the user types and presses the dialog's own Create or Update button, the data source refuses with a 403 shaped as the ObjectStack adapter throws it.userMessage: the sentence shows, the generic line does not, the developermessagedoes not;userMessageshows the generic line and withholds the developermessage.Red-first on base, and ablations
e3f0cfd4(tree only), the new and edited tests kept: Test Files 3 failed, 1 passed (4); Tests 6 failed, 26 passed (32). The six reds are exactly theuserMessagecases of the three console: render the localized userMessage and advisory text instead of substituting raw server prose (ruling C on #32440, layer 3) #32657 files; the controls passed.ModalForm.markedRefusal-33524.test.tsxpassed on base, 6 of 6: the console: the record create/edit dialog shows only "You don't have permission to save this record." on a controlled_by_parent master-edit refusal, dropping the API's reason ("requires edit access to its master record"); #7366 regression on this path #33524 console half does not reproduce. Restored withgit checkout HEAD --,git diff HEADempty.scripts/ablation-replace.mjs, each restored to the HEAD blob withgit diff HEADempty. ui vitest reads@object-ui/*from source, so no build sits between mutation and run.formatFinding'suserMessagebranch made unreachable: Tests 3 failed, 18 passed; the reds are the two rendereduserMessagecases and the unit case; both controls green.formatMetadataIssuemade to readmessagealways: Tests 2 failed, 3 passed.issueRefusal'suserMessagebranch made unreachable: Tests 1 failed, 4 passed.declaredUserMessage: Tests 4 failed, 2 passed; the four marked cases red, the two CONTROLs green. The first A4 attempt passed a two-line anchor and was refused before running anything; it was rerun with a one-line anchor.Acceptance notes
userMessagethat names the master, is reported to the seat as a server finding; the seat re-lanes the card.messageandhint, as the contract declares (the remaining catalogue is tracked on lint: every save-door advisory rule carries a localized user sentence (67 rule ids in 36 rule files remain after the security posture), per ruling C on #32440 #32835).DEVELOPER_ORGANIZATION_MEMBER_LIMIT_REACHED) is produced by cloud. If cloud sends it with auserMessage, the invite dialog's reader decides what shows; this PR did not measure that dialog.extractWriteErrorMessagealone (for example the sharing-rule clone dialog and the position and assigned-user sections), which reads the server'serror/messageand notuserMessage. Noted, not changed here: outside this card's declared surface.FormPage.tsxorflowResponse.tsedit, no server, spec or cloud change.Verification
Run on head
e0302529(baseorigin/maine3f0cfd4).Build, tests, type-check and lint
pnpm --workspace-concurrency=2 --filter '@objectstack/spec...' --filter '@objectstack/formula...' --filter '@objectstack/client...' --filter '@objectstack/lint...' --filter '@objectstack/sdui-parser...' build(VERDICT command-exit 0), thenpnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build(VERDICT command-exit 0).pnpm --dir ui exec vitest run --maxWorkers=2 packages/data-objectstack/ packages/app-shell/src/providers/ packages/app-shell/src/views/studio-design/ packages/plugin-designer/src/MetadataFieldsPage.refusalIssues-11302.test.tsx packages/plugin-form/src/ModalForm packages/components/src/__tests__/form-write-error-message.test.tsx: Test Files 499 passed (499), Tests 4343 passed (4343).pnpm --filter @object-ui/data-objectstack run type-check(tsc --noEmit),pnpm --filter @object-ui/app-shell run type-checkandpnpm --filter @object-ui/plugin-form run type-check(bothtsc --noEmit && tsc -p tsconfig.test.json, so the new test files are compiled).no-explicit-anylines inmetadata-client.ts).pnpm --dir ui run check:new-line-citations: 0 new citations.Derived gates (37)
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 37 families one0302529. All 37 exited 0.pnpm --dir ui exec vitest run scripts/was SIGKILLed in the loop while a 23-minute suite run held the box; rerun alone under the lock with--maxWorkers=2: Test Files 171 passed, 2 skipped (173), exit 0.--ranreconciliation: 37 derived, 37 run, 0 NOT-MEASURED. Changed lines: 446, under the 3,000 threshold. No dogfood battery: no!in the title and no(narrowing)arm.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Development by the card thread
session_01FEkQ5m1xDT63aCaQJNjwXDfor the seatdomain:ui#2.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01FEkQ5m1xDT63aCaQJNjwXD