# Phase 2 Lock Screen Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Make lock-screen appearance configurable from Appearance without editing the tracked hyprlock file or risking an unlocked session.

**Architecture:** Schema-backed values feed an atomic `panama-lock` generator under `$XDG_STATE_HOME`. Hypridle invokes the helper, a small Quickshell service proactively regenerates on relevant changes, and an ordinary QML preview mirrors the selected roles without launching the real locker.

**Tech Stack:** Bash, jq, hyprlock 0.9.6 hyprlang, Quickshell 0.3, Qt 6 QML

**Spec:** `docs/superpowers/specs/2026-08-18-phase2-expectation-gaps-design.md`

## Global Constraints

- Never rewrite `config/dot/hypr/hyprlock.conf` at runtime.
- Authentication, PAM, arbitrary commands, markup, fonts, and paths are not configurable.
- Generation is atomic; failure preserves the last valid generated config.
- `run` falls back to the tracked config if generation fails.
- Tests never acquire the live session lock.
- Appearance owns visual controls; Power and Privacy retain their established timing controls.

---

### Task 1: Schema and deterministic generator

**Files:**
- Modify: `config/dot/quickshell/config/PreferenceSchema.qml`
- Create: `config/dot/quickshell/scripts/panama-lock`
- Create: `tests/quickshell/lock-screen-helper-contract`

**Interfaces:**
- Consumes: `lockBackgroundMode`, `lockBlurLevel`, `lockShowClock`, `lockShowDate`, `lockShowUser`, `lockFadeOnEmpty`, `use24Hour`, `colorScheme`, and wallpaper policy from `settings.json`.
- Produces: `panama-lock generate`, `panama-lock status`, and `panama-lock run`.

- [ ] **Step 1: Write the failing helper contract**

Use temporary `XDG_CONFIG_HOME`, `XDG_STATE_HOME`, `HOME`, fake `hyprctl`, and fake `hyprlock`. Cover literal dark defaults and every background mode. Parse the generated file and assert:

```text
background.path = screenshot
background.blur_passes = 3
background.blur_size = 8
clock command = date +"%-I:%M"
date label present = true
user label present = true
input-field.fade_on_empty = false
```

For light solid mode assert `rgba(245, 246, 250, 1.0)`. For wallpaper mode with two fake outputs assert one background block per output and the per-monitor path fallback. Prove invalid enum, blur, boolean, JSON, and path values return to shipped defaults.

- [ ] **Step 2: Run and verify RED**

Run: `tests/quickshell/lock-screen-helper-contract`

Expected: FAIL because `panama-lock` does not exist.

- [ ] **Step 3: Add the six schema entries**

Use exact types and defaults:

```qml
{
    key: "lockBackgroundMode", type: "enum", def: "screenshot", group: "lockAppearance",
    label: "Background", detail: "What appears behind the lock screen",
    options: [
        { value: "screenshot", label: "Blurred desktop" },
        { value: "wallpaper", label: "Current wallpaper" },
        { value: "solid", label: "Solid color" }
    ]
},
{
    key: "lockBlurLevel", type: "int", def: 3, min: 0, max: 5, step: 1,
    group: "lockAppearance", label: "Background blur", detail: "Softens what is behind the password field"
},
{ key: "lockShowClock", type: "bool", def: true, group: "lockAppearance", label: "Show clock", detail: "Use the desktop's 12 or 24-hour format" },
{ key: "lockShowDate", type: "bool", def: true, group: "lockAppearance", label: "Show date", detail: "Show the weekday and full date" },
{ key: "lockShowUser", type: "bool", def: true, group: "lockAppearance", label: "Show user name", detail: "Identify the signed-in account" },
{ key: "lockFadeOnEmpty", type: "bool", def: false, group: "lockAppearance", label: "Hide password field until typing", detail: "Keep the empty field out of the way" }
```

Insert these entries using the repository's existing schema object shape; do not add a new schema feature.

- [ ] **Step 4: Implement `panama-lock`**

Commands and results:

```text
panama-lock generate  -> atomically writes state/panama/hyprlock.conf
panama-lock status    -> {"generated":true,"path":"...","fallback":false,"error":""}
panama-lock run       -> exec hyprlock -c generated; fallback to config/hypr/hyprlock.conf
```

Read values with `jq`, validate again in Bash, map blur levels exactly as:

```text
0 -> passes 0, size 1
1 -> passes 1, size 3
2 -> passes 2, size 5
3 -> passes 3, size 8
4 -> passes 4, size 10
5 -> passes 5, size 12
```

Write `generated.tmp`, validate that it is non-empty and contains `auth`, `background`, and `input-field` blocks, then `mv` it into place. On failure remove only the temporary file.

- [ ] **Step 5: Extend the contract for atomicity and fallback**

