8 KiB
TASK 51 — viewtop from "it is the session" to "it is the daily driver"
Status: open. Created 2026-08-02, after the day viewtop became the session
and then had to survive being used. Repo: souveraine-viewtop unless noted.
This is the punch list that stands between runs and stable. Everything here was found by using the phone, not by reading the code. Ordered by what blocks daily use.
1. Windows stack, and nothing tells them apart
Every toplevel is configured to the same rectangle at the same origin, so two
apps land exactly on top of each other. place and pose now work
(276bb3e), so the mechanism to arrange them exists and nothing uses it:
there is no default placement policy, no border or focus indication, and no
occlusion — a fully covered window is still composited every frame, which on a
phone is battery spent drawing what nobody can see.
Wanted, and named by Casey: multiple apps at once, boundaries, expandable
windows. The prior art is right there — the reference compositor's
window_placement_store.rs, window_grab.rs (interactive move/resize) and
local_windows.rs. Read those before inventing a layout model.
Landed 2026-08-03: placement.rs (tile until tiling stops helping, capacity
derived from minimums rather than written down), a focus border — server-side
decoration was already promised by new_decoration and never drawn — and a
three-finger drag that carries a window, floating it via place().
The window-action overlay, and why it is blocked on the wire
2026-08-05: the overlay itself is no longer blocked and now ships (TASK-55).
It reaches windows through the control socket's scene verbs rather than the
per-window-texture wire below, which is enough for close/kill/place and is what
"no way to close an app" needed. What is still blocked on this section is the
scaled/sliding visuals — cards, flick-to-dismiss, live miniatures. Note the
compositor already has the harder half: pose scales a real window and
unpose maps input back through the inverse (both tested), so a shrunken app
stays touchable. Nothing drives pose yet, which is precisely why Casey has
never seen it.
Casey, 2026-08-03: three fingers should raise an overlay with options — move, resize, close — with the scaled/sliding visuals the reference shell has.
Read how that shell actually does it before building ours, because the answer is
structural. Its compositor publishes, per window, a texture_id and full
geometry (surface_*, geometry_*, content_*, transform, scale_120) in
one Window table on the wire. The shell then draws that texture wherever and
at whatever scale it likes — which is the entire trick behind the overview's
scaled cards, the flick-to-dismiss, and every sliding transition. The compositor
composites the shell's scene; the shell decides where window content goes.
Ours cannot do that yet, and the gap is specific:
- The registry publishes a surface's size and never its position — the
comment on
place()says so deliberately ("the space owns where a window sits"). A shell cannot draw a frame, a handle or a card around something whose location it is not told. - There is no way for the shell to draw window content at all. viewtop
serves
ext-image-copy-capturewith an output capture source; the reference shell's equivalent is per-toplevel.ext-image-capture-source-v1has a foreign-toplevel variant, and quickshell already speaks this protocol family.
So the overlay is two pieces of plumbing, in this order: publish window position
alongside size, then a per-toplevel capture source. Doing the overlay in the
compositor instead would be quicker and wrong — it is UI, layers.rs and the
lock surface both argue the compositor does not author it, and it would be the
one piece of chrome the agent could not restyle.
Two things to keep straight while building it:
- a placement policy belongs in its own module, testable without a GPU, the
way
layers.rsandinput.rsare. The compositor asks it; it does not decide innew_toplevel. - whatever it decides,
placemust still override it. Geometry is the agent's (§13's 60) and a policy that cannot be overridden is a window manager again.
2. Screen capture is written and unmerged, so nothing visual can be verified
ext-image-copy-capture-v1 + ext-output-image-capture-source-manager-v1 are
done and sitting in a whole viewtop tree on archdev at ~/vt-capture:
ImageCopyCaptureHandler around wayland.rs:1816, helpers capture_peer /
copy_output_into / render_capture around 676-850. gate.rs's
Act::Capture half is already merged into the live tree, so the port is the
handler and its helpers only.
This is not cosmetic. Without screenshots every UI change ships unverified — which is exactly how an unverified change shipped on 2026-08-02. It renders the scene a second time off-screen rather than reading the scanout buffer, and that reason is worth preserving: handing a client a mapping of the swapchain also hands it whatever the next frame's allocation reuses.
3. Volume buttons — DONE 2026-08-02 (266af98)
Action::Volume is pushed on the button down edge (device_state.rs) and
executed through sessiond's executor table (server.rs), with tests. It went
the way §12 required — an Action, not a shell-out from the compositor — and
acts on the press rather than on a recognised gesture, because the multi-tap
window would otherwise put 300 ms between the press and the sound changing.
Still unbound: the gesture level. The recogniser resolves volume taps and holds from the same edges, so hold-to-ramp or double-tap costs a binding rather than a mechanism. That is where the Activator-shaped binding table lands.
4. furniture_at() returns None
So Route::Shell is unreachable and the pill cannot be addressed as furniture.
Waiting on the engine publishing a region layout; inventing rectangles the shell
has not declared would be a second, drifting idea of where the pill is.
5. Named holes that are one decision each
foreign_topleveladmits every client, and window titles cross while the session is locked — the registry would never allow that for a surface it owns. A disclosure gate on the window list is owed, and belongs with the one owed atdata_control,virtual_keyboardand the capture boundary.- The control socket admits every caller.
SO_PEERCREDis available there and unused, exactly asmay_take_lockis still a uid check. TASK-41 lands at all of them or none. Today's ceiling is what any uid-1000 process could already do by runningblueline-screen-toggle, so it is not a new hole — butscenewidened what is reachable through it. - Navigation intents refuse.
navigation.rsholds TASK-37's policy, both tiers and the predictive drag, and nothing calls it. - Xwayland — no X11 apps.
- viewtop is not packaged (
/usr/local/bin, hand-copied) — TASK-25's scope, andsouveraine/CLAUDE.mdnames that pattern as the one that silently never updates.
6. Two smithay patches, and they are not the same kind
Keep the distinction visible or the fork becomes a place protocol violations go to be tolerated:
8772275is upstream's bug — the layer-shell pre-commit hook outliving the role object, which kills any client on normal teardown. Worth sending upstream.403a3d0is a concession.ext-session-lock-v1says committing before acking the first configure is a protocol error and Qt does it anyway. The real fix belongs in Qt; delete this when it lands.
Acceptance
The phone is used for a day without ssh: apps can be switched between and told apart, the screen sleeps and wakes on its own controls, volume works, and a screenshot can be taken of any of it.
Connects to
TASK-43 (viewtop, the parent), TASK-50 (her hand on the glass), TASK-25
(packaging), TASK-41 (attestation at every one of these boundaries), TASK-39
(audio as state), TASK-37 (navigation), LOCK-DPMS-LESSONS.md §1 (the ordering
this must never regress).