Skip to content

fix(app-shell): a record form opened on the app-name address stays in the app after save - #28066

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-c1be89
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-c1be89

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread
Fixes #28036

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:整页记录表单在保存/取消后"要不要后退"的判断,改为读 react-router 自己写在浏览器历史里的条目序号(history.state.idx):只有控制台自己推入过页面(序号大于 0)才后退,否则编辑落到记录页、新建落到列表页,都在应用内。
  • 为什么改:用应用名地址(/apps/showcase_app/...)打开表单时,控制台加载时会用一次"替换"把地址换成包 id 地址,这次替换给条目生成了新 key,旧判断(key 不是 default 就后退)误以为身后有应用内页面,保存后后退出了应用、提示也丢了。替换不改序号,推入才加一,所以序号能分清这两种情况。
  • 风险与回滚:只改 RecordFormPage 的 goBack 一处判断,不碰 AppContent.tsx 的重定向。从列表、视图、记录页进入表单的路径仍然后退(单测与实栈对照都测了)。回滚 = revert 本 PR。
  • 先看:ui/packages/app-shell/src/views/RecordFormPage.tsx 的 goBack;RecordFormPage.aliasAddressReturn-28036.test.tsx;ui/e2e/live/record-form-direct-url.spec.ts 的新循环。
  • 两分钟验证:看本 PR 的 Live E2E UI 检查日志,Run the live e2e specs 里 record-form-direct-url.spec.ts 应有两条通过:package id 与 app name 各一条。
  • 需要你做的:无,席位落地。

What changes

  • RecordFormPage's goBack (@object-ui/app-shell). It went Back when location.key !== 'default'. The console's canonical-app-pathname effect replaces an app-NAME address with the package-id one on load, and that replace gives the entry a generated key with nothing of the console's behind it, so Save and Cancel ran navigate(-1) out of the document (console: a full-page record form opened by direct URL saves silently and then leaves the app for /_console/home #27454's fix held only on the package-id address). The page now reads react-router's own entry index, history.state.idx, at the moment of the click: react-router writes 0 on the entry the document loaded on, keeps it on a replace and adds one per push. Back only when it is a number above 0; otherwise the record detail (edit) or the object list (create), inside the app, as before. useLocation is no longer needed by the page and is dropped. Why the index rather than a console-written location.state flag: the index is already written by every push the console makes, through any component, and kept by every replace, so nothing else in the console has to remember to mark its navigations; a flag would need every pushing call site to set it.
  • jsdom tests. New RecordFormPage.aliasAddressReturn-28036.test.tsx loads the form on an app-name address under BrowserRouter on jsdom's real history, replays the console's canonical replace to the package-id address, and asserts edit Save lands on the record detail with one toast, create Save lands on the list with one toast, edit Cancel lands on the record detail with no toast, and the control: a form PUSHED from a view page, then canonicalised, goes Back to that view. Each cell also asserts the replace minted a non-default key, the condition the old test misread. RecordFormPage.directUrlReturn-27454.test.tsx keeps its five cells and assertions; its harness moves from MemoryRouter to BrowserRouter with a real react-router push, because the page now reads the browser history's entry index, which a memory router does not write.
  • Live spec. ui/e2e/live/record-form-direct-url.spec.ts now runs its edit case on both addresses (com.example.showcase and showcase_app); the landing assertion is the package-id record path for both. Its header says why both.
  • Changeset .changeset/28036-record-form-app-name-address.md, @object-ui/app-shell patch.

Tests

  • pnpm exec vitest run packages/app-shell/src/views/RecordFormPage (from ui/): 11 files, 65 tests passed (head 71c89ea2).
  • Ablation, under scripts/ablation-replace.mjs (anchor hit 1 to 0, blob 4cf71998 to e207c822, restored to the HEAD blob, git diff HEAD empty): goBack's condition put back to the old key test (history.state.key not default). Direction written before the run: the three app-name cells RED, the in-app control GREEN, the five console: a full-page record form opened by direct URL saves silently and then leaves the app for /_console/home #27454 cells GREEN. Observed: exactly that, 3 failed / 6 passed of 9. Run on 93e6b790 and again on 71c89ea2.
  • pnpm --filter @object-ui/app-shell type-check (both tsc programs, the test one included): exit 0 on 71c89ea2.
  • pnpm --dir ui run type-check:e2e: exit 0. eslint on the four changed files: 0 errors; the 17 warnings are in lines of RecordFormPage.tsx this diff does not touch.

