Files
Panama/docs/superpowers/plans/2026-08-18-phase2-lock-screen.md
T

14 KiB

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

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:

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

Expected: FAIL because panama-lock does not exist.

  • Step 3: Add the six schema entries

Use exact types and defaults:

{
    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:

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:

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

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

  • Step 7: Commit the generator
git add config/dot/quickshell/config/PreferenceSchema.qml config/dot/quickshell/scripts/panama-lock tests/quickshell/lock-screen-helper-contract.sh
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.sh
  • Modify: tests/quickshell/lock-screen-helper-contract.sh

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:

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

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.sh; 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.sh && tests/quickshell/lock-screen-service-contract.sh

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

  • Step 6: Commit lock invocation and service state
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.sh tests/quickshell/lock-screen-service-contract.sh
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.sh
  • Modify: tests/quickshell/settings-search-contract.sh
  • 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.sh && tests/quickshell/settings-search-contract.sh && tests/quickshell/settings-ownership-contract.sh

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.sh && tests/quickshell/settings-search-contract.sh && tests/quickshell/settings-ownership-contract.sh

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

  • Step 7: Commit the lock-screen Settings experience
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.sh tests/quickshell/settings-search-contract.sh
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.sh
  • Modify: tests/quickshell/settings-commit-reset-contract.sh
  • Modify: tests/quickshell/panama-doctor-contract.sh
  • Modify: tests/quickshell/lock-screen-service-contract.sh

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.sh && tests/quickshell/settings-commit-reset-contract.sh && tests/quickshell/panama-doctor-contract.sh

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.sh && tests/quickshell/settings-commit-reset-contract.sh && tests/quickshell/panama-doctor-contract.sh && tests/quickshell/lock-screen-service-contract.sh

Expected: PASS with restore ordering and redacted status.

  • Step 5: Commit integration
git add config/dot/quickshell/services/SettingsBackup.qml config/dot/quickshell/scripts/panama-doctor tests/quickshell/settings-backup-live-contract.sh tests/quickshell/settings-commit-reset-contract.sh tests/quickshell/panama-doctor-contract.sh tests/quickshell/lock-screen-service-contract.sh
git commit -m "Integrate managed lock screen recovery"

Task 5: Slice verification

  • Step 1: Run all lock contracts once
tests/quickshell/lock-screen-helper-contract.sh
tests/quickshell/lock-screen-service-contract.sh
tests/quickshell/lock-screen-settings-contract.sh
tests/quickshell/settings-search-contract.sh
tests/quickshell/settings-ownership-contract.sh
tests/quickshell/settings-backup-live-contract.sh
tests/quickshell/settings-commit-reset-contract.sh
tests/quickshell/panama-doctor-contract.sh

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.