Repository navigation
fix(console): a refused form submit says a localized sentence, never the server's response body - #29184
Merged
Conversation
…the response body
FormPage threw `Create failed (403): {"error":…}` and drew that string on the
banner and the toast. The refusal now carries the response the way the client
attaches it (status, code, details, userMessage) and the page reads it the way
the record dialog does: a denial is form.noPermissionToSave, a 5xx or a refusal
without a sentence is form.submitFailed, a 4xx keeps the server's own sentence
out of the envelope, and the named rows are still marked.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jRRUHCK9kYPaCjRKL2
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jRRUHCK9kYPaCjRKL2
…ence Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsR2jRRUHCK9kYPaCjRKL2
This was referenced Nov 2, 2026
Contributor
Author
|
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y 席位核对 · ACCEPT · 2026-11-02T09:13ZHead-sha:
Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Steedos · project thread
Fixes #29019
Clause-②: no
Metadata-protocol: none
维护者速读
/forms/NAME、应用内表单、公开/f/SLUG)提交被拒时,横幅和 toast 不再显示服务端原始 JSON,改为与记录对话框同一套读法:无权限显示本地化的「您没有权限保存这条记录。」,5xx 显示本地化的「保存失败,请重试。」,其他 4xx 显示服务端信封里的那句话;字段标记照旧。Create failed (403): {"error":…,"code":"PERMISSION_DENIED","object":…},连对象机器名都露出来了。FormPage.tsx的拒绝文案读取;没有新增 i18n 键(复用十个语言包里已有的form.noPermissionToSave/form.submitFailed)。回滚即还原此 PR。ui/apps/console/src/components/FormPage.tsx的WriteRefusal与refusalText;FormPage.refusalText-29019.test.tsx。cd ui && pnpm exec vitest run apps/console/src/components/FormPage.refusalText-29019.test.tsx,预期 7 个用例全绿。What changes
ui/apps/console/src/components/FormPage.tsxonly (plus its tests and a changeset):refusedWriteError(message, body)is replaced by a page-localWriteRefusalerror that attaches the response the way@objectstack/clientdoes:status,code,details(the wrapped envelope'serror.details, else the whole flat body) and the producer-markeduserMessagefrom either dialect. Its message is the server's own sentence (the envelope'serror, the wrappederror.message, or a plain-text body), never the body itself; the raw body stays on.bodyfor a debugger.@object-ui/components' form renderer, same@object-ui/reactreaders, same order):userMessage, or a delegated administrator's scope refusal, as written;403,PERMISSION_DENIED,FORBIDDEN) asform.noPermissionToSave("You don't have permission to save this record.");unmarkedRefusalLines), as before;4xx;5xx, or a refusal with no sentence (a proxy's markup page), asform.submitFailed("Could not save. Please try again.").extractFieldErrorsreads the samecode/detailsit read before.Clause-②: noholds by construction and no pack changes.Why
A member with read but no create right on an object opened its form link, pressed Submit, and read
Create failed (403): {"error":"You do not have permission to perform this action. …","code":"PERMISSION_DENIED","object":"qar8_fsb"}verbatim, in English, on the banner and the toast. The record dialog already says the localized denial for the same refusal.Tests
FormPage.refusalText-29019.test.tsx(7 cases): the QA run's 403PERMISSION_DENIEDcreate on the internal route reads the localized denial on the banner and the toast, identical, with no{, no status code and nofailed (; the same inzh(您没有权限保存这条记录。); the dispatcher's wrapped 403 on the public route; a producer-markeduserMessagesaid as written; a 400VALIDATION_FAILEDwithfields[]still marks its field and says the server's sentence; a 500 reads the generic line and not the driver text; a 502 markup page reads the generic line.Submit failed (400)) and now assert the server's sentence:FormPage.refusalMarks-27417.test.tsx(the banner/toast pin of the first case) andFormPage.nativeInvalid-27496.test.tsx.scripts/ablation-replace.mjs, anchor? refusalText(err, unmarked, t)x1 -> x0, replaced by the old… failed (STATUS): BODYtext): the new suite went 7/7 red; restored, blob equals HEAD andgit diff HEADis empty.Acceptance notes
/apps/:appName/forms/:namerenders underConsolePermissionsScope(App.tsxpathless layout around/homeand/apps/:appName/*), so it already holds the caller's/me/permissionsanswer. The app-less/forms/:nameroute of the reproduction is outside that scope (App.tsx, the/forms/:nameroute underProtectedRoutealone), so knowing the create right there needs a new request; the public/f/:slugis anonymous. Doing it on one route only would make the same form behave differently by URL, so it is reported to the seat as a finding instead.Could not open record … (STATUS): DETAIL, the load path, not the submit) still appends the response body; it is outside this card's ruling (the submit/create/update messages) and is reported to the seat.Verification
Local subset on head
bc782a0d(merged withorigin/mainasc83558d5; the FormPage suites re-run there):Tests
cd ui && pnpm exec vitest run --maxWorkers=2 apps/console/src/components/FormPage apps/console/src/components/InternalFormRoute apps/console/src/components/InAppFormRouteonc83558d5: Test Files 43 passed (43), Tests 390 passed (390).pnpm --filter @object-ui/console type-check: exit 0 (itstsconfig.jsonincludessrc, so the test files are compiled).scripts/ablation-replace.mjsonFormPage.tsx, anchor x1 -> x0, the new suite 7 failed (7); restored, blob == HEAD,git diff HEADempty.Derived gates (`dispatch-gates --commands`, 33 families), reconciled with `--ran`: 33 run, 0 NOT-MEASURED
Every other family is CI's: Lint UI, Type Check UI, Test UI and the rest of the required set on this PR.
Human Minimum
packages/specholds no runtime logic. (not touched)pnpm lintpasses. (CI's Lint UI is the authority)main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed. (not touched)content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
Generated by Claude Code