Acceptance notes

  • The card's table, re-measured on this branch (head 1de7cdda; the later commit changes only the jsdom test harness), with the Live E2E UI job's build (its turbo filters plus VITE_BASE_PATH=/ vite build), start-backend.sh and vite preview, one fresh Playwright page per row:

    Address Mode history.state after load URL after Save Toasts
    showcase_app (app name) edit key generated, idx 0 record detail, in app 1
    showcase_app (app name) new key generated, idx 0 object list, in app 1
    com.example.showcase (package id) edit idx 0 record detail, in app 1
    com.example.showcase (package id) new idx 0 object list, in app 1
    com.example.showcase, control: pushed from the list edit idx 1 object list (Back), in app 1

    The app-name rows read about:blank and 0 toasts on test(ui): pin the direct-URL record form save and nav CEL visibility in Live E2E UI #28034's stack before this change (the card's table). The new rows came from a throwaway Playwright file, not committed. The control row's push was written the way react-router's own push writes it (pushState with idx + 1, then the popstate its listener reads), because no showcase object declares editMode: 'page', so the console's Edit and New open the modal form and no in-app control there pushes the full-page form; the jsdom control drives a real react-router push.

  • pnpm --dir ui run test:e2e:live:ci --retries 0 on the same stack: 16 passed (1.5 min on a shared box), the 15 before plus the new app-name case.

  • Not changed, noted: FormPage.tsx in ui/apps/console reads location.key !== 'default' for its Cancel, the same test this PR replaces; whether a canonical replace reaches its routes was not measured, and it is outside this card's file surface. PageHeader in ui/packages/layout still reads window.history.length > 1 for its back button.

  • console/AppContent.tsx is untouched (seat 4's open PR perf(console): opening a record re-reads no list, asks its field access once, and the bell polls once a minute #27907 holds it).

  • The project attribution link in the first two lines names the seat's thread, as the order requires.

Verification

Derived gates (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack), every command run with its exit code recorded before any pipe, reconciled with --ran: 34 derived, 34 run, 0 NOT-MEASURED.

34 derived gates, plus three outside the derivation
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @object-ui/app-shell type-check :: exit 0
pnpm --dir ui run type-check:e2e :: exit 0
pnpm --dir ui check:new-line-citations :: exit 0

All gate readings on head 71c89ea2.


Generated by Claude Code

… the app after save

RecordFormPage went Back when location.key was not 'default', but the
console's canonical-pathname replace mints a key on the entry the
document loaded on, so Save on an app-name address left the app. Read
react-router's own entry index (history.state.idx) instead: 0 on the
load entry, unchanged by a replace, one more per push.

Claude-Session: https://claude.ai/code/session_01Utp6CZxX4vFUoXs7HyvMsp
Co-authored-by: Claude <noreply@anthropic.com>
…place through buttons

Claude-Session: https://claude.ai/code/session_01Utp6CZxX4vFUoXs7HyvMsp
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 30, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

席位核对

ACCEPT · head 71c89ea29acae22706effdacc5734bf406f1d492 · 2026-10-30T11:37Z

  • Shape: draft → main, Fixes #28036. Clause-②: no and Metadata-protocol: none match the claim. The change is @object-ui/app-shell only, so no contract review is owed. console/AppContent.tsx, which seat 4's perf(console): opening a record re-reads no list, asks its field access once, and the bell polls once a minute #27907 holds, is untouched.
  • Mechanism, measured: AppContent's canonical replace mints a location.key on the load entry, so console: a full-page record form opened by direct URL saves silently and then leaves the app for /_console/home #27454's key !== 'default' test read "in-app history" and Save went Back out of the document. goBack now reads react-router's own history.state.idx at click time. That index is 0 on the load entry, kept by a replace, and one more per push. It goes Back only when the index is above 0; otherwise it goes to the detail page (edit) or the list (new), inside the app. This was chosen over a location.state flag because every push already writes the index, so no call site has to mark its navigations.
  • Tests: the new jsdom aliasAddressReturn-28036 covers three app-name cells plus a pushed control. Ablating back to the key test turns the three app-name cells red and leaves 6 green, as predicted. The console: a full-page record form opened by direct URL saves silently and then leaves the app for /_console/home #27454 test's harness moves from MemoryRouter to BrowserRouter because MemoryRouter writes no idx; its cells are unchanged. 65/65 pass. The live record-form-direct-url.spec.ts now saves on both addresses. The live re-measure has all four card rows in the app with one toast each, the in-app control goes Back to the list, and test:e2e:live:ci passes 16/16.
  • Gates (check-runs on the head): 44 success, 11 skipped, 0 failure, Live E2E UI included; mergeable_state clean. 34/34 derived gates exit 0 locally. check-governed-merges --pr 28066: NOT governed, 313 changed lines.
  • Accepted deviations: the console: a full-page record form opened by direct URL saves silently and then leaves the app for /_console/home #27454 test's router harness change is outside the named surface, and is declared.
  • Same family, not changed here: console FormPage.tsx's cancel uses the same key test, and the layout PageHeader uses history.length > 1. The seat notes them; neither is reported broken.
  • Landing: non-governed, all checks green, mergeable_state clean ⇒ the seat lands it (pr_ready + automerge_enable).

Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Console: a full-page record form opened on the app-name address saves, then leaves the document (#27454 holds only on the package-id address)

1 participant