Watch
1
0
Fork
You've already forked SouveraineOS
0

TASK-60: one owner for the transition

Two writers own one geometry — the rail's pose and ZoneOverview's scale,
running opposite curves off the same progress. Three attempts to make the
numbers agree failed in a session. The strand proves it: the repro path
is card taps, which never reach the rail's clear-the-pose code.

Plan follows quickstep, which is on this machine: one shift 0..1, the
live window transformed onto the real card rect, end targets as an enum
with LAST_TASK a destination rather than an abandon branch, drag length
derived from that rect.

The transition is a verb because she gives the tour, under the origin
rules input.rs already enforces: agent input is not evidence and the
lock surface takes fingers only.
This commit is contained in:
Fimeg 2026-08-07 09:18:56 -04:00
commit ee8697cd8f

View file

@ -1,8 +1,110 @@
# 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.
**Status: 2026-08-07 (later) — the cards draw, and the transition is being
rebuilt around one owner.** Patching the two geometries into agreement failed
three times in one session; the plan below replaces them with one. Read
"One owner" before touching any of it.
## One owner — the rebuild, 2026-08-07
### The fault, stated once
**Two writers own one geometry.** The rail scales the real windows through
`pose`; `ZoneOverview` scales its cards through `scale`. Both read
`zonePullProgress`, and a session was spent trying to make the two numbers
agree — the card ran `0.6 → 1.0` while the pose ran `1.0 → 0.6`, so at the
handoff the window was at 0.6 of the glass and the card arrived near full
bleed.
That is `DEVICE-STATE-MACHINE.md` §1's blind actors in the render path, and
TASK-52 names the same rule for exactly this reason: *one attention model, one
clock, effects as strategies over it*. Every symptom is the one fault — the
size pop, the doubled frame (viewtop's 3 px `#9EC7FF` under the card's
`radius: 18`), and the strand.
**The strand is the proof.** Casey, 2026-08-07: open btop, swipe to
multitasking, tap Home, reopen multitasking, tap btop — btop comes back as a
small card in the middle of the screen. Every clear-the-pose path built that
day hangs off the rail's drag edge (`endPull``clearPose`). *Those taps are
`ZoneOverview`'s and never touch the rail*, so nothing ever runs. A transform
that one surface applies and a different surface must remember to undo will
always have a path that forgets. Note also that `pose` is TASK-52 **#10,
gravity wells** — an atmosphere primitive that navigation borrowed.
### What the prior art does — quickstep, on this machine
`~/Downloads/lineage-trebuchet/quickstep/src/com/android/quickstep/`.
- **One float.** `SwipeUpAnimationLogic.mCurrentShift` ∈ [0,1] — its own comment:
0 = preview snapshot completely visible, 1 = *"preview snapshot completely
aligned with the recents view"*. `TaskViewSimulator` transforms the **live
window onto the real card rect**. The card never has to be made to match the
window, because the window is placed on the card. There is no second geometry.
- **The drag length is derived from the destination.**
`getSwipeUpDestinationAndLength(dp, ctx, TEMP_RECT, …)` returns the rect and
the length together, and `mDragLengthFactor = dp.heightPx / mTransitionDragLength`.
Our `missionAt` is 18% of the panel, a number with no relationship to where
the card actually is.
- **End targets are an enum**, not a set of booleans: `HOME, RECENTS, NEW_TASK,
LAST_TASK, ALL_APPS` (`GestureState.java:77`). Every gesture resolves to
exactly one, and **`LAST_TASK` is a first-class destination** — "went back to
the app" is a real endpoint that animates, not an abandon branch.
- **The controller owns the transform and hands it back at the endpoint.**
Nothing hopefully-undoes anything, which is structurally why Android cannot
strand a window and we can.
- **Dwell is first-class.** `isFling = mGestureStarted && !mIsMotionPaused &&
|endVelocity| > threshold`, and in gestural mode `mIsMotionPaused → RECENTS`
(`AbsSwipeUpHandler:1121,1322`). A pause is never a fling.
`MIN_PROGRESS_FOR_OVERVIEW = 0.7`.
Sailfish/Glacier (`~/phone-qml-research/glacier-home`) is the QML-side reference
for the same shape and is worth reading before the shell half.
### The plan
1. **One owner of the geometry.** The compositor owns the transition: one verb
carrying `shift` 0→1, where 1 means the window sits exactly on its card rect.
The destination rect is named once at gesture start, not re-derived per
frame. The shell stops calling `poseActiveZone` for navigation and stops
scaling cards; `ZoneOverview` draws chrome — label, close control,
neighbours — and never a competing transform.
2. **End targets as an enum.** One end-target state replaces
`missionControlOpen` / `overviewOpen` / `zonePullProgress`. Entering and
leaving become the same machinery whether the gesture came from the pill or
from a card tap — which is precisely what the strand repro broke. `LastTask`
is a destination, so the transform is released by *arriving*, not by
remembering to clear.
3. **Detents from the destination rect**, not 18%. Keep the dwell; make
"paused is never a fling" explicit rather than emergent.
4. **One frame.** A window inside the transition is furniture in the overview's
composition, so `focus_border` suppresses itself while a window is owned by
the transition and the card carries the chrome.
### It has to be a verb, because she gives the tour
Casey, 2026-08-07: there is a point where an agent is asked for a tour or a demo
of the phone, *"and that does mean even these little gesture steps will be
possible."* So the transition is a **verb with a caller**, not a side effect of a
touch handler — `shift` and the end target are both reachable from the verb
table, or a tour is impossible and TASK-30's third capability enumeration grows
a fourth.
The principles that already govern her hand govern this too, and they are built
(`input.rs`):
- `Origin::{Physical, Agent}`, carried from the source, never a caller-set field.
- `AGENT_SLOT_BASE = 1 << 16`, so a synthesized contact cannot land on a slot a
thumb owns.
- `Origin::is_evidence()`**an agent-driven tour must not be evidence.** A demo
that raises `observed_confidence` or feeds the idle budget is the machine
believing a human is present because she moved a window.
- Step-up stays human-only, and already is: `input.rs:432` routes
`Origin::Physical => Route::LockSurface` and `Origin::Agent =>
Route::Withheld("the lock surface takes fingers, not the agent")`. The
navigation verb must not become a way around that.
Connects to TASK-50 (the same origin machinery, and the inverse mapping `pose`
owes the hit test) and doctrine §13 (the 60 is hers; the credential is the 40).
## 2026-08-07 — the cards were zero pixels wide
@ -185,8 +287,16 @@ changes whether cards need to be drag targets.
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.
where a window is scaled by one and laid out by the other. Met by there being
one geometry, not by two that were tuned to match.
- The scale-on-drag either continues into the view or is gone. Not both.
- **The strand repro passes.** btop → multitasking → Home → multitasking → tap
btop, and btop is full size. No path leaves a window transformed, including
the ones that never touch the rail.
- **The gesture is reachable as a verb.** An agent can drive `shift` and pick an
end target well enough to demo multitasking, and the run leaves
`Origin::Agent` throughout: no evidence, no idle-budget credit, no route to
the lock surface.
## Connects to