Files
Panama/docs/superpowers/plans/2026-08-18-phase2-lock-screen.md
Gabriel Brown e1faaf7a76 Drop the extension, and give the test suite a front door
Phase 6, the last of the fresh-install spec.

159 scripts lose their .sh: 110 contracts, 47 Vicinae commands, 2 compositor
contracts. A shebang and the executable bit already select the interpreter. The
extension only ever added something that had to stay in sync, and the rename
proved the point twice over in the space of an hour.

The spec's stated risk was Vicinae's script discovery. One script was renamed and
reloaded on its own before the other 46 followed; it came back as
scripts:panama.capture and all 47 resolve. What the probe turned up instead is
that the extension was never only a filename: Vicinae's command IDs embed it, so
every ID changed. Nothing in this repository refers to them, so nothing breaks.
The only trace is Vicinae's metadata.json, whose visited map had two Panama
entries that are now orphaned -- two commands lost their usage ranking and will
earn it back. Worth knowing before anyone renames these again on a machine that
has a keybind pointing at one.

Rewriting the references by exact filename missed two things it structurally
could not see: a name built from a variable, settings-$page.sh, and a glob,
-name '*.sh'. Both were in the contract that counts the generated commands, which
promptly reported 47 expected and 0 found. The mechanical part of a rename is the
part that looks finished.

The three subcommands. panama doctor fronts a health check that already existed
and already ran at the end of every install but could not be reached from a
terminal. panama upgrade re-runs the installer from anywhere. panama test runs
the suite, which had no entry point at all -- 121 files that were the main safety
net in this repository and were invisible in it.

Writing that runner found three tests nothing was running.
calendar_agenda_bridge_test, home_assistant_bridge_test and kdeconnect_bridge_test
are unittest suites without the executable bit, so no contract invoked them and
the first draft of the runner skipped them silently. All three pass, and have
passed unobserved for weeks. The runner collects *_test.py as well now, because a
runner with a blind spot is worse than no runner for the same reason a dependency
checker with one is: it reports PASS.

Six worktrees pruned. Each was re-checked rather than trusted to the spec's list,
and two needed it: panama-commands is not on feat/panama-commands but on
feat/gnome-tweaks-parity, and fix/panama-displays-review reads [ahead 3] -- ahead
of its remote, not of main, with every commit patch-equivalent to landed work.
roadmap-completion stays; it has five commits that are genuinely unlanded. The
branches are left alone: pruning a worktree costs nothing, deleting a branch is a
decision.

121 contracts pass.

Claude-Session: https://claude.ai/code/session_01NvgBuSWB5sE43yWmg21ozj
2026-08-20 21:55:55 -04:00

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

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

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

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

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