113 lines
6.2 KiB
Markdown
113 lines
6.2 KiB
Markdown
# TASK 37 — "Back" as a first-class verb
|
|
|
|
**Status:** open, opened 2026-07-27. Prompted by @wiredwireless's `xdg_navigation`
|
|
sketch (Matrix, 2026-07-24) and Casey's ask to work out how it fits here.
|
|
**Size:** our half is small and worth doing now; the protocol half is upstream's
|
|
problem and should not block us.
|
|
|
|
## The outside idea, in one line
|
|
|
|
An app declares "my current view has somewhere to go back to"; the compositor
|
|
decides how the user expresses "back" (edge swipe, key, mouse button 8,
|
|
accessibility) and invokes it. Later: gesture *progress* with commit/cancel, so
|
|
the app can drag the outgoing page as Android's predictive back does.
|
|
|
|
## Verdict: the concept is sound. The framing needs work before it ships anywhere.
|
|
|
|
The idea is right and the pieces exist. Three things would decide whether it
|
|
survives contact with freedesktop, and they are worth getting right regardless
|
|
of whether we ever send it upstream.
|
|
|
|
**1. The killer objection is "just send `XF86Back`."** Apps already handle that
|
|
keysym and mouse button 8. A compositor can synthesise it today, with no
|
|
protocol at all. Any proposal that leads with the discrete `back()` call walks
|
|
straight into this and loses.
|
|
|
|
**2. The answer is `can_go_back` — the state channel, not the verb.** A
|
|
synthesised key is fire-and-forget: the compositor learns nothing about whether
|
|
anything happened. It therefore cannot decide what to do when the app has
|
|
nowhere to go — close the window? switch workspace? nothing? — so in practice
|
|
it does nothing, and "back" is unreliable, which is exactly why it never caught
|
|
on. Declared availability is the new capability, and it is what lets the
|
|
compositor own the *fallback* policy while the app owns only its own stack.
|
|
|
|
Android is the precedent, and it cuts in this proposal's favour: `onBackPressed()`
|
|
was the key-shaped model, and Android 13 replaced it with `OnBackInvokedCallback`
|
|
— a *registered* callback with declared availability — precisely because the
|
|
key model could not express delegation or predictive back. The platform being
|
|
cited already tried the cheap version and migrated off it.
|
|
|
|
**3. Lead with predictive back, not with back.** Progress + commit + cancel
|
|
cannot ride a key event under any encoding. That makes it a mechanism argument
|
|
("here is a thing input events structurally cannot do") rather than a policy
|
|
argument ("here is a nicer way to do a thing you can already do"). Discrete
|
|
back then falls out as the degenerate case: progress 0 → 1 in one step.
|
|
Mechanism arguments are the register Wayland review accepts; policy arguments
|
|
are the ones that summon the metaphysics thread.
|
|
|
|
### Two design questions to answer before posting, not after
|
|
|
|
- **What happens at the root**, when `can_go_back` is false. This is where the
|
|
"what does back *mean*" fight actually lives, and the design already answers
|
|
it — the compositor knows, so the compositor decides (close / switch / do
|
|
nothing), and no app ever needs an opinion. Saying so up front removes a
|
|
whole class of objection.
|
|
- **Scope.** One verb, one state, one progress channel. No `forward`, no `up`,
|
|
no `home`. Each extra verb multiplies the semantic argument and none of them
|
|
have back's near-universal agreement. Also: this belongs on `xdg_toplevel`
|
|
(precedent: xdg-dialog, xdg-toplevel-icon), not on bare `wl_surface`, and
|
|
dialogs/popups need a stated answer.
|
|
|
|
## How it fits here — we have already built the compositor half, twice
|
|
|
|
This is not an analogy; the shapes line up exactly.
|
|
|
|
- **`services/Gestures.qml` is the routing table in the sketch.** It already
|
|
maps gesture → named semantic action (`squeezeAction`, `edgeAction`, a
|
|
`bind(gesture, action)` IPC) rather than gesture → hardcoded behaviour, which
|
|
`INTERFACE-ARCHITECTURE` §4 requires of every input. Adding `back` as an
|
|
action is one table entry.
|
|
- **`can_go_back` is the dial's `enabled`, and its refusal reason is the dial's
|
|
`reason`.** TASK-30's verb tables and TASK-31's radial dial already carry
|
|
"this verb exists but is not available right now, and here is why, in the
|
|
words the refusal used". A back verb is that shape with a one-bit state.
|
|
Whatever we build for back should feed the same table, or we will have three
|
|
places that enumerate capabilities instead of one — the exact drift TASK-31
|
|
was written to prevent.
|
|
|
|
**What we do not have is the app half**, and that is the whole point of doing
|
|
this ourselves: our first-party apps are ours. culver already pushes a stack on
|
|
narrow layouts (contacts, conversation) and pops it; souveraine-settings is a
|
|
page tree; the player has views. Every one of them already knows its own
|
|
answer to "where does back go" and has no way to say so.
|
|
|
|
## The move
|
|
|
|
Implement the *semantics* over our own IPC now, shaped like the protocol so a
|
|
later swap is a transport change and not a redesign:
|
|
|
|
1. A `navigation` service in the shell holding, per focused app: `canGoBack`,
|
|
and `back()` / `backProgress(f)` / `backCommit()` / `backCancel()`.
|
|
2. First-party apps report their stack depth into it. culver first — it is the
|
|
app where the missing gesture is felt daily.
|
|
3. `back` becomes a gesture-table action like any other (TASK-13's squeeze,
|
|
the pill, an edge swipe), never a hardcoded binding.
|
|
4. Predictive back last, and only after the discrete version feels right.
|
|
|
|
**Third-party apps** (Firefox, GTK apps) cannot participate and will not until
|
|
something lands upstream. They get the legacy `XF86Back` key. This is a
|
|
*declared two-tier policy*, not a silent fallback: the compositor knows exactly
|
|
which tier a surface is in, and in tier 2 it does not claim to know whether
|
|
anything happened. It must never present tier-2 behaviour as if it were tier 1.
|
|
|
|
The by-product matters as much as the feature: doing this first means that if
|
|
the idea does go to freedesktop, it arrives as a working implementation with a
|
|
device behind it, not as a proposal. That is the difference between being
|
|
argued with and being eaten.
|
|
|
|
## Connects to
|
|
|
|
TASK-30 (verb tables — back is a verb and belongs in them), TASK-31 (the dial —
|
|
same enabled/reason shape, same routing table), TASK-13 (squeeze — another
|
|
gesture wanting a semantic destination), `INTERFACE-ARCHITECTURE.md` §4,
|
|
TASK-32 (pill and OSK as apps — the pill is one place a back gesture lands).
|