Seed a valid generated file, force generation failure through an unwritable fixture target, and prove its checksum is unchanged. Make fake `hyprlock` record argv and prove `run` uses the generated path after success and the tracked fallback after forced failure.

- [ ] **Step 6: Run and verify GREEN**

Run: `tests/quickshell/lock-screen-helper-contract`

Expected: PASS for modes, visibility, clock format, validation, atomicity, and fallback.

- [ ] **Step 7: Commit the generator**

```bash
git add config/dot/quickshell/config/PreferenceSchema.qml config/dot/quickshell/scripts/panama-lock tests/quickshell/lock-screen-helper-contract
git commit -m "Generate managed lock screen configuration"
```

### Task 2: Make every lock path use the generator

**Files:**
- Modify: `config/dot/hypr/hypridle.conf`
- Modify: `config/dot/quickshell/scripts/panama-idle`
- Create: `config/dot/quickshell/services/LockScreen.qml`
- Create: `config/dot/quickshell/lock-screen-harness.qml`
- Create: `tests/quickshell/lock-screen-service-contract`
- Modify: `tests/quickshell/lock-screen-helper-contract`

**Interfaces:**
- Consumes: `panama-lock generate/status` and `DesktopPreferences.revision`.
- Produces: `LockScreen.generated`, `LockScreen.path`, `LockScreen.lastError`, `LockScreen.busy`, `LockScreen.regenerate()`, and `LockScreen.refresh()`.

- [ ] **Step 1: Write the failing service and idle-path contract**

Assert both hypridle sources emit exactly:

```text
lock_cmd = pidof hyprlock || ~/.config/quickshell/scripts/panama-lock run
```

Through the QML harness, change one lock preference three times inside 250 ms and prove exactly one helper generation starts. Return malformed status JSON and prove the previous valid path remains while `lastError` becomes **The lock-screen configuration could not be read.**

- [ ] **Step 2: Run and verify RED**

Run: `tests/quickshell/lock-screen-service-contract`

Expected: FAIL because the idle paths still invoke `hyprlock` directly and the service is absent.

- [ ] **Step 3: Update shipped and generated hypridle commands**

Change only `lock_cmd`; preserve blanking, sleep, and lock timing behavior. Keep both shipped/generated idle-command assertions in `tests/quickshell/lock-screen-service-contract`; this repository has no separate idle-lock contract.

- [ ] **Step 4: Implement `LockScreen.qml`**

Use one `Process` for generation and one for status. Coalesce `DesktopPreferences.revision` through a 250 ms single-shot Timer. On generation exit, call status; accept only JSON with boolean `generated` and string `path`. Keep the last valid status on malformed output.

- [ ] **Step 5: Run and verify GREEN**

Run: `tests/quickshell/lock-screen-helper-contract && tests/quickshell/lock-screen-service-contract`

Expected: PASS with one coalesced generation and no live locker process.

- [ ] **Step 6: Commit lock invocation and service state**

```bash
git add config/dot/hypr/hypridle.conf config/dot/quickshell/scripts/panama-idle config/dot/quickshell/services/LockScreen.qml config/dot/quickshell/lock-screen-harness.qml tests/quickshell/lock-screen-helper-contract tests/quickshell/lock-screen-service-contract
git commit -m "Route session locking through Panama"
```

### Task 3: Appearance card and representative preview

**Files:**
- Create: `config/dot/quickshell/modules/settings/LockScreenPreview.qml`
- Modify: `config/dot/quickshell/modules/settings/AppearancePage.qml`
- Modify: `config/dot/quickshell/services/SettingsSearch.qml`
- Create: `tests/quickshell/lock-screen-settings-contract`
- Modify: `tests/quickshell/settings-search-contract`
- Modify: `config/dot/quickshell/modules/settings/README.md`

**Interfaces:**
- Consumes: schema values, `Wallpaper` effective preview path, `Theme`, and `LockScreen.lastError`.
- Produces: Appearance **Lock screen** card and search routes for background, blur, clock, date, user name, and password-field behavior.

- [ ] **Step 1: Write the failing UI and routing contract**

Construct the real Appearance page in an isolated QML harness for screenshot, wallpaper, and solid modes. Assert the preview changes background source and visibility without starting `hyprlock`. Assert the six settings route to `appearance`, and no new duplicated setting row appears on Power or Privacy.

- [ ] **Step 2: Run and verify RED**

Run: `tests/quickshell/lock-screen-settings-contract && tests/quickshell/settings-search-contract && tests/quickshell/settings-ownership-contract`

Expected: FAIL because the preview, card, and group route are absent.

- [ ] **Step 3: Implement the preview**

