Uh oh!
There was an error while loading. Please reload this page.
fix(console): resolve a modal action's target as a page, not an object (#3530) - #2826
Merged
Merged
Conversation
#3530) Submitting a `type: 'modal'` action failed with "Error loading form — Bad Request". The console read the action's `target` as an OBJECT name and opened a create form for it, so a target naming a page issued `GET /meta/object/<page>` — which 400s — and the dialog rendered ModalForm's error state instead of the page. Every modal action in an app hit this. The spec is explicit that for `type: 'modal'`, `target` is "the modal/page name to open". - `normalizeModalSchema` no longer guesses "object" for a string target. It records the raw name and the new `useActionModal.resolveModalTarget` resolves it against metadata: page first, then object for back-compat. Resolution uses `getItem(type, name)`, a single-item fetch, so it never eagerly loads the lazy page/object lists — this hook is mounted at the console root. - The `create_x` / `edit_x` prefix convention still yields an object form, but only as a fallback: a page actually named `create_opportunity` now wins over the object `opportunity` the name would otherwise be parsed into. - A target naming neither reports what is wrong instead of surfacing a downstream HTTP error. Modal dispatch is also now the same on every console surface. `type: 'modal'` was wired straight to the server-action POST in `useConsoleActionRuntime` (list pages, SDUI pages, the declared-actions bar) while `RecordDetailView` opened modals client-side — the same button did two different things depending on where it was mounted. Both now run one rule: render `target` when it names a page or object, otherwise complete the action through its server-side handler, so a modal action bound to `engine.registerAction(...)` keeps working. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KYuc9N6YRbvoDMbCvJiX9f
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-zhuang
marked this pull request as ready for review
July 27, 2026 03:59
Uh oh!
There was an error while loading. Please reload this page.
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 freeto 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.
Fixesobjectstack-ai/objectstack#3530.
The bug
Submitting a
type: 'modal'action failed with "Error loading form — Bad Request". The console read the action'stargetas an object name and opened a create form for it, so a target naming a page issuedGET /api/v1/meta/object/<page>— which 400s — and the dialog rendered<ModalForm>'s error state instead of the page. The action body never ran and nothing was written.The spec is explicit about what the name means:
The reporter hit this on every modal action in their app (
log_call,log_meeting,create_campaign, …), including freshly authored ones, and the only workaround was re-authoring each as a screen flow.What was wrong
normalizeModalSchemamapped any bare string target straight to{ objectName: <target>, mode: 'create' }. That is a guess, and page-vs-object is not knowable without asking the metadata service.The issue also notes "two code paths that disagree about what
targetmeans", which turned out to be broader than the param dialog:type: 'modal'was wired straight to the server-action POST inuseConsoleActionRuntime(list pages, SDUI pages, the declared-actions bar) whileRecordDetailViewopened modals client-side. The same button did two different things depending on which surface mounted it.Changes
Target resolution (
useActionModal)normalizeModalSchemano longer guesses "object" for a string target; it records the raw name undertargetName.resolveModalTarget(schema)resolves that name against metadata — page first (what the spec says it means), then object for back-compat,nullwhen neither. It usesgetItem(type, name), a single-item fetch, so resolution never eagerly loads the lazy page/object lists; this hook is mounted at the console root, where a list read would cost every page.create_x/edit_xprefix convention still yields an object form, but now only as a fallback — a page actually namedcreate_opportunitywins over the objectopportunitythe name would otherwise be parsed into.Modal target "x" matches no page or object …) instead of surfacing a downstream HTTP error.One modal transport across the console (
useConsoleActionRuntime,RecordDetailView)type: 'modal'through the same rule: rendertargetwhen it names a page or object, otherwise complete the action through its server-side handler. That fallback is what keeps a modal action bound toengine.registerAction(...)working, and it is now available on the record page too (it previously had no server fallback at all).An explicit
{ objectName, mode }descriptor — what a lookup field's inline "create the referenced record" passes — is unchanged and resolves without any metadata lookup.Related
The reported action also declared a
bodyon atype: 'modal'action, expecting it to run on submit. Per specbodyis script-only, so it silently never runs; objectstack-ai/objectstack#3530 is paired with a spec-side change that rejects that combination at author time and points attype: 'script'.Testing
useActionModal.resolve.test.tsxpins the resolution contract: page wins, object fallback, prefix-vs-page precedence,nullfor unresolvable, descriptor pass-through, and — the regression itself — that a page target never issues theobjectlookup.useActionModal.test.tsupdated to the corrected normalization semantics.useConsoleActionRuntime.test.tsxfor the open-vs-server-fallback dispatch rule.packages/app-shellbuild (tsc) clean;vitest runoverapp-shell,core,react,fields— 324 files / 3598 tests pass; eslint reports 0 errors on the changed files.🤖 Generated with Claude Code
https://claude.ai/code/session_01KYuc9N6YRbvoDMbCvJiX9f
Generated by Claude Code