Skip to content

fix(studio): an uncommitted nav rename raises the held-edit guard - #27557

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-27464-interfaces-rename-held-guard
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-27464-interfaces-rename-held-guard

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

维护者速读

  • 改了什么:Studio Interfaces 导航编辑器里正在输入、尚未提交(未失焦/未回车)的重命名,现在作为「待保存编辑」上报给工作室已有的 held-edit 守卫。
  • 为什么改:此前刷新或关闭标签页会静默丢掉这次重命名,而其他各支柱的未保存编辑都会弹出浏览器离开确认。
  • 风险与回滚:低;只新增一个可选回调并接入现有上报,没有第二个 beforeunload 监听;回滚即 revert 本 PR。
  • 先看:StudioDesignSurface.tsx 中 navRenameClause 一段,AppNavCanvas.tsx 中 pendingRename 一段,新测试文件。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/StudioDesignSurface.navRenameHeld-27464.test.tsx,预期 4 passed。
  • 需要你做的:无,席位落地。

Fixes #27464

Clause-②: no
Metadata-protocol: none

What changes

  • AppNavCanvas gains an optional onRenamePendingChange(label | null): a card reports its entry while the rename input differs from the authored label, and takes the report back on commit, on Escape, when the text is typed back, and on unmount. Only a card that reported clears it, so other cards' mounts never erase it.
  • The Interfaces pillar folds that report into the one useHeldEditReport call it already makes (the leaf's held clause wins, then the rename clause engine.studio.held.navRename, en + zh). The surface's existing unsavedPillarWork therefore covers it: the one beforeunload guard, the shell link guard and Publish's held refusal. ⛔ No second listener, per triage.
  • AppPreview passes no report and is unchanged.

Tests

StudioDesignSurface.navRenameHeld-27464.test.tsx, through the real StudioDesignSurface route: typing a rename makes beforeunload cancel; Escape and typing the authored label back release it; a blur commit hands the rename to the nav autosave (the save carries the new label) and the guard releases once it lands. A fourth case renders a bare AppNavCanvas with no report (the AppPreview shape) and renames as before.

Ablation: deleting the onRenamePendingChange={setNavRenamePending} wiring through scripts/ablation-replace.mjs (anchor 1 to 0, blob b47faa46 to 2dc39a60) turned the three guard cases red (3 failed, 1 passed); restore verified blob equal to HEAD and git diff HEAD empty.

Acceptance notes

  • Commit on blur happens before an in-app link click, which is why in-app departures already asked; only the document-level guard missed the uncommitted text.

Verification

Local runs at 301cf9f
  • New suite: Test Files 1 passed, Tests 4 passed.
  • Related (at e814dd1, before a test-only trim commit; the trimmed suite re-ran 4/4 at the head): metadata-admin/previews/, studio-design nav* / held* / pillarNavGuard / InterfacesPillar*, and packages/i18n/: Test Files 271 passed, Tests 3248 passed, 13 skipped.
  • @object-ui/i18n and @object-ui/app-shell type-check: exit 0.
  • dispatch-gates --commands: 34 derived, 34 run, all exit 0; --ran: 0 NOT-MEASURED (derived zero). check:new-line-citations: 0 new.

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01L6bhgXU26UBEjd1KKHgNEx

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-sales · session_01L6bhgXU26UBEjd1KKHgNEx

席位核对 · ACCEPT · 2026-10-29T12:57Z

Head-sha: 301cf9ffb6193c7a5d64ee4e162b05be0a45d298 · card #27464 · claim 6495516023 · dev report comment 6496913008.

  • 形态: draft → main, Fixes #27464 only, 6 files (+195/−1), inside the claimed surface (AppNavCanvas.tsx, StudioDesignSurface.tsx wiring, new test, studio i18n en/zh, one changeset; AppPreview.tsx untouched because the prop is optional). check-governed-merges --pr 27557: NOT governed, 196 lines ≤ 3000.
  • 前提实测: seat read of the diff: NavCard reports label while editing and the trimmed draft differs from the authored label, clears only if it reported (so sibling mounts never clear another card's report), and clears on unmount; the Interfaces pillar folds it into its ONE existing useHeldEditReport call (leaf clause first), so the surface's existing beforeunload guard fires — no second listener, as the triage direction requires.
  • 门禁 check-runs (pinned to head): 44 success, 11 skipped, 0 failed, 0 pending; mergeable_state: clean. Tests: four pins; ablation of the onRenamePendingChange wiring turns the three guard legs red, the AppPreview-shape control stays green.
  • 接受的偏差: session-URL PR footer per os-dev.md; AppPreview.tsx not edited (optional prop, no default needed).
  • 范围外去向: none. Skills-docs: not owed (Metadata-protocol: none).
  • 落地条款: ordinary queue landing. Seat landing is on hold: the seat's landing op for fix(studio): OWD overview confirms the controlled-by-parent prerequisite on a 409 DESTRUCTIVE_CHANGE #27553 was refused by the session's permission classifier (recorded on Studio OWD panel: saving "Controlled by parent" on a detail whose master_detail is not yet required answers 409 DESTRUCTIVE_CHANGE with no way forward #27458, 6497200148); this PR lands once the maintainer settles that.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-sales · session_01L6bhgXU26UBEjd1KKHgNEx

席位核对 · ACCEPT 续接 · 2026-10-29T19:45Z

head 从 301cf9ff(ACCEPT 6497309909)移到 09d27e93,只多一个合并 main 的提交。冲突只在 ui/packages/i18n/src/namespaces/studio/en.ts 与 zh.ts:本卡的 engine.studio.held.navRename 和 main 上 #27617 的 engine.studio.held.celInvalid 加在同一位置,两行都保留。对 main 的 diff 仍是 +195/−1,与 ACCEPT 时一致。

新 head CI:55 个 check,44 success、11 skipped。check-governed-merges.mjs --pr 27557:NOT governed。原 ACCEPT 继续有效,落地。

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 tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Studio Interfaces: an in-progress navigation rename does not raise the beforeunload guard, so a reload or tab close silently loses it

1 participant