--- task_id: souveraine-lock-primitives-001 title: "Souveraine-owned AGPL lock primitives — cut the cord from ii by rewriting" status: pending priority: high phase: build created: 2026-07-18 references: docs/tasks/qs-souveraine-composer.md, docs/DECISIONS.md, ~/Projects/MAKE_PROPER_LICENSES_FOR_ALL_PROJECTS.md, surfaces/quickshell/modules/ii/lock/TouchLockSurface.qml, surfaces/quickshell/modules/souveraine/lock/ --- # Souveraine-owned AGPL lock primitives ## The end goal (build THIS, not an increment) A fully Souveraine-owned, fully AGPL lock surface. Zero ii-derived files in the lock dependency graph. Every primitive the lock uses is original Souveraine-authored QML, licensed AGPLv3, designed to behave how *we* want — not a copy of ii. There is no v1/v2 here. We do not vendor ii files. We do not borrow ii widgets as a bridge. We accept that the lock may not compile, or may be missing a subcomponent, while the owned primitives are built. That is preferable to a single borrowed-widget compromise. See the cord-cut rule in `docs/DECISIONS.md` ("Shell = own config") and `docs/tasks/qs-souveraine-composer.md`. ## Why rewrite, not vendor Vendoring an ii file inherits GPLv3 — we can never make it ours by relabeling. The licensing master doc governs the boundary: original Souveraine files get AGPL headers + Casey as copyright holder; genuinely inherited GPLv3 files keep their headers. Because we choose to author rather than inherit, every lock primitive will be original AGPL, and the lock surface conveys cleanly under our license. ii is behavioral reference only — read how it behaves, write our own implementation. Do not copy. ## What the lock surface currently depends on Direct external symbols referenced by the lock files (verified by grep, 2026-07-18): | Symbol | Used by | What it is | |--------|---------|------------| | `RippleButton` | `TouchLockSurface.qml` (KeypadButton, UtilityButton inline components) | Pressable button with material-ripple feedback. Transitive ii dep: `MaterialShape`. | | `StyledText` | `TouchLockSurface.qml` (dots hint, keypad glyphs, hint labels) | Themed text bound to `Appearance` colors/fonts. | | `MaterialSymbol` | `TouchLockSurface.qml` (icons: backspace, arrow, power, reboot) | Material Symbols icon glyph. | | `ErrorShakeAnimation` | `TouchLockSurface.qml` (wrong-PIN shake on `dotsArea`) | Frame-driven horizontal shake animation. | | `Appearance` (singleton) | `TouchLockSurface.qml` (15×), `LockScreen.qml` (1×). The host + cards use raw literals instead — evidence for the palette work below. | Colors (`colOnLayer1`, `colError`, `colSubtext`, `colOnSurfaceVariant`, `colOnPrimary`) + font pixel sizes. | | `ColorUtils` | `TouchLockSurface.qml` | `transparentize()`. | | `Translation` | `TouchLockSurface.qml` | `tr()` i18n. | | `MaterialShape` | transitive (used by ii's RippleButton) | Material-style rounded/shape background. First-class rewrite target since our RippleButton needs it. | Everything else in the lock files is QtQuick/QtQuick.Layouts/Quickshell (primitives we keep) or Souveraine-owned already (`GlobalStates`, `LockContext`, `Config`, `LockContentPolicy`, `Ai`, `Battery`, `LockSurfaceHost`, `LockMediaCard`, `LockAgentCard`). ## What to build (the primitive set) All under a Souveraine-owned path — propose `surfaces/quickshell/modules/souveraine/widgets/` — each file original AGPL, Casey as copyright holder, designed from the lock's needs with ii as behavioral reference only. ### Tier 0 — theme singletons (these are not optional; everything below keys off them) - **`Appearance`** — colors + font scale. This is the single source of truth every styled component reads. Decide the Souveraine lock palette here (replacing the scattered literals `#d9ffffff`, `#e6ffffff`, `#1c1b20`, `#b3181b20`, `#22000000`, `#33ffffff`, `#55ffffff` currently hand-rolled across the three lock cards). One palette, named tokens. - **`ColorUtils`** — at minimum `transparentize(color, amount)`. - **`Translation`** — `tr()` passthrough to whatever i18n we standardize on. ### Tier 1 — visual primitives - **`StyledText`** — text bound to Appearance tokens; the type every label uses. - **`MaterialSymbol`** — icon glyph renderer. - **`MaterialShape`** — material background shape (rounded rect minimum; designed so `RippleButton` can compose it). - **`ErrorShakeAnimation`** — horizontal shake with the *midpoint-crossing termination* rule (stop when the oscillation would cross the rest point, not on a fixed timer — Phosh's `shake_entry` model, frame-clock driven). ### Tier 2 — composite primitives (compose Tier 1) - **`RippleButton`** — the pressable button. Ripple feedback baked in from day one (this is one of the "minor details" Casey wants to tweak — build it ours, tunable). Properties the lock uses today: `contentItem`, `colBackground`, `buttonRadius`, `toggled`, `enabled`, signals `clicked`/`pressAndHold`. ### Tier 3 — lock-surface components (built on the owned primitives) These replace the inline blocks in `TouchLockSurface.qml`: - **`AmbientCard`** — the shared bordered rounded-rect card both `LockMediaCard` and `LockAgentCard` currently hand-roll with different radii/alphas. One primitive, one palette. - **`PinDots`** — the entered-PIN dots row + empty-state hint, with `ErrorShakeAnimation` wired in. - **`LockKeypad`** — the 3×3 + 0/backspace/confirm grid, built from our `RippleButton`. - **`LockUtilityRow`** — power / battery / reboot, from our `RippleButton`. - **`LockReveal`** — the glance↔PIN stage controller. Owns the reveal intent (`reveal()`, `retreat()`) so every input path (swipe, tap-hint, hardware key, future double-tap-clock) funnels through one transition instead of each re-implementing threshold logic. Replaces the inline `pressY`/threshold/retreat-timer block in `TouchLockSurface.qml`. ### The result `TouchLockSurface.qml` shrinks to ~layout + intent wiring (target ~60–80 lines), built entirely on Souveraine-owned AGPL QML. The lock dep graph has zero ii-derived files. ## How to build it (approach, not steps) 1. **Tier 0 first.** `Appearance`/`ColorUtils`/`Translation` — nothing else compiles without them. Define the Souveraine lock palette here as named tokens; retire the scattered literals. 2. **Tier 1 next**, in dependency order: `StyledText` (needs Appearance), `MaterialSymbol`, `MaterialShape`, then `ErrorShakeAnimation` (standalone). 3. **Tier 2:** `RippleButton` (composes `MaterialShape`). 4. **Tier 3:** `AmbientCard`, `PinDots`, `LockKeypad`, `LockUtilityRow`, `LockReveal` — built on Tiers 0–2. 5. **Rewire `TouchLockSurface`** and the two cards to consume the owned primitives, removing every ii import. Each file lands with an AGPLv3 header and Casey as copyright holder. Behavioral reference from ii is fine (read how ii's `RippleButton` feels, what properties it exposes); copying is not. ## ii reference access (behavioral, read-only) ii's widget sources are NOT in this repo. They live on the phone at `/home/casey/.config/quickshell/ii/modules/common/widgets/` (RippleButton, StyledText, MaterialSymbol, MaterialShape, ErrorShakeAnimation, etc.) and on the laptop at `~/.config/quickshell/ii/`. SSH: `ssh pixel`. Read them to understand behavior and the property contract the lock code expects — do not copy. Write fresh AGPL implementations. Phosh's lockscreen source is also on the phone at `~/build/phosh/src/` (`lockscreen.c`, `ui/lockscreen.ui`) — useful behavioral reference for the swipe-capture model (deck/carousel, rounded-progress page gating) and the shake midpoint-crossing termination. Do not port; implement in QML. ## Manifest + composer wiring Every new Souveraine-owned file needs a line in `surfaces/quickshell/deploy.sh` `MANIFEST` (the composer only ships files in the manifest — a missing line means "is not a type" at load). As files are written, add manifest lines pointing them into the `souveraine/` composed config. Borrowed ii widget dirs stay borrowed for the rest of the shell until their own cord-cut; only the lock's deps are cut here. ## License headers Per `~/Projects/MAKE_PROPER_LICENSES_FOR_ALL_PROJECTS.md`: - Every file written here: AGPLv3 header, Casey Tunturi as copyright holder. - Do NOT put inherited GPLv3 headers on these — they are original work. - Mixed tree is honest: the lock dep graph is all-original AGPL after this task; other parts of the shell may still carry inherited GPLv3 until their own rewrite. ## Non-goals / out of scope for this task - Rewriting non-lock widgets (dock, sidebar, overview, etc.) — separate cord-cuts, separate tasks. - Changing lock *behavior* beyond what the primitive rewrite naturally enables. Feature additions (clock double-tap, new gesture paths, peek mode) are follow-on work on top of the owned primitives, not part of getting the substrate owned. - Touching sessiond (Rust) — it is already Souveraine-owned and unaffected. ## Done when - [ ] Tier 0–2 primitives exist as original AGPL files under `modules/souveraine/widgets/` (or agreed path), each with AGPL header. - [ ] Tier 3 lock components exist, built on the owned primitives. - [ ] `TouchLockSurface.qml` + both lock cards import only Souveraine-owned + Qt/Quickshell modules — zero `qs.modules.common.widgets` ii-borrowed imports in the lock graph. - [ ] The lock surface dep graph contains no ii-derived files (grep verifiable). - [ ] Manifest lines added; `deploy.sh --phone` ships and the lock runs. - [ ] Cord-cut ledger updated (this file's "Done when", or a dedicated ledger) recording which borrowed dirs remain.