Desktop: back/forward navigation should be global, not per-tab #6

Closed
opened 2026-08-18 05:53:05 +00:00 by logan · 1 comment
Collaborator

Report

Back is tab-scoped. If I am on an album detail view and click over to the Tracks tab, there is no way to get "back" to that album — I have to go to Albums and find it again. Back and forward should be global history, like a browser.

Findings

  • frontend/index.ts already records every navigation with history.pushState and replays popstate with _isBack; the in-app back buttons (navigate-back) go through history.back(). So a global stack exists.
  • What is missing is (a) forward, and (b) a persistent global back/forward affordance in the chrome rather than per-view back buttons that read as tab-local.
  • The first navigation deliberately replaces the launch entry (so Android does not cost a back press before quitting) — keep that.

Direction

Add global back/forward controls to the header driven by history.back()/history.forward(), and make sure a primary-view navigation is a history entry too (it is cached, not unmounted, so restoring one is a class toggle). Disabled state needs care: the History API does not expose stack depth, so track our own counters alongside pushState.

**Report** Back is tab-scoped. If I am on an album detail view and click over to the Tracks tab, there is no way to get "back" to that album — I have to go to Albums and find it again. Back and forward should be global history, like a browser. **Findings** - `frontend/index.ts` already records every navigation with `history.pushState` and replays `popstate` with `_isBack`; the in-app back buttons (`navigate-back`) go through `history.back()`. So a global stack exists. - What is missing is (a) forward, and (b) a persistent global back/forward affordance in the chrome rather than per-view back buttons that read as tab-local. - The first navigation deliberately *replaces* the launch entry (so Android does not cost a back press before quitting) — keep that. **Direction** Add global back/forward controls to the header driven by `history.back()`/`history.forward()`, and make sure a primary-view navigation is a history entry too (it is cached, not unmounted, so restoring one is a class toggle). Disabled state needs care: the History API does not expose stack depth, so track our own counters alongside `pushState`.
logan added the Area/Shell-NavKind/FeaturePlatform/Desktop
Priority
Medium
3
labels 2026-08-18 14:36:04 +00:00
logan self-assigned this 2026-08-19 21:39:35 +00:00
logan added the
Status
In Progress
label 2026-08-19 21:39:35 +00:00
Author
Collaborator

Taking this on branch feat/6-global-back-forward, now that #72 has
landed — the two share handleNavigate, and #72 made one place
responsible for saying where the user is, which is what a forward
control has to keep honest. (The argument for taking them in this
order, against #73's stated one, is a comment on #73.)

Approach.

The stack is already global: recordNavigation pushes every
navigation and popstate replays any entry, goForward() included —
back-navigation.spec.ts has asserted forward since it was written.
So the report's "back is tab-scoped" is about the affordance: the
only way back is a detail view's own button, which disappears the
moment you leave for another tab. What is missing is a persistent
control, and a truthful disabled state for it.

  • A <nav-history> in the top bar, driven by history.back() /
    history.forward(), published from the shell the way the active
    view now is.
  • Depth is ours to track, since the History API exposes none. The
    existing pushedEntries counter cannot answer forward: popstate
    fires identically in both directions, so a counter that decrements
    on every pop cannot tell them apart. Each entry carries an index
    instead, and the shell keeps the current one and the high-water mark
    — back is available below the floor, forward below the mark.
  • The launch entry is still replaced, not pushed, so the app still
    exits on one back press from the root.

Verifying at all three size bands, and checking the header still fits
at 900×600 — #69 is recent enough that two more buttons in that bar
need measuring rather than assuming.

Taking this on branch `feat/6-global-back-forward`, now that #72 has landed — the two share `handleNavigate`, and #72 made one place responsible for saying where the user is, which is what a forward control has to keep honest. (The argument for taking them in this order, against #73's stated one, is a comment on #73.) **Approach.** The stack is already global: `recordNavigation` pushes every navigation and `popstate` replays any entry, `goForward()` included — `back-navigation.spec.ts` has asserted forward since it was written. So the report's "back is tab-scoped" is about the *affordance*: the only way back is a detail view's own button, which disappears the moment you leave for another tab. What is missing is a persistent control, and a truthful disabled state for it. - A `<nav-history>` in the top bar, driven by `history.back()` / `history.forward()`, published from the shell the way the active view now is. - Depth is ours to track, since the History API exposes none. The existing `pushedEntries` counter cannot answer forward: `popstate` fires identically in both directions, so a counter that decrements on every pop cannot tell them apart. Each entry carries an index instead, and the shell keeps the current one and the high-water mark — back is available below the floor, forward below the mark. - The launch entry is still *replaced*, not pushed, so the app still exits on one back press from the root. Verifying at all three size bands, and checking the header still fits at 900×600 — #69 is recent enough that two more buttons in that bar need measuring rather than assuming.
logan closed this issue 2026-08-19 22:27:39 +00:00
gitea-actions bot removed the
Status
In Progress
label 2026-08-19 22:27:49 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yonlu/yellowjacket#6