Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/docs/tasks/60-the-multitasking-view.md
Fimeg 528d290ba6 59/60 and the bug dump: what landed the day her face went on the glass
TASK-59 becomes a record of five bugs that each presented as something else —
the missing #live_talk element, --app-id doing nothing, the GL context claimed
before the canvas was sized, posture wired to a function that does not exist,
and a feed that dropped its own reader for being slow. Plus the furniture role,
input regions, gaze, and what the rig costs next.

TASK-60: the cards were zero pixels wide, and the capture-hang theory is
retracted — the session had been left locked by a test.

DUMP: 3/4/5 closed, 7 retracted with the trap that produced it, 8/9/10 new.
2026-08-07 02:29:37 -04:00

10 KiB

TASK 60 — multitasking is a place, not a transition

Status: 2026-08-07 — the cards draw. Zone cards with live previews, a close control and a zone label, verified on hardware. What remains is the swipe into it, which is still awkward.

2026-08-07 — the cards were zero pixels wide

Reported as "it goes to a blur screen but nothing else", and every signal read healthy: zones 2, windows 1, subscribed true, loader active, item present, opacity 1, progress 1. The blur was the backdrop; the cards were there and 540 px shorter than nothingitem.w: 0, h: 842.

ZoneOverview took its width from the Column it sits in, and a Column is as wide as its widest child. In the drawer the search widget supplies that width; mission control is the cards alone and has no search, so the only child left was the loader — whose width came from the column, whose width came from the loader. The cycle resolves to zero. Bound to panelWindow.width instead, which is what this surface covers anyway (its mask is missionBackdrop, not the drawer's sheet).

Two things that made it findable, and are worth keeping:

  • overview missionControl and overview state verbs. The one surface reported broken was the only one nothing but a pill swipe could raise, so it could not be inspected or tested. state reports what the cards are drawn from, and reading item.w: 0 beside column.w: 0 is what located this in one step after a long time spent on wrong theories.
  • A false lead worth recording: the compositor was refusing every capture with "no client captures the screen behind a lock screen", and grim hung the same way the cards did. That is correct behaviour behind a lock — the session had been left locked by a test. Capture was never the problem. Check locked in state before concluding anything from a capture failure.

Cards are inset (26 px sides, 12 top) so neighbouring zones show through — full-bleed reads as "you are looking at that app" rather than "here are the places you can go", and leaves nothing on screen saying there ARE neighbours.

Still open: the swipe in

Casey, 2026-08-07: "the visual style from swiping up into this mode... we lost the consistency... the swipe to this swap is awkward."

The machinery exists and is where to look. ZoneOverview.progress follows GlobalStates.zonePullProgress while the rail is pulling and the state flag takes over on commit — this task's own acceptance is "no frame where a window is scaled by one and laid out by the other." Two concrete suspects:

  1. Cards start at scale 0.6 + 0.4 * share, chosen to match the scale the rail's pose has reached at the multitasking detent. 0.6 is a written-down constant; if the rail's actual pose there is not 0.6, the card pops to a different size at handoff.
  2. The inset above makes the card smaller than the full-bleed window it replaces at the handoff moment, which may have made that seam worse. Either the rail's pose should end at the inset card's rect, or the inset should animate in after the handoff rather than being there at share = 0.

Needs watching in motion. Screenshots cannot see it.

Original — design, 2026-08-05

Written after building the wrong thing and being told so. Casey, on device:

"Still major issues. We might need to take these concepts we've built back to the workshop. The scaling doesn't follow the selected app. meaning app#2 opened even if focused is not scalling down. Second, all we have is scaling; it's supposed to be showing me all sorta backgrounded zones; including split zones. You've seen macOS. That whole multitasking view is a function of it's own."

Repo: souveraine (shell surfaces), souveraine-viewtop (capture + zone facts).

What was built, and why it is not the thing

The rail's short swipe scales the live windows on the active zone from 1.0 to 0.6 as the thumb climbs (ViewtopControl.poseActiveZone), then opens GlobalStates.missionControlOpen. That is a transition — the animation of leaving an app — and it was mistaken for the destination.

Two failures follow from that mistake, and both were seen on hardware:

  1. It scales the wrong windows. poseActiveZone poses everything whose workspace matches ViewtopControl.activeZone, and that list comes from a 2-second poll of {"op":"workspaces"}. Open a second app and the poll has not caught up, so the newly focused window is not in the set being scaled. There is no "focused window" fact in the shell at all — the compositor knows it (Surface::focused) and does not report it in the workspaces reply.
  2. Scaling one zone is not a view of all of them. Even done perfectly, it shows the zone you are already on, smaller. It cannot show what is backgrounded, which is the entire question multitasking answers.

What exists

Piece Where State
WindowOverview.qml modules/souveraine/navigation/ One column of equal cards, one per window, live ScreencopyView texture, flick-up to dismiss
Zone facts {"op":"workspaces"} count, active, offset (the float the strip is scrolled to), settled, and per window {id, workspace, at, size}
Per-window capture viewtop, since 2026-08-03 A card can show a window rather than the screen covering it
pose / unpose viewtop Now genuinely scales content (fixed 2026-08-05 — it had only ever moved it)

WindowOverview.qml's own header states the gap: "viewtop has them [workspaces], and they are not Hyprland's: a continuous strip that grows when a window needs a room and shrinks when the last one leaves… That is a thing this overview could draw and does not yet." This task is that sentence.

What macOS actually does, since it is the named reference

Mission Control is two levels in one surface: a row of spaces across the top, and the windows of the current space spread below. Neither alone is the feature — the row answers "where else am I?" and the spread answers "what is here?". Dragging a window onto a space in the row moves it there, which is the same gesture doing placement.

Ours differs in one load-bearing way and it must not be papered over: macOS spaces are a fixed set the user creates; viewtop's zones are minted and destroyed by need (workspace.rs: "a workspace exists because something is on it"). A row of zones is therefore a row that grows and shrinks under you, and home is the one that is always there and always empty.

The questions

Q1. What is a card — a window, or a zone?

Today it is a window. macOS says both, at two levels. A phone has room for one level at a time. Options: zones-as-cards with their windows composited inside (split zones then read correctly, which is Casey's explicit ask); windows-as- cards grouped by zone; or a two-level surface where the zone row is a strip and tapping one spreads its windows. Q's real content: a split zone has two windows and must look like one thing you can switch to, not two.

Q2. Live textures, or posed real windows?

Two mechanisms now exist and they are not the same:

  • ScreencopyView — a live picture. Cheap to lay out anywhere, but it is not the window; input goes to the card.
  • pose — the real window, scaled, still touchable through unpose's inverse mapping.

The reference compositor composes client windows as external textures directly into the shell's scene, which is the third answer and the one the in-process Flutter shell would make natural (SHELL-BOUNDARY.md). Choosing here decides whether the overview is a gallery of pictures or the actual desktop, zoomed out — and whether the transition can be continuous into it.

Q3. Where does the focused window come from?

The shell has no such fact. Surface::focused exists in the compositor's registry and is not in the workspaces reply. Any answer to Q1 needs it, and it should be reported rather than inferred — inferring "the last window I saw open" is the kind of shadow copy §4 spends its whole argument against.

Q4. Is the poll acceptable?

ViewtopControl polls every 2 s. That is already the proximate cause of failure

  1. A view built on it will be wrong every time something changes faster than two seconds — which is every gesture. Either the compositor pushes zone/window changes, or this surface asks synchronously when it opens and stops guessing in between. Design the push channel; it is owed anyway.

Q5. What does the transition become?

If the destination is a real view, the scale-on-drag is either (a) the first frames of it, continuous into the cards, or (b) noise that should be deleted. macOS and Phosh both do (a) — Phosh's home.c is a drag surface travelling between two states, not a button. (a) is better and harder: it means the transition and the view share one progress value, which is the reference overview's whole trick (WindowOverview.qml already cites it: "one progress driving everything").

Q6. What can you do to a card?

Switch to it, certainly. Close it — the close/kill verbs exist. Move a window between zones — {"op":"workspace","to":N,"surface":id} exists too, so drag-a-card-onto-a-zone is reachable today. Decide before building, because it changes whether cards need to be drag targets.

Acceptance (measured, on the phone)

  • Every zone with something on it is represented, and a split zone reads as one destination containing two windows — not as two unrelated cards.
  • The view reflects state at the moment it opened, not up to two seconds stale. Open an app, immediately open multitasking: the new app is there.
  • Tapping a card goes to that zone. Home is reachable from it.
  • Whatever drives the transition and whatever draws the view agree — no frame where a window is scaled by one and laid out by the other.
  • The scale-on-drag either continues into the view or is gone. Not both.

Connects to

TASK-14 (the overview pane and its app grid; "a grid of workspace thumbnails is dead weight on a phone" is the constraint that made cards windows in the first place), TASK-55 (the sheet, and ViewtopControl as the one path to scene verbs), TASK-43 (the compositor's surface model and per-window capture), TASK-52 (atmosphere — pose is a gravity well and this is its first real user), VIEWTOP-AND-DENIAL.md (external textures into the shell's scene; Q2's third answer), SHELL-BOUNDARY.md (the in-process shell that would make it natural).