A dismissible surface should answer the back gesture, and the shell should own that #200

Open
opened 2026-08-22 05:34:00 +00:00 by logan · 0 comments
Collaborator

Report

Several surfaces in the app are dismissible: the overlaid queue, every
wa-dialog, the bottom-sheet menus, and — since #63 — a list's selection
mode. Each has arrived at its own answer for "how do I get out of this",
and they do not agree:

  • the overlaid queue is a history entry (#55), so the platform back
    gesture closes it;
  • dialogs and menus take Escape, each attaching its own listener;
  • selection mode takes Escape (from selection-bar, so all four lists
    share one implementation) and has no answer for back at all.

On Android, back is the universal "get out of this", and every one of
Google's own list surfaces (Gmail, Files, Photos) exits selection mode on
it. Ours does not.

Findings

  • The obvious fix — selection-bar pushing a history entry — is the
    fault navStack was deleted for. The shell owns the stack (#6, #55)
    and index.ts is the only thing that calls pushState/history.back()
    precisely so that two stacks cannot come to disagree about what one
    press means. Four components each reaching for history is four
    stacks.
  • It is also wrong on its own terms: a mode is per-component and four
    lists could each push one, and a user who enters selection mode,
    navigates away and comes back has an entry for a mode that no longer
    exists.
  • #55 settled the shape for a place — the overlaid queue is one, a
    column is not. A mode is not a place either, which is why it did not
    simply inherit that answer.
  • queue-panel already demonstrates the middle ground: its Escape is
    attached only while the overlay is up, and is documented as a
    dismissal, not a shortcut
    , which is why it is not a panel-scoped
    shortcut binding. selection-bar now does the same. Neither can
    answer a back press.

Direction

One shell-owned register of dismissible surfaces: something opens,
declares how to close itself, and the shell's existing popstate
handler closes the topmost one before treating the press as a
navigation. That is one stack, one owner, and it retro-fits the queue
overlay's history entry, the dialogs and selection mode alike rather
than adding a fourth private answer.

Worth deciding as part of it: whether Escape should route through the
same register, so "dismissible" has one definition rather than two that
happen to agree.

Found while doing #63 phase 4, which is where the question was left open
in .planning/plans/active/019-android-touch-model.md.

**Report** Several surfaces in the app are *dismissible*: the overlaid queue, every `wa-dialog`, the bottom-sheet menus, and — since #63 — a list's selection mode. Each has arrived at its own answer for "how do I get out of this", and they do not agree: - the overlaid queue is a **history entry** (#55), so the platform back gesture closes it; - dialogs and menus take **Escape**, each attaching its own listener; - selection mode takes Escape (from `selection-bar`, so all four lists share one implementation) and has no answer for **back** at all. On Android, back is the universal "get out of this", and every one of Google's own list surfaces (Gmail, Files, Photos) exits selection mode on it. Ours does not. **Findings** - The obvious fix — `selection-bar` pushing a history entry — is the fault `navStack` was deleted for. The shell owns the stack (#6, #55) and `index.ts` is the only thing that calls `pushState`/`history.back()` precisely so that two stacks cannot come to disagree about what one press means. Four components each reaching for `history` is four stacks. - It is also wrong on its own terms: a mode is per-component and four lists could each push one, and a user who enters selection mode, navigates away and comes back has an entry for a mode that no longer exists. - #55 settled the shape for a *place* — the overlaid queue is one, a column is not. A mode is not a place either, which is why it did not simply inherit that answer. - `queue-panel` already demonstrates the middle ground: its Escape is attached only while the overlay is up, and is documented as *a dismissal, not a shortcut*, which is why it is not a panel-scoped shortcut binding. `selection-bar` now does the same. Neither can answer a back press. **Direction** One shell-owned register of dismissible surfaces: something opens, declares how to close itself, and the shell's existing `popstate` handler closes the topmost one before treating the press as a navigation. That is one stack, one owner, and it retro-fits the queue overlay's history entry, the dialogs and selection mode alike rather than adding a fourth private answer. Worth deciding as part of it: whether Escape should route through the same register, so "dismissible" has one definition rather than two that happen to agree. Found while doing #63 phase 4, which is where the question was left open in `.planning/plans/active/019-android-touch-model.md`.
logan added the Area/Shell-NavKind/EnhancementPlatform/Android
Priority
Medium
3
labels 2026-08-22 05:34:07 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yonlu/yellowjacket#200