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.confat runtime. - Authentication, PAM, arbitrary commands, markup, fonts, and paths are not configurable.
- Generation is atomic; failure preserves the last valid generated config.
runfalls 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 fromsettings.json. -
Produces:
panama-lock generate,panama-lock status, andpanama-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/statusandDesktopPreferences.revision. -
Produces:
LockScreen.generated,LockScreen.path,LockScreen.lastError,LockScreen.busy,LockScreen.regenerate(), andLockScreen.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,
Wallpapereffective preview path,Theme, andLockScreen.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()andpanama-lock status. -
Produces: lock regeneration during restore and a redacted
desktop.hyprlockhealth 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.