Use a clipped `Rectangle` with one asynchronous, decode-bounded `Image` only in wallpaper mode. Screenshot mode uses a static themed approximation with the configured blur level; solid uses `Theme.bg`. Render clock/date/user/password-field elements from the same preferences. Do not use ShaderEffect, live screencopy, pulse, shimmer, or a repeating animation.

- [ ] **Step 4: Add the Appearance controls**

Place the card after Wallpaper and before Typography. Use `ChoiceRow` for background, `SliderRow` with `zeroLabel: "Off"` for blur, and `ToggleRow` for the four booleans. Show `LockScreen.lastError` in the card only when non-empty.

- [ ] **Step 5: Add search and ownership documentation**

Map `lockAppearance` to `appearance`; add manual terms **lock screen background** and **password field** only if the schema labels do not already find them. Document that lock visuals belong to Appearance while lock timing belongs to Power and the existing Privacy mirror.

- [ ] **Step 6: Run and verify GREEN**

Run: `tests/quickshell/lock-screen-settings-contract && tests/quickshell/settings-search-contract && tests/quickshell/settings-ownership-contract`

Expected: PASS with zero QML warnings and no additional mirrors.

- [ ] **Step 7: Commit the lock-screen Settings experience**

```bash
git add config/dot/quickshell/modules/settings/LockScreenPreview.qml config/dot/quickshell/modules/settings/AppearancePage.qml config/dot/quickshell/services/SettingsSearch.qml config/dot/quickshell/modules/settings/README.md tests/quickshell/lock-screen-settings-contract tests/quickshell/settings-search-contract
git commit -m "Add lock screen appearance settings"
```

### Task 4: Backup, reset, and diagnostics integration

**Files:**
- Modify: `config/dot/quickshell/services/SettingsBackup.qml`
- Modify: `config/dot/quickshell/scripts/panama-doctor`
- Modify: `tests/quickshell/settings-backup-live-contract`
- Modify: `tests/quickshell/settings-commit-reset-contract`
- Modify: `tests/quickshell/panama-doctor-contract`
- Modify: `tests/quickshell/lock-screen-service-contract`

**Interfaces:**
- Consumes: `LockScreen.regenerate()` and `panama-lock status`.
- Produces: lock regeneration during restore and a redacted `desktop.hyprlock` health check.

- [ ] **Step 1: Write failing restore and health assertions**

Restore a fixture snapshot with non-default lock values and assert regeneration happens after preferences reload and before shell reload. Reset and assert screenshot background, blur level 3, clock/date/user visible, and the empty password field visible, followed by one regeneration. For doctor fixtures, assert `desktop.hyprlock` is `ok` for a valid generated file, `warning` when fallback is in use, and `error` only when neither generated nor tracked config can be used. Assert no wallpaper path appears in copied diagnostics.

- [ ] **Step 2: Run and verify RED**

Run: `tests/quickshell/settings-backup-live-contract && tests/quickshell/settings-commit-reset-contract && tests/quickshell/panama-doctor-contract`

Expected: FAIL because restore and Health do not know the lock generator.

- [ ] **Step 3: Add restore ordering and health probe**

Inject `regenerateLock` into `SettingsBackup.qml`, start it after preference reload and idle regeneration, and include its bounded busy state in the existing settle timer. Add one authored doctor check whose parsed status includes no config contents or paths.

- [ ] **Step 4: Run and verify GREEN**

Run: `tests/quickshell/settings-backup-live-contract && tests/quickshell/settings-commit-reset-contract && tests/quickshell/panama-doctor-contract && tests/quickshell/lock-screen-service-contract`

Expected: PASS with restore ordering and redacted status.

- [ ] **Step 5: Commit integration**

```bash
git add config/dot/quickshell/services/SettingsBackup.qml config/dot/quickshell/scripts/panama-doctor tests/quickshell/settings-backup-live-contract tests/quickshell/settings-commit-reset-contract tests/quickshell/panama-doctor-contract tests/quickshell/lock-screen-service-contract
git commit -m "Integrate managed lock screen recovery"
```

### Task 5: Slice verification

- [ ] **Step 1: Run all lock contracts once**

```bash
tests/quickshell/lock-screen-helper-contract
tests/quickshell/lock-screen-service-contract
tests/quickshell/lock-screen-settings-contract
tests/quickshell/settings-search-contract
tests/quickshell/settings-ownership-contract
tests/quickshell/settings-backup-live-contract
tests/quickshell/settings-commit-reset-contract
tests/quickshell/panama-doctor-contract
```

Expected: all PASS and no process named `hyprlock` is started by the tests.

- [ ] **Step 2: Validate formatting and process safety**

Run: `bash -n config/dot/quickshell/scripts/panama-lock && bash -n config/dot/quickshell/scripts/panama-idle && git diff --check`

Expected: exit 0.
