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
Console at phone width: Back with the nav drawer open leaves the page (to /home) instead of closing the drawer #29131
Filing gate: ② out-of-scope finding, class a, from the #29075 audit (phone-width Navigation flow, run record /mnt/project-files/issue-29075/run-record-before.md) · filed by domain:ui#1 · ⛔ Not a claim. No grade: triage sets it.
Business reason. On a phone, people close an open menu with Back, and Android's back gesture in the WebView does the same. Today Back with the nav drawer open throws them out of the list they were on and lands them on Home.
Measured (origin/main6b3ba82c, real Console, CRM and showcase, 375 and 390 px, step 07). On /_console/apps/com.example.crm/crm_lead, open the nav drawer and press Back: the URL becomes /_console/home and the drawer is gone ({"urlAfter":".../_console/home","drawerStill":false}), in all four runs.
Decision needed first.ui/AGENTS.md#8 classifies the drawer as ephemeral state, so it pushes no history entry. Closing it on Back needs either a history entry while it is open (and a rule for removing it on a normal close) or a popstate guard. Settle which one before code.
Wanted.
Below sm, Back with the nav drawer open closes the drawer and leaves the URL as it was.
Back with the drawer closed behaves as today.
Opening and closing the drawer by tap leaves no stray history entries, so Back never stops on a no-op.
Desktop is unchanged.
Tests. Open the drawer, Back, and the drawer closes on the same URL. Open, tap close, Back, and you go to the previous page. Desktop has no history change.
Reader.domain:ui (area:navigation). Size S to M, depending on the history decision.
Dedupe. Searched back drawer phone, android back drawer, sidebar back button: no product card.
Claim: PM loop round 12 · 2026-11-02T10:17Z
Session: session_01QsR2jTvKAh25Doey2NM9Qw (card thread, os-dev); dispatched by seat session session_01QsR2jR7ZFkpqmm4U8S1f9y
Account: zhuangjianguo
Branch: claude/project-thread-a4er0q (platform-assigned to the card thread; it can push only this branch)
Worktree: objectstack-issue-29131
Domain: domain:ui
Seat: domain:ui#4
File surface: a new nav-drawer Back hook in ui/packages/app-shell/src/layout/ (or chrome/), its one mount line in UnifiedSidebar.tsx or ConsoleLayout.tsx, the marker constant, unit tests, an e2e phone spec, and a changeset (stop on breach; explain in the report)
Container & model: M, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: default
Clause-②: no
Metadata-protocol: none
Responsibility: at phone width Back with the nav drawer open leaves the page for /home instead of closing the drawer | the drawer gets one router-owned history entry while open (seat ruling: no popstate guard) | every phone and native-shell user
Thread-read: 6572431951
Premise: holds (at 6b3ba82c, seat 1 measured 4/4 runs: URL becomes /_console/home and the drawer is gone)
Serial constraints cleared: PR #29168 (#29075, UnifiedSidebar.tsx) lands first or is merged in before the mount-line edit; #29141 runs in parallel on the same file, second to land merges main
{
"issue": 29131,
"status": "done",
"branch": "claude/project-thread-a4er0q",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29323",
"head": "a76bb1a8",
"session": "session_01QsR2jTvKAh25Doey2NM9Qw",
"premise_still_valid": true,
"summary": "Premise re-measured at main 07eaec2d (after PR 29168): Back with the phone drawer open left the list for /home in 4 of 4 runs (CRM and showcase, 375 and 390). Implemented the seat's ruling as a package-internal hook, usePhoneNavDrawerHistory in app-shell/src/layout, mounted once in ConsoleLayoutInner (no edit to UnifiedSidebar.tsx): opening the drawer pushes one router entry with NAV_DRAWER_OPEN_STATE (new constant in urlParams.ts); Back to an entry without the flag closes the drawer; every other close calls navigate(-1) only on a flagged entry; a link chosen inside the drawer replaces the flagged entry (document capture-phase click listener, preventDefault so Link skips its push; a link to the current page pops instead); a flagged entry this document did not push (reload) is replaced without the flag and the drawer stays closed; Forward to one it pushed reopens the drawer. The live e2e found a real race: BrowserRouter commits a push and an immediate Back as one transition with no location change, so a popstate listener also closes the drawer. Desktop writes no history.",
"files_changed": [
".changeset/29131-phone-nav-drawer-back.md",
"ui/e2e/live/phone-nav-drawer-back-29131.spec.ts",
"ui/packages/app-shell/src/layout/ConsoleLayout.tsx",
"ui/packages/app-shell/src/layout/__tests__/usePhoneNavDrawerHistory-29131.test.tsx",
"ui/packages/app-shell/src/layout/usePhoneNavDrawerHistory.ts",
"ui/packages/app-shell/src/urlParams.ts"
],
"line_budget": "625 changed lines, 6 files, under the 3000 threshold; inside the claim's file surface (hook, one mount line in ConsoleLayout.tsx, the marker constant, unit tests, live e2e phone spec, changeset). Neither the hook nor the constant is exported from the package entry (grep of src/index.ts and src/layout/index.ts: 0 hits), so Clause-2 stays no.",
"tests": "Unit, from ui/: usePhoneNavDrawerHistory-29131.test.tsx 7/7 (Back closes on same URL; Escape pops then Back goes to previous page; nav entry replaces, Back returns to the opening page; All apps link does not double-pop; reload on a flagged entry; Forward reopens; desktop CONTROL). With every ConsoleLayout.* and UnifiedSidebar.* test on the merged head 17898094: 31 files, 152 tests passed under os-verify-lock. Ablations (scripts/ablation-replace.mjs, anchor 1->0, restore blob == HEAD): hook body early-return -> 6 red, CONTROL green; link capture listener removed -> exactly the 2 link tests red. Live e2e (Console vite preview of the branch, showcase backend, Chromium touch): 9/9 passed on the final hook. Reverse: hook unmounted (mount line from base) -> 8/8 phone tests red, desktop CONTROL green; popstate close ablated -> exactly the 2 'Back with the drawer open' tests red. Step 07 table in the PR body: before 07a FAIL x4, after all 5 checks pass x4; script and 56 screenshots in project files issue-29131/. app-shell type-check exit 0 on 17898094; type-check:e2e exit 0 and ESLint 0 errors on the changed files, run before the last spec edit, which changed a comment only.",
"gates": "dispatch-gates --commands at 17898094 (merged main 7788eedb): 34 families, 34/34 exit 0 captured before any pipe; check:type-check-debt and pnpm --dir ui exec vitest run scripts/ (169 files, 4900 tests) under os-verify-lock. --ran reconciliation: 34 derived, 34 run, 0 NOT-MEASURED (a derived zero). check-sdui-manifest exit 0. PR body gated before pr_create: check-changeset-no-major, check-adr-0087-registration, check-closing-keyword-parity, check-partof-closing-keyword all exit 0. Head a76bb1a8 merges 10 more main commits (to 504ba510) touching none of the 6 paths. CI families: in_progress at report time; read from the PR's check runs.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0 — no GitHub MCP calls; card, comments and PR files read with gh api GET",
"api_writes": "2 — relay pr_create (objectstack-ai/objectstack#29323, read back byte-identical, assignees none) and this os-dev-report comment via post-stamped; plus git pushes of the branch (not REST)",
"deviations": [
"Beyond the ruling's text, two mechanisms the ruled behaviour needed: a popstate close (the router reports no location change when a Back lands before the push commits; measured red in the live spec, ablation shows it is load-bearing) and Forward-to-a-pushed-entry reopens the drawer (otherwise a reopen after Forward would push a second flagged entry and Back would stop on a no-op). A link to the page already shown pops the entry instead of replacing it, for the same no-op reason.",
"ui/package.json was edited once (live spec added to test:e2e:live:nightly) and reverted before the PR because it is outside the claim's file surface; no workflow runs the nightly list. The spec is in no script list; promoting it to test:e2e:live:ci is the seat's call.",
"Main merged twice (1aaa32a6, then a76bb1a8 right before pr_create); local gates and suites cite 17898094.",
"PR body ends with the AGENTS.md session-URL footer, not the harness reminder's footer form; the harness attribution header is kept as the first two lines."
],
"open_questions": [],
"out_of_scope_findings": []
}
Claim amended · 2026-11-02T11:58Z · the file surface widens by one script line; the branch, session and declaration lines are unchanged.
Clause-②: no
Metadata-protocol: none
The dev's live spec, ui/e2e/live/phone-nav-drawer-back-29131.spec.ts, found a real race: a Back pressed before the push commits. The popstate close that fixes it is covered only by this spec. A spec under ui/e2e/live/ runs only when a script list names it, so as delivered it would never run.
Ruling: add the spec to test:e2e:live:nightly in ui/package.json. The :ci allowlist keeps its two-minute budget (ci.yml, above Live E2E UI), and the unit suite already guards every rule on each PR. That suite is 31 files, 152 tests, and its ablation reds the popstate pair. Promoting the spec to :ci stays a separate call, made once nightly has a run population for it.
File surface (added): ui/package.json, that one script line only. check-governed-merges --test ui/package.json reads NOT governed.
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p2 (priority added) · 2026-11-02T08:20Z
⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Claim: PM loop round 12 · 2026-11-02T10:17Z
Session:
session_01QsR2jTvKAh25Doey2NM9Qw(card thread, os-dev); dispatched by seat sessionsession_01QsR2jR7ZFkpqmm4U8S1f9yAccount:
zhuangjianguoBranch:
claude/project-thread-a4er0q(platform-assigned to the card thread; it can push only this branch)Worktree:
objectstack-issue-29131Domain:
domain:uiSeat:
domain:ui#4File surface: a new nav-drawer Back hook in
ui/packages/app-shell/src/layout/(orchrome/), its one mount line inUnifiedSidebar.tsxorConsoleLayout.tsx, the marker constant, unit tests, an e2e phone spec, and a changeset (stop on breach; explain in the report)Container & model: M,
mode:thread,model: default tier (dispatch-gates --tier: no path-derived mandate),effort: defaultClause-②: no
Metadata-protocol: none
Responsibility:
at phone width Back with the nav drawer open leaves the page for /home instead of closing the drawer | the drawer gets one router-owned history entry while open (seat ruling: no popstate guard) | every phone and native-shell userThread-read: 6572431951
Premise: holds (at
6b3ba82c, seat 1 measured 4/4 runs: URL becomes /_console/home and the drawer is gone)Serial constraints cleared: PR #29168 (#29075, UnifiedSidebar.tsx) lands first or is merged in before the mount-line edit; #29141 runs in parallel on the same file, second to land merges main
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jTvKAh25Doey2NM9Qw
os-dev-report
{ "issue": 29131, "status": "done", "branch": "claude/project-thread-a4er0q", "pr": "https://github.com/objectstack-ai/objectstack/pull/29323", "head": "a76bb1a8", "session": "session_01QsR2jTvKAh25Doey2NM9Qw", "premise_still_valid": true, "summary": "Premise re-measured at main 07eaec2d (after PR 29168): Back with the phone drawer open left the list for /home in 4 of 4 runs (CRM and showcase, 375 and 390). Implemented the seat's ruling as a package-internal hook, usePhoneNavDrawerHistory in app-shell/src/layout, mounted once in ConsoleLayoutInner (no edit to UnifiedSidebar.tsx): opening the drawer pushes one router entry with NAV_DRAWER_OPEN_STATE (new constant in urlParams.ts); Back to an entry without the flag closes the drawer; every other close calls navigate(-1) only on a flagged entry; a link chosen inside the drawer replaces the flagged entry (document capture-phase click listener, preventDefault so Link skips its push; a link to the current page pops instead); a flagged entry this document did not push (reload) is replaced without the flag and the drawer stays closed; Forward to one it pushed reopens the drawer. The live e2e found a real race: BrowserRouter commits a push and an immediate Back as one transition with no location change, so a popstate listener also closes the drawer. Desktop writes no history.", "files_changed": [ ".changeset/29131-phone-nav-drawer-back.md", "ui/e2e/live/phone-nav-drawer-back-29131.spec.ts", "ui/packages/app-shell/src/layout/ConsoleLayout.tsx", "ui/packages/app-shell/src/layout/__tests__/usePhoneNavDrawerHistory-29131.test.tsx", "ui/packages/app-shell/src/layout/usePhoneNavDrawerHistory.ts", "ui/packages/app-shell/src/urlParams.ts" ], "line_budget": "625 changed lines, 6 files, under the 3000 threshold; inside the claim's file surface (hook, one mount line in ConsoleLayout.tsx, the marker constant, unit tests, live e2e phone spec, changeset). Neither the hook nor the constant is exported from the package entry (grep of src/index.ts and src/layout/index.ts: 0 hits), so Clause-2 stays no.", "tests": "Unit, from ui/: usePhoneNavDrawerHistory-29131.test.tsx 7/7 (Back closes on same URL; Escape pops then Back goes to previous page; nav entry replaces, Back returns to the opening page; All apps link does not double-pop; reload on a flagged entry; Forward reopens; desktop CONTROL). With every ConsoleLayout.* and UnifiedSidebar.* test on the merged head 17898094: 31 files, 152 tests passed under os-verify-lock. Ablations (scripts/ablation-replace.mjs, anchor 1->0, restore blob == HEAD): hook body early-return -> 6 red, CONTROL green; link capture listener removed -> exactly the 2 link tests red. Live e2e (Console vite preview of the branch, showcase backend, Chromium touch): 9/9 passed on the final hook. Reverse: hook unmounted (mount line from base) -> 8/8 phone tests red, desktop CONTROL green; popstate close ablated -> exactly the 2 'Back with the drawer open' tests red. Step 07 table in the PR body: before 07a FAIL x4, after all 5 checks pass x4; script and 56 screenshots in project files issue-29131/. app-shell type-check exit 0 on 17898094; type-check:e2e exit 0 and ESLint 0 errors on the changed files, run before the last spec edit, which changed a comment only.", "gates": "dispatch-gates --commands at 17898094 (merged main 7788eedb): 34 families, 34/34 exit 0 captured before any pipe; check:type-check-debt and pnpm --dir ui exec vitest run scripts/ (169 files, 4900 tests) under os-verify-lock. --ran reconciliation: 34 derived, 34 run, 0 NOT-MEASURED (a derived zero). check-sdui-manifest exit 0. PR body gated before pr_create: check-changeset-no-major, check-adr-0087-registration, check-closing-keyword-parity, check-partof-closing-keyword all exit 0. Head a76bb1a8 merges 10 more main commits (to 504ba510) touching none of the 6 paths. CI families: in_progress at report time; read from the PR's check runs.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "0 — no GitHub MCP calls; card, comments and PR files read with gh api GET", "api_writes": "2 — relay pr_create (objectstack-ai/objectstack#29323, read back byte-identical, assignees none) and this os-dev-report comment via post-stamped; plus git pushes of the branch (not REST)", "deviations": [ "Beyond the ruling's text, two mechanisms the ruled behaviour needed: a popstate close (the router reports no location change when a Back lands before the push commits; measured red in the live spec, ablation shows it is load-bearing) and Forward-to-a-pushed-entry reopens the drawer (otherwise a reopen after Forward would push a second flagged entry and Back would stop on a no-op). A link to the page already shown pops the entry instead of replacing it, for the same no-op reason.", "ui/package.json was edited once (live spec added to test:e2e:live:nightly) and reverted before the PR because it is outside the claim's file surface; no workflow runs the nightly list. The spec is in no script list; promoting it to test:e2e:live:ci is the seat's call.", "Main merged twice (1aaa32a6, then a76bb1a8 right before pr_create); local gates and suites cite 17898094.", "PR body ends with the AGENTS.md session-URL footer, not the harness reminder's footer form; the harness attribution header is kept as the first two lines." ], "open_questions": [], "out_of_scope_findings": [] }objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Claim amended · 2026-11-02T11:58Z · the file surface widens by one script line; the branch, session and declaration lines are unchanged.
Clause-②: no
Metadata-protocol: none
The dev's live spec,
ui/e2e/live/phone-nav-drawer-back-29131.spec.ts, found a real race: a Back pressed before the push commits. The popstate close that fixes it is covered only by this spec. A spec underui/e2e/live/runs only when a script list names it, so as delivered it would never run.Ruling: add the spec to
test:e2e:live:nightlyinui/package.json. The:ciallowlist keeps its two-minute budget (ci.yml, above Live E2E UI), and the unit suite already guards every rule on each PR. That suite is 31 files, 152 tests, and its ablation reds the popstate pair. Promoting the spec to:cistays a separate call, made once nightly has a run population for it.File surface (added):
ui/package.json, that one script line only.check-governed-merges --test ui/package.jsonreads NOT governed.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Seat review · 2026-11-02T12:44Z · PR #29323 at
9626c788: ACCEPT (on the PR;Clause-②: no, no contract review owed). It goes to the merge queue now.Generated by Claude Code