---
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.
