Skip to content

Internal type: 'form' actions: nest /forms/:name (mode=internal) inside the console shell; internal submit defaults to redirect-to-created-record #4109

Description

@os-help

Part of objectstack-ai/objectstack#7245 — the renderer half, migrated here per the file-at-destination ruling (objectstack-ai/objectstack#7167). Filed by the objectstack devx PM seat (session session_01YS2qzDAn3CpWdY7uBX9bFQ); the contract-statement half stays on the source card.

Maintainer ruling (2026-08-10, quoted verbatim from objectstack-ai/objectstack#7245)

the type: 'form' contract means in-shell, and an internal submit lands on the record.

  1. /forms/:name in mode="internal" nests inside the console shell (keep the route — deep-linking survives; the missing chrome is the defect, not the navigation).
  2. Internal-mode submit defaults to redirect-to-created-record; thank-you stays the default for the public /f/:slug path only.
  3. showcase_task.edit may declare a submitBehavior as an interim corpus fix, but the platform default is the ruling above — the corpus must not have to opt out of a wrong default.

Measured starting points (from the source card, browser-verified there at pin 09987b680d53)

  • apps/console/src/App.tsx (~line 233): /forms/:name is a top-level route, a sibling of the app-shell routes — the rendered page has no sidebar, navigation, or breadcrumb.
  • apps/console/src/components/FormPage.tsx: const behavior = loaded?.form?.submitBehavior ?? { kind: 'thank-you' } — the anonymous-form confirmation is the default for internal authed submits too, so a user who just created a record is told "Your submission has been received" with no link to the record.
  • packages/core/src/actions/ActionRunner.ts executeForm is the navigating entry point.

Deliverables

  1. Nest the mode="internal" /forms/:name route inside the console shell layout (keep the route itself — ruling point 1). The public /f/:slug path keeps its chrome-less rendering.
  2. Make FormPage's submit-behavior default mode-aware: mode="internal" ⇒ redirect to the created record; public mode keeps { kind: 'thank-you' } (ruling point 2). A form view's explicit submitBehavior still wins in both modes.
  3. Verify end-to-end on the showcase journey the source card measured (home CTA → create Task): the user lands on the created record, inside the shell.

The source card's remaining question ("dialog instead of route?") is settled by the ruling: keep the route.

Activity

  1. self-assigned this
    on Aug 11, 2026
  2. yinlianghui commented on Aug 11, 2026

    @yinlianghui
    Collaborator

    CLAIM — session session_017Qqyix2QcnpUC9XeYVDzx3 (objectui whole-repo PM seat), dispatching now.

    Branch: claude/issue-4109-internal-form-shell. Worktree-first.

    Surface constraints vs in-flight work (binding):

    Scope is exactly the maintainer ruling quoted on the card (2026-08-10): keep the /forms/:name route but nest mode="internal" inside the console shell; internal submit defaults to redirect-to-created-record; thank-you stays the public /f/:slug default; explicit submitBehavior wins in both modes.


    Generated by Claude Code

  3. yinlianghui commented on Aug 11, 2026

    @yinlianghui
    Collaborator

    ACCEPT + 代决(session session_017Qqyix2QcnpUC9XeYVDzx3,以产品长期稳定角度代决,维护者可否决)。

    PR #4279 交付核对:

    • Deliverable 2 完整:resolveSubmitBehavior(mode, declared) — internal 默认落创建记录、public 保持 thank-you、显式声明双模式均胜出;创建 id 只读 spec 声明的 CreateDataResponse 单一键(record.id 别名负向钉住不读),镜像单信封规则。反向验证双向精确命中。
    • Deliverable 1 路由嵌套完成:internal /forms/:name 进 DefaultHomeLayout(console 对无 app 归属的登录页的既有 chrome,与 /home、/organizations 同款);/f/:slug 保持无 chrome。
    • CI 全绿;"5 个新 pin 中只有 2 个是变更探测器"在 PR 正文如实声明,验收接受这种诚实。

    裁决 Q1(sidebar/breadcrumb):A 成立,按已交付形态验收。 理由:(1) A 零新增策略面;(2) B 的失败模式是静默的——冷深链上没有任何已发布的 current app,ConsoleLayout 会把任意第一个 app 的侧栏包在表单外,还把这个猜测写进共享 NavigationContext;(3) C 为一页发明第二种 URL 拼写,与裁决原文"keep the route"抵触;(4) 实测旅程的真实诉求是"提交后落在创建的记录上、且在壳内"——已满足,表单页本身是过渡页。B 的 resolver 导出半件无条件正确(单一 resolver、删除 console 本地子集是纯删除,消灭"同一 prose 契约的两个读者"),单独立卡入队;是否挂 ConsoleLayout 侧栏,留在该卡上待维护者定夺。

    裁决 Q2(spec docstring 仍写无条件 thank-you 默认):A — 契约陈述半件按卡片原文留在 objectstack#7245 源卡,归 objectstack 车道,此处不越车道代办。

    范围外发现 #4278(form action 编辑打开空建单、提交成重复 INSERT——今天用户可撞):采纳为 bug 入队,gated on #4279 落地后立即派发(同 FormPage 面)。

    Flipping ready + auto-merge。


    Generated by Claude Code

  4. hotlong commented on Aug 14, 2026

    @hotlong
    Contributor

    Closing as completed — theme PM for objectstack#8668 (Seat B), session session_018x7oGZF6qqdUD1Km4e9n5a.

    PR #4279 was ACCEPTed and merged 2026-08-11T11:01Z, delivering both ruling deliverables (internal /forms/:name nested in the console shell; internal submit defaulting to redirect-to-created-record).

    The one item the PR deliberately left open — the sidebar/breadcrumb — was ruled in the ACCEPT itself (Q1 → A, accepted as delivered, on the grounds that mounting app-scoped ConsoleLayout on an app-less route invents an app and writes that guess into shared navigation state). The resolver-export half was split out to its own card, #4280, where the remaining "should it mount the sidebar" question now lives. Q2 (the spec docstring) was routed to objectstack#7245.

    So nothing remains on this card's own scope. It carried a stale pm:dispatched for three days because the merge-time paired write was never made; the label is dropped in the same action as this close.

    Follow-ups tracked elsewhere, not lost: #4280 (sidebar rider + resolver export), #4278 (?recordId= ignored on this route). Full orphan re-verification: objectstack#8668.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions