182 lines
9.5 KiB
Markdown
182 lines
9.5 KiB
Markdown
# Phone shell — interface architecture
|
|
|
|
The phone has four primary interfaces the user actually touches: the **pill**,
|
|
the **dock**, the **keyboard**, and **touch/gesture**. This doc names what each
|
|
one is *meant to be*, what it is *today*, and the single seam where a hardcode
|
|
should become a small framework so the surface can grow without rewrites.
|
|
|
|
Principle: **clean beats feature-rich.** Each surface has one job and one place
|
|
its behaviour is tuned. A new feature should be a new entry in an existing
|
|
table, not a new hardcoded branch. Where a value is tuned in more than one
|
|
place today, that is the bug — not the value.
|
|
|
|
Truth hierarchy: this doc describes intent; `HOW-IT-WORKS.md` describes the
|
|
current wiring; the code is the reality. When they disagree, fix the code or
|
|
fix the doc in the same change — never leave a third story.
|
|
|
|
---
|
|
|
|
## 1. Pill — the always-on gesture bar
|
|
|
|
**Meant to be:** the one surface that is *always* reachable, on any app, in any
|
|
state. A thin, dumb input strip at the bottom edge that recognises a small
|
|
fixed vocabulary of gestures and forwards them — it owns no app state and makes
|
|
no decisions beyond "which gesture was this."
|
|
|
|
**Today (`pill/shell.qml`):** `WlrLayer.Overlay`, `ExclusionMode.Ignore`,
|
|
`margins.bottom:0`. Gesture arbitration is one MouseArea (double-tap, swipe up,
|
|
swipe down) → `dock` IPC. This part is clean and well-reasoned.
|
|
|
|
**The seam / the bug:** the pill's z-order guarantee is *documented but not
|
|
enforced*. `pill/shell.qml:23-27` sets namespace `quickshell:pill` and points
|
|
at "the `order` layerrule in hyprland.lua" — **that layerrule does not exist**
|
|
(hyprland.lua only has a commented-out generic `^my-overlay$` example). So
|
|
after an `ii` restart the pill can be buried under the dock and stop taking
|
|
touch — the exact failure the comment says is solved. Fix: add the real
|
|
`order` layerrule for `quickshell:pill`, or drop the promise from the code and
|
|
docs. Do not add more pill gestures until stacking is enforced — an always-on
|
|
surface that silently dies is worse than a plain one.
|
|
|
|
**Growth rule:** new pill gestures are new cases in the one MouseArea, each
|
|
forwarding to a *named* IPC target — never doing app logic inline.
|
|
|
|
---
|
|
|
|
## 2. Dock — the app switcher and stack surface
|
|
|
|
**Meant to be:** the surface that shows and switches running/pinned apps and
|
|
lets the user group them into stacks. It owns app-presentation state; it does
|
|
not own gesture recognition (the pill and hyprgrass feed it via IPC).
|
|
|
|
**Today:** interaction layer (`Dock.qml`, `DockAppButton.qml`, `DockStack.qml`)
|
|
is strong — drag-to-combine, fan-out arc, visibility state machine
|
|
(`computeDockState`). The weak layer is **state/config**:
|
|
|
|
- Stacks are a hand-parsed string `"stackId|appId,appId"` (`TaskbarApps.qml:25`).
|
|
A `|` or `,` in a name corrupts it silently; parsed/encoded in ~6 places.
|
|
→ move to **one JSON blob per entry** (array-of-strings the adapter tolerates,
|
|
but each string is JSON): kills the delimiter-corruption class, keeps the
|
|
adapter happy. This is the agreed direction.
|
|
- Stack ids are count-based (`nextStackName` = `"Stack " + (count+1)`,
|
|
`:87`) → collide after a delete, and `id` is the lookup key. Two stacks can
|
|
share an id and cross-contaminate members. → stable uid, display name
|
|
separate from id.
|
|
- Combining into a new stack suppresses the target's standalone pin at *render*
|
|
time (`apps` getter) but never removes it from `pinnedApps` in config
|
|
(`:99-108`) → the pin lingers; unstack works by luck. → remove-on-combine so
|
|
config matches what's shown.
|
|
- Tap-cycle `lastFocused` index (`DockAppButton :196`) is never reconciled when
|
|
windows open/close and is also written by hover (`:180`) → tapping can focus
|
|
the wrong window. → clamp/reconcile against the live toplevel model.
|
|
- Magic numbers live in the components: `pillStripHeight 32` (`Dock.qml:161`),
|
|
dwell `500` (twice: `DockAppButton:133`, `DockStack:116`), double-tap `350`,
|
|
arc geometry (`DockStack:33-36`). → lift into `Config.options.dock` next to
|
|
the `height`/`monochromeIcons`/`hoverRegionHeight` that already live there.
|
|
- `reorderStackMember` (`TaskbarApps:113`) is dead — superseded by
|
|
`setStackOrder`. → remove, or wire the menu reorder it was meant for.
|
|
|
|
**The seam:** `Config.options.dock` already exists — the framework anchor is
|
|
already there. "More structure" here means *finishing* that namespace (all
|
|
tuning knobs + a real stack schema under it), not inventing a new one.
|
|
|
|
**Growth rule:** dock feel/config knobs go under `Config.options.dock`; stack
|
|
members are structured entries, not delimited strings. Rename + ungroup are
|
|
table-stakes and should exist before any fancier stack feature.
|
|
|
|
---
|
|
|
|
## 3. Keyboard (OSK) — squeekboard
|
|
|
|
**Meant to be:** an on-screen keyboard that appears on text focus and can be
|
|
summoned by one deliberate gesture, driven through **one** IPC target so its
|
|
visibility and any focus-grab shield always move together.
|
|
|
|
**Today:** squeekboard, launched from `hyprland.lua`, auto-shows via
|
|
input-method-v2, toggled by 3-finger swipe-up routed through ii's `osk` IPC.
|
|
The *routing* is right (single `osk` target).
|
|
|
|
**The seam / the drift:** the comments and the retained
|
|
`quickshell-ii-patches/OnScreenKeyboard.qml` still describe **wvkbd**
|
|
(retired 2026-07-10) and its focus-grab shield — the `osk` IPC now drives
|
|
squeekboard, but the code comments and the history-only patch file still tell
|
|
the wvkbd story. Same doc-vs-reality drift as the pill layerrule. → update the
|
|
comments to squeekboard reality; confirm whether `OnScreenKeyboard.qml`'s
|
|
GlobalStates.oskOpen plumbing is still the thing `osk toggle` hits, or vestigial.
|
|
|
|
**Growth rule:** one `osk` IPC target owns show/hide; layout/theme is
|
|
squeekboard config, addressable later from souveraine-settings. No second path
|
|
to toggle the keyboard.
|
|
|
|
---
|
|
|
|
## 4. Touch / gesture — the input spine
|
|
|
|
**Meant to be:** one routing layer that maps a physical touch gesture to a
|
|
named interface action. Today this is the messiest surface because it is spread
|
|
across four dispatch styles.
|
|
|
|
**Today (`hyprland.lua`):**
|
|
- `hl.gesture{fingers=3,horizontal}` → workspace (Hyprland-native)
|
|
- `hl.plugin.hyprgrass.bind{...}` → close / terminal / edge-swipes →
|
|
`qs -c ii ipc call <target>`
|
|
- the pill's own MouseArea → `dock` IPC
|
|
- `touchdevice.output = "DSI-1"` binds touch to the panel for rotation
|
|
|
|
Two input sources (pill swipe-up, hyprgrass edge-swipe-up) both drive the dock;
|
|
no single arbiter. Gesture → action bindings are inline `exec_cmd` strings
|
|
scattered through the file.
|
|
|
|
**The seam:** this is the memory note "don't hardcode bindings blindly; need a
|
|
routing system to avoid overlap." The clean form is **one gesture table** —
|
|
edge/finger/direction → named IPC target — read top-to-bottom so overlaps are
|
|
visible in one place. Every binding already ends in a `qs ... ipc call <target>`;
|
|
the framework is just making that table explicit and single-sourced instead of
|
|
sprinkled through `hyprland.lua`.
|
|
|
|
**Growth rule:** a new gesture is a new row in the gesture table pointing at a
|
|
named IPC target. Two rows may not target the same action from different edges
|
|
without being adjacent in the table (so overlap is obvious).
|
|
|
|
---
|
|
|
|
## Cross-cutting: the one recurring defect
|
|
|
|
Three of four surfaces have the **same** flaw — *a documented guarantee the
|
|
code doesn't enforce*: the pill layerrule (doc says pinned, code doesn't pin),
|
|
the keyboard (comments say wvkbd, code runs squeekboard), the dock stack pin
|
|
(render says suppressed, config says still pinned). Cleaning these up is mostly
|
|
making the code match its own stated contract, then giving each surface exactly
|
|
one place it's tuned.
|
|
|
|
## Status (2026-07-13)
|
|
|
|
1. ~~Pill layerrule~~ DONE — `pill-above-dock` rule added to hyprland.lua
|
|
(`order = 1` on `^quickshell:pill$`). VERIFY ON GLASS: `order`
|
|
passthrough in the Lua layer_rule wrapper is untested on this build.
|
|
2. ~~Gesture table~~ DONE — hyprgrass binds are one `touch_gestures` table
|
|
in hyprland.lua; the two other gesture sources (hl.gesture workspace
|
|
swipe, the pill's MouseArea) are listed in its header so overlap stays
|
|
visible in one read.
|
|
3. ~~Dock state layer~~ DONE — JSON-per-entry stacks (legacy pipe entries
|
|
still parse, rewritten on next write), minted `stack-N` ids that never
|
|
reuse after delete, name separate from id (+ `renameStack()`, no UI
|
|
yet), pin actually removed from config on combine, tap-cycle starts
|
|
from the truly focused window, dead `reorderStackMember` removed,
|
|
`dragDwellMs`/`pillStripHeight` lifted into `Config.options.dock`.
|
|
4. ~~Keyboard~~ DONE — hyprland.lua comments tell the squeekboard story;
|
|
`OnScreenKeyboard.qml` (the "osk" IPC owner) moved into this surface +
|
|
deploy manifest. Remaining hand-deployed patches (BarContent, Network,
|
|
QuickSliders, switchwall.sh) noted in overlays/QUICKSHELL-MOVED.md.
|
|
|
|
## Open decision — two-stage swipe-up was removed, not lost by accident?
|
|
|
|
Souveraine commit `6e21c50` ("resume agent conversations from server")
|
|
reworked the dock state machine: the Peek state, `dockRevealPulse`, and the
|
|
two-stage swipeUp (reveal → escalate to overview) all went away in favor of
|
|
a flat reveal/hide contract — under a commit title that says nothing about
|
|
the dock. Pixel3Arch CLAUDE.md still records two-stage as SHIPPED
|
|
(2026-07-11). The replacement comment is articulate, so this reads as a
|
|
deliberate redesign, but it needs Casey's confirmation: keep the flat
|
|
contract (overview = the dock's apps button), or restore the escalation.
|
|
Comments in hyprland.lua/HOW-IT-WORKS now describe the flat behavior that
|
|
actually runs.
|