Session and device authority now have living owners; belief and felt state match the Rust that actually runs.\n\nThe Panel audit keeps three real commands, names the image seam, and refuses to call inherited ii stubs parity.
70 lines
6.4 KiB
Markdown
70 lines
6.4 KiB
Markdown
# Motion
|
|
|
|
How the shell moves, and who is allowed to move a window. The code is `surfaces/quickshell/services/ZoneTransition.qml`, `modules/souveraine/navigation/`, and `crates/compositor/src/transition.rs` in viewtop.
|
|
|
|
## One clock, two strategies
|
|
|
|
The rail measures a thumb and reports one number: how far it has climbed. It derives nothing. `ZoneTransition.pullTo(travel, detent)` takes it, and `clock` is `travel / detent` — 0 at rest, 1 at the multitasking detent, and it keeps counting past that, because the climb toward home is more of the same motion and not a second gesture.
|
|
|
|
Everything visible is a curve over that one number, and every curve lives in that one file.
|
|
|
|
`shift` is what the compositor carries the window on. `presence` is how present the destination is. They are genuinely different: the window keeps travelling as the climb continues toward home, while the destination has to recede, or a release would arrive somewhere it was never going to take you. Two curves is correct. Two *places* is the fault — a scale on the rail and a scale on the card, tuned by hand to agree, which is how the original was built and what TASK-60 was written to remove.
|
|
|
|
## The card rect is one number, not two that agree
|
|
|
|
A card is the usable zone drawn small. One scale factor, origin included, because `Viewtop::carried` takes its scale from the width alone and applies it uniformly — a destination of any other aspect would letterbox the live window inside the card it is supposed to *be*.
|
|
|
|
`ZoneTransition` owns the rect. `ZoneOverview` lays its card out at `cardInset*`; the carry names `cardX`/`cardY` as the destination. They are the same rectangle in two coordinate spaces, not two rectangles kept in agreement.
|
|
|
|
`contentTop` comes off a window's `at.y` before scaling. The card is a picture of the usable zone, not of the whole output, and a window tiled below the bar's reservation starts at the card's top edge. Miss the subtraction in one of the two places and the carried window lands a bar's height off its own picture.
|
|
|
|
## Releasing is arriving
|
|
|
|
Every swipe resolves to exactly one end target: `home`, `overview`, `last_zone`, or a named zone. The transform is released by `commit` or `cancel` inside the compositor and there is no third way for a carry to end, so a window cannot outlive the gesture that carried it.
|
|
|
|
That is structural, not careful. The old shape was a transform one surface applied and a *different* surface had to remember to undo — so tapping a card, which never touches the rail, left a window shrunk with no way back.
|
|
|
|
`last_zone` is a destination with a name, not an abandon branch, for the same reason.
|
|
|
|
## The settle is a spring, not a duration
|
|
|
|
A lift at 60% of the climb used to commit *at* 60%: the window jumped from where the thumb left it to its final state. Two motions with a cut between them.
|
|
|
|
A duration and an easing curve cannot carry release velocity, so a flick and a drift settled identically. The settle is a critically damped spring integrated on `FrameAnimation` — the render clock, so the carry advances once per painted frame — seeded with the velocity the rail measures over the last 100 ms of the gesture. Critically damped and never underdamped: a navigation surface that rings reads as a toy.
|
|
|
|
Hard-capped at 600 ms. The commit is what releases the carry, so a settle that never converged has to arrive anyway.
|
|
|
|
Known and not yet fixed: on the `last_zone` path `travel` clamps at 0 while the exit test still waits for `|velocity| < 0.02`, so the commit can lag the visual arrival by up to that cap on the commonest gesture there is.
|
|
|
|
## The transport is fast and lossy, and only the second one matters
|
|
|
|
Measured on blueline, 2026-08-16, 200 samples, full connect → write → event-loop hop → reply → close: `workspaces` 0.47 ms median, 1.04 ms worst. Under 3% of a frame. A quantiser protecting against that cost is protecting against nothing, and `_lastShift`'s 0.01 bought a visible staircase — one step per two logical pixels across 275 px of travel. It is 0.002 now.
|
|
|
|
What the transport *is* is lossy: 52 failures and 113 `PeerClosedError`s in one 90-minute boot, each of which used to execute `_queue = []`. A lost `overview_commit` leaves the compositor carrying windows the shell believes it released and nothing in the system will ever put them back. `overview_commit`, `overview_cancel` and `workspace` are idempotent and are redelivered once. `overview_progress` is a level and still drops.
|
|
|
|
Rate is bounded by the render clock, not by a quantiser. `WindowResize` pumps its `place` the same way.
|
|
|
|
## The curve bank
|
|
|
|
`Appearance.animationCurves` is Material 3 Expressive and it has been in the tree since the `ii` days. Every spatial curve in it overshoots — `expressiveFastSpatial` peaks at 1.67, `expressiveDefaultSpatial` at 1.21 — and there is a `clickBounce` beside them.
|
|
|
|
Audited 2026-08-16: twelve souveraine surfaces, 31 hand-typed animations, three calls into that bank from two files. Every navigation surface was animating on a flat linear ramp with a hand-typed duration. That was the whole of "the reference compositors have more whoosh."
|
|
|
|
Use the bank. A hand-typed `NumberAnimation { duration: N }` in a new surface is a defect unless it says in a comment why the bank is wrong for it.
|
|
|
|
## The rule under all of it
|
|
|
|
The shell does not transform a window. It names a destination and the compositor carries it there.
|
|
|
|
Any shell-side scale over a window's geometry is a second writer, and a transform one surface applies and another must remember to undo always has a path that forgets. Where a card *must* have motion of its own — depth, the entry rise — it is gated on `ZoneTransition.inFlight` so that during a carry every card sits at its true rect and the motion eases in on the commit frame.
|
|
|
|
## Not yet true
|
|
|
|
`transition.rs:79` clamps `shift_to` to `0..=1`, so the resistance past the detent is written, shipped and inert. Raising it to `maxOvershoot` (1.22) and updating `shift_is_clamped` is a compositor change. Safe to ship the shell ahead of it: an old compositor clamps and the gesture behaves exactly as it did.
|
|
|
|
The control socket also serves one request per connection. The QML client is
|
|
correct to make commit/cancel/workspace idempotent and retry once today. A
|
|
persistent connection needs the compositor handler to loop to EOF with one
|
|
`BufReader`; rebuilding the reader for each request can discard bytes it read
|
|
past the first newline. Persistence changes transport cost and failure shape,
|
|
not the authority or acknowledgement rules above.
|