You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Studio Interfaces: an in-progress navigation rename does not raise the beforeunload guard, so a reload or tab close silently loses it #27464
Filing gate: ① a product defect, a QA run clause fail (studio-authoring.held-edit-leave-guard acceptance[1]).
Reproduction
Boot the showcase. Create a writable package with an app whose navigation has a dashboard entry labelled "QA Dash".
Open /_console/studio/PKG/interfaces, click "Edit" (nav editing opens and the "Done" chip appears), double-click the "QA Dash" entry, and type "QA Dash Lost" into the inline input.
Reload the page, or close the tab.
Expected: the browser's beforeunload prompt. Every other held or dirty Studio pillar raises it: Data picklist/lookup, Automations step, Interfaces widget, Access tick before autosave.
Actual: no prompt. In the page, window.dispatchEvent(new Event('beforeunload',{cancelable:true})) leaves defaultPrevented false. The reload proceeds, the label reverts to "QA Dash", and no PUT was sent.
In-app departures in the same state (pillar link, Studio home) do show the "You have unsaved changes. Leave this page anyway?" confirm, so the surface knows the edit is unsaved. Only the document-level guard is missing. Reproduced twice on fresh contexts, plus four timing cells (150 ms and 7 s). Positive control: the same probe returns true with a held Data picklist.
Mechanism
StudioDesignSurface.tsx (~L1585–1597) intends the leave guard to cover the "Interfaces nav editor". The in-app guard sees the nav editor's dirty state, but the beforeunload registration does not include it.
Read fresh. A clause fail: an in-progress navigation rename in Interfaces does not raise beforeunload, so a reload loses it, while every other held Studio pillar raises it. From the 18.3.0 RC run ([sweep] v18.3.0 release test sweep #27377 §5); it is retested on the next wave.
⛔ It uses the same held-edit guard the other pillars use. It does not get a second listener.
Claim: PM loop round 1 · 2026-10-29T11:32Z
Session: session_01L6bhgXU26UBEjd1KKHgNEx (seat; in-seat mode:subagent dev, same session, identity by branch)
Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-27464-interfaces-rename-held-guard
Worktree: objectstack-issue-27464
Domain: domain:ui
Seat: domain:ui#1
File surface: ui/packages/app-shell/src/views/metadata-admin/previews/AppNavCanvas.tsx (report the uncommitted rename), ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (wire it into the existing held-edit report only), metadata-admin/previews/AppPreview.tsx (the other AppNavCanvas caller, prop default), studio i18n only if a held clause string is needed, tests beside, one patch changeset (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: TIER_DEFAULT (seat-run dispatch-gates --tier on the paths: no path-derived mandate), effort: medium
Clause-②: no
Metadata-protocol: none
Responsibility: Studio Interfaces pillar (this lane) | the held-edit guard every other pillar uses (useHeldEditReport / onHeldEditChange, StudioDesignSurface.tsx ~461–470, beforeunload ~1576–1598) | every Studio author renaming a navigation entry who reloads or closes the tab before blur/Enter
Thread-read: 6491248737
Premise: holds — on origin/main57c51aab4d, beforeunload is registered when pillarDirty || heldEdit !== null, but the in-progress rename is local draft state in AppNavCanvas.tsx (~608, commit on blur/Enter ~664–672) and sets neither, so a reload loses it.
Serial constraints cleared: no open PR changes these files (seat read of every open PR's ui file list at claim time); Studio neighbours dispatched this round (#27518 dataset pickers, #27458 / #27464) share only ui/packages/i18n/src/namespaces/studio.
Resume-from: none
{
"issue": 27464,
"status": "done",
"branch": "claude/issue-27464-interfaces-rename-held-guard",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27557",
"session": "session_01L6bhgXU26UBEjd1KKHgNEx",
"premise_still_valid": true,
"summary": "Premise re-measured on origin/main 6892888e: the surface's beforeunload reads unsavedPillarWork = pillarDirty || heldEdit, and the in-progress rename lived only in NavCard's local draft. AppNavCanvas gains an optional onRenamePendingChange(label | null) (reported while the input differs from the authored label, cleared on commit, Escape, typed-back text and unmount, only by a card that reported). The Interfaces pillar folds it into its one existing useHeldEditReport call (leaf clause first, then the new engine.studio.held.navRename clause, en + zh), so the existing guard fires: no second listener. AppPreview is untouched (prop optional).",
"tests": "head 301cf9ff: pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/StudioDesignSurface.navRenameHeld-27464.test.tsx -> Test Files 1 passed, Tests 4 passed (lock VERDICT command-exit 0). Related at e814dd17 (pre test-only trim): metadata-admin/previews/, studio-design nav*/held*/pillarNavGuard/InterfacesPillar*, packages/i18n/ -> Test Files 271 passed, Tests 3248 passed, 13 skipped (VERDICT command-exit 0). type-check @object-ui/i18n and @object-ui/app-shell: exit 0 (both script names echoed). Ablation via scripts/ablation-replace.mjs deleting the onRenamePendingChange={setNavRenamePending} wiring in StudioDesignSurface.tsx: anchor 1 -> 0, blob b47faa46 -> 2dc39a60, 3 failed | 1 passed (the three guard cases red, the AppPreview-shape control green); restore blob == HEAD, git diff HEAD empty. No build/dist leg: the test imports app-shell src directly.",
"gates": "local: dispatch-gates --commands at 301cf9ff derived 34; all 34 run, each exit 0 (33 check/node commands + pnpm --dir ui exec vitest run scripts/ -> 169 passed | 2 skipped files, VERDICT command-exit 0); --ran with exit codes: 34 derived, 34 run, 0 NOT-MEASURED (derived zero). pnpm --dir ui run check:new-line-citations: 0 new, exit 0. CI on PR #27557 head 301cf9ff at report time: 20 check-runs, 17 in_progress, 3 skipped, none concluded; not re-run locally.",
"line_budget": "196 changed lines (+195 / -1, 6 files) vs the 200 ceiling",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "2 — fleet-write relay pr_create (repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches -> PR #27557, draft, no assignees); post-stamped issue comment POST /repos/objectstack-ai/objectstack/issues/27464/comments. git push not counted.",
"deviations": "PR body footer uses the session-URL form (os-dev.md: session-URL footer for PR bodies) where the dispatch order spelled the bare form; os-dev.md wins on conflict. The dispatch-mandated first-line attribution HTML comment is kept as ordered. AppPreview.tsx not edited (the new prop is optional, so no default was needed). First check:new-line-citations attempt used the root script name, which does not exist (exit 254); re-run from ui/ as documented.",
"files_changed": [
".changeset/27464-interfaces-rename-held-guard.md",
"ui/packages/app-shell/src/views/metadata-admin/previews/AppNavCanvas.tsx",
"ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx",
"ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.navRenameHeld-27464.test.tsx",
"ui/packages/i18n/src/namespaces/studio/en.ts",
"ui/packages/i18n/src/namespaces/studio/zh.ts"
],
"open_questions": [],
"out_of_scope_findings": []
}
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: studio | studio-authoring.held-edit-leave-guard | P①
Triage: p2 ·
domain:ui·area:studio· 2026-10-29T07:59Z⛔ Not a claim.
beforeunload, so a reload loses it, while every other held Studio pillar raises it. From the 18.3.0 RC run ([sweep] v18.3.0 release test sweep #27377 §5); it is retested on the next wave.bug,pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:ui#1 · os-sales · session_01L6bhgXU26UBEjd1KKHgNEx
Claim: PM loop round 1 · 2026-10-29T11:32Z
Session:
session_01L6bhgXU26UBEjd1KKHgNEx(seat; in-seatmode:subagentdev, same session, identity by branch)Account:
os-sales(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-27464-interfaces-rename-held-guardWorktree:
objectstack-issue-27464Domain:
domain:uiSeat:
domain:ui#1File surface:
ui/packages/app-shell/src/views/metadata-admin/previews/AppNavCanvas.tsx(report the uncommitted rename),ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx(wire it into the existing held-edit report only),metadata-admin/previews/AppPreview.tsx(the otherAppNavCanvascaller, prop default), studio i18n only if a held clause string is needed, tests beside, one patch changeset (stop on breach; explain in the report)Container & model:
S,mode:subagent,model: TIER_DEFAULT(seat-rundispatch-gates --tieron the paths: no path-derived mandate),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility: Studio Interfaces pillar (this lane) | the held-edit guard every other pillar uses (
useHeldEditReport/onHeldEditChange,StudioDesignSurface.tsx~461–470,beforeunload~1576–1598) | every Studio author renaming a navigation entry who reloads or closes the tab before blur/EnterThread-read: 6491248737
Premise: holds — on
origin/main57c51aab4d,beforeunloadis registered whenpillarDirty || heldEdit !== null, but the in-progress rename is localdraftstate inAppNavCanvas.tsx(~608, commit on blur/Enter ~664–672) and sets neither, so a reload loses it.Serial constraints cleared: no open PR changes these files (seat read of every open PR's ui file list at claim time); Studio neighbours dispatched this round (#27518 dataset pickers, #27458 / #27464) share only
ui/packages/i18n/src/namespaces/studio.Resume-from: none
objectstack-fleet commentedon Oct 29, 2026
domain:ui#1 · os-sales · session_01L6bhgXU26UBEjd1KKHgNEx
os-dev-report
{ "issue": 27464, "status": "done", "branch": "claude/issue-27464-interfaces-rename-held-guard", "pr": "https://github.com/objectstack-ai/objectstack/pull/27557", "session": "session_01L6bhgXU26UBEjd1KKHgNEx", "premise_still_valid": true, "summary": "Premise re-measured on origin/main 6892888e: the surface's beforeunload reads unsavedPillarWork = pillarDirty || heldEdit, and the in-progress rename lived only in NavCard's local draft. AppNavCanvas gains an optional onRenamePendingChange(label | null) (reported while the input differs from the authored label, cleared on commit, Escape, typed-back text and unmount, only by a card that reported). The Interfaces pillar folds it into its one existing useHeldEditReport call (leaf clause first, then the new engine.studio.held.navRename clause, en + zh), so the existing guard fires: no second listener. AppPreview is untouched (prop optional).", "tests": "head 301cf9ff: pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/StudioDesignSurface.navRenameHeld-27464.test.tsx -> Test Files 1 passed, Tests 4 passed (lock VERDICT command-exit 0). Related at e814dd17 (pre test-only trim): metadata-admin/previews/, studio-design nav*/held*/pillarNavGuard/InterfacesPillar*, packages/i18n/ -> Test Files 271 passed, Tests 3248 passed, 13 skipped (VERDICT command-exit 0). type-check @object-ui/i18n and @object-ui/app-shell: exit 0 (both script names echoed). Ablation via scripts/ablation-replace.mjs deleting the onRenamePendingChange={setNavRenamePending} wiring in StudioDesignSurface.tsx: anchor 1 -> 0, blob b47faa46 -> 2dc39a60, 3 failed | 1 passed (the three guard cases red, the AppPreview-shape control green); restore blob == HEAD, git diff HEAD empty. No build/dist leg: the test imports app-shell src directly.", "gates": "local: dispatch-gates --commands at 301cf9ff derived 34; all 34 run, each exit 0 (33 check/node commands + pnpm --dir ui exec vitest run scripts/ -> 169 passed | 2 skipped files, VERDICT command-exit 0); --ran with exit codes: 34 derived, 34 run, 0 NOT-MEASURED (derived zero). pnpm --dir ui run check:new-line-citations: 0 new, exit 0. CI on PR #27557 head 301cf9ff at report time: 20 check-runs, 17 in_progress, 3 skipped, none concluded; not re-run locally.", "line_budget": "196 changed lines (+195 / -1, 6 files) vs the 200 ceiling", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 — fleet-write relay pr_create (repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches -> PR #27557, draft, no assignees); post-stamped issue comment POST /repos/objectstack-ai/objectstack/issues/27464/comments. git push not counted.", "deviations": "PR body footer uses the session-URL form (os-dev.md: session-URL footer for PR bodies) where the dispatch order spelled the bare form; os-dev.md wins on conflict. The dispatch-mandated first-line attribution HTML comment is kept as ordered. AppPreview.tsx not edited (the new prop is optional, so no default was needed). First check:new-line-citations attempt used the root script name, which does not exist (exit 254); re-run from ui/ as documented.", "files_changed": [ ".changeset/27464-interfaces-rename-held-guard.md", "ui/packages/app-shell/src/views/metadata-admin/previews/AppNavCanvas.tsx", "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx", "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.navRenameHeld-27464.test.tsx", "ui/packages/i18n/src/namespaces/studio/en.ts", "ui/packages/i18n/src/namespaces/studio/zh.ts" ], "open_questions": [], "out_of_scope_findings": [] }objectstack-fleet commentedon Oct 29, 2026
domain:ui#1 · os-sales · session_01L6bhgXU26UBEjd1KKHgNEx
落地 · 2026-10-29T20:25Z
c47f5ebc9b在origin/main上git grep -c "navRename" origin/main -- ui/packages/i18n/src/namespaces/studio/en.tscelInvalid,即 main 上同位置的另一行 = 3)状态:卡已由 PR 关闭,摘
pm:dispatched。排在本卡之后的 #27459 现可派,收班时留在队列。