29 KiB
Panama Control Center 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: Expand Panama Quick Settings into the approved Control Center with secure Home Assistant favourites and capability-aware iPhone continuity.
Architecture: Two executable Python helpers own the external boundaries: KDE Connect CLI/D-Bus and the Home Assistant REST API/credential lookup. Quickshell singletons normalize those JSON contracts for focused Home and Phone components inside the existing Quick Settings window; Ongoing and Signal Glass consume transfer lifecycle without duplicating state.
Tech Stack: Python 3.14 standard library, Secret Service CLI, KDE Connect 25.12 CLI/D-Bus, Home Assistant REST API, Quickshell 0.3/QML, Bash contract tests, Python unittest, jq, Hyprland 0.56
Spec: docs/superpowers/specs/2026-08-17-panama-control-center-design.md
Global Constraints
- Keep Wi-Fi, Bluetooth, Focus, Caffeine, Night Light, audio, account, settings, power, Escape, and outside-click behavior intact.
- The Control Center width is 430 logical pixels and its top margin is exactly
Theme.barHeight + 2. - Only one of Wi-Fi, Bluetooth, sink, source, Home, and Phone may be expanded at once.
- Never print Home Assistant tokens, Authorization headers, clipboard text, certificate fingerprints, IP addresses, response bodies, or full local file paths.
- Home Assistant tokens remain in the gitignored environment file or Secret Service; never write one into tracked content.
- Home Assistant toggle commands accept only entity IDs from the configured favourites snapshot.
- KDE Connect actions derive from advertised plugins; no fake battery or remote-input control is allowed.
- Every Home toggle, file chooser, file send, clipboard send, and Ring action requires a user click.
- Home toggle success, phone Ring, and clipboard success remain globally quiet.
- A running file send appears in Ongoing without a fabricated percentage.
- Automated tests do not toggle a real Home entity, ring the phone, send clipboard contents, or transfer a real file.
- No pulse, shimmer, animated gradient, idle animation, or high-frequency polling.
File map
KDE Connect boundary
config/dot/quickshell/scripts/panama-kdeconnect— validates device/file inputs, normalizes device/plugin status, and invokes allow-listed actions.tests/quickshell/kdeconnect_bridge_test.py— pure parser, capability, validation, and command tests with fake runners.tests/quickshell/kdeconnect-helper-contract.sh— live read-only status contract with names and IDs redacted.config/dot/quickshell/services/KdeConnect.qml— singleton device, action, transfer, fixture, and recent-exchange state.
Home Assistant boundary
config/dot/quickshell/scripts/panama-home-assistant— configuration resolution, normalized REST reads, allow-listed toggles, and URL handoff.tests/quickshell/home_assistant_bridge_test.py— local fake API and credential/configuration precedence tests.tests/quickshell/home-assistant-helper-contract.sh— live read-only probe/snapshot contract with entity content redacted.config/dot/quickshell/services/HomeAssistant.qml— singleton entities, stale state, per-entity action state, fixture, and inline errors.
Control Center presentation and integration
config/dot/quickshell/config/Theme.qml— dedicatedcontrolCenterWidthandcontrolCenterTopGapgeometry tokens.config/dot/quickshell/modules/quicksettings/QuickSettings.qml— tighter top attachment and service preparation.config/dot/quickshell/modules/quicksettings/QuickSettingsPanel.qml— expanded composition and exclusive section routing.config/dot/quickshell/modules/quicksettings/ControlSectionHeader.qml— quiet section label and optional trailing action.config/dot/quickshell/modules/quicksettings/HomeControls.qml— four favourites, expanded configured list, loading/stale/failure states.config/dot/quickshell/modules/quicksettings/HomeTile.qml— one domain-aware favourite tile.config/dot/quickshell/modules/quicksettings/PhoneControls.qml— approved compact Continuity Card.config/dot/quickshell/modules/quicksettings/RecentExchange.qml— one session-local completed transfer row.config/dot/quickshell/modules/bar/StatusCluster.qml— reachable-phone glyph inside the existing click target.config/dot/quickshell/services/DeviceEvents.qml— consume shared KDE Connect state instead of a second polling process.config/dot/quickshell/services/Ongoing.qml— append and control one running phone transfer.config/dot/quickshell/services/StatusEvents.qml— open a completed received/sent path through the existing allow-listed action.config/dot/quickshell/shell.qml— diagnostics/fixture IPC for the two services.tests/quickshell/control-center-services-contract.sh— deterministic service, activity, and event contract.tests/quickshell/control-center-contract.sh— panel geometry, routing, components, and live layer contract.
Documentation
config/dot/hypr/DESKTOP-PARITY.md— mark both remaining integrations live.config/dot/hypr/README.md— document Control Center, credential resolution, and iOS capability limits.
Task 1: KDE Connect helper contract
Files:
- Create:
config/dot/quickshell/scripts/panama-kdeconnect - Create:
tests/quickshell/kdeconnect_bridge_test.py - Create:
tests/quickshell/kdeconnect-helper-contract.sh
Interfaces:
-
Produces:
panama-kdeconnect statusJSON withavailableanddevices. -
Produces device fields:
id,name,type,paired,reachable, andactions. -
Produces:
send-file DEVICE FILE,send-clipboard DEVICE, andring DEVICEwith one normalized JSON result. -
Accepts an injectable
runner: Callable[[list[str]], CompletedProcess[str]]in pure functions. -
Step 1: Write failing parser and action tests
Create tests/quickshell/kdeconnect_bridge_test.py with import-by-path and these cases:
class KdeConnectBridgeTest(unittest.TestCase):
def test_status_omits_network_and_exposes_supported_ios_actions(self):
listing = "- Gib's iPhone: 90B54E84548E4F109268EC2D0E7D6930 on 192.168.1.7 via LAN (paired and reachable)\n1 device found\n"
plugins = ["kdeconnect_clipboard", "kdeconnect_findmyphone", "kdeconnect_ping", "kdeconnect_share"]
device = bridge.normalize_device_line(listing.splitlines()[0], plugins)
self.assertEqual(device["name"], "Gib's iPhone")
self.assertEqual(device["actions"], ["clipboard", "ping", "ring", "share"])
self.assertNotIn("192.168.1.7", json.dumps(device))
def test_invalid_device_id_is_rejected(self):
with self.assertRaisesRegex(ValueError, "invalid-device"):
bridge.validate_device_id("phone; shutdown")
def test_send_file_requires_a_regular_file(self):
with tempfile.TemporaryDirectory() as directory:
with self.assertRaisesRegex(ValueError, "invalid-file"):
bridge.validate_file(pathlib.Path(directory))
def test_action_command_uses_separate_arguments(self):
command = bridge.action_command("ring", "90B54E84548E4F109268EC2D0E7D6930")
self.assertEqual(command, ["kdeconnect-cli", "-d", "90B54E84548E4F109268EC2D0E7D6930", "--ring"])
- Step 2: Run the test and verify RED
Run python3 tests/quickshell/kdeconnect_bridge_test.py.
Expected: FAIL because panama-kdeconnect does not exist.
- Step 3: Implement the minimal helper
Implement public functions named validate_device_id, validate_file,
normalize_device_line, action_command, collect_status, invoke_action,
and main. Use these exact validation and command-building implementations:
DEVICE_ID = re.compile(r"^[A-Fa-f0-9]{32,64}$")
PLUGIN_ACTIONS = {
"kdeconnect_clipboard": "clipboard",
"kdeconnect_findmyphone": "ring",
"kdeconnect_ping": "ping",
"kdeconnect_share": "share",
}
def validate_device_id(value: str) -> str:
if not DEVICE_ID.fullmatch(value):
raise ValueError("invalid-device")
return value
def validate_file(value: pathlib.Path) -> pathlib.Path:
resolved = value.expanduser().resolve(strict=True)
if not resolved.is_file():
raise ValueError("invalid-file")
return resolved
def action_command(action: str, device_id: str, file_path: pathlib.Path | None = None) -> list[str]:
device_id = validate_device_id(device_id)
options = {
"ring": ["--ring"],
"clipboard": ["--send-clipboard"],
}
if action == "share" and file_path is not None:
return ["kdeconnect-cli", "-d", device_id, "--share", str(validate_file(file_path))]
if action not in options:
raise ValueError("unsupported-action")
return ["kdeconnect-cli", "-d", device_id, *options[action]]
normalize_device_line(line: str, plugins: list[str]) -> dict[str, object]
must parse one detailed CLI line and omit transport/address text.
collect_status(runner=subprocess.run) -> dict[str, object] must return
available plus normalized devices. invoke_action -> dict[str, object]
must run only action_command output. main(argv: list[str]) -> int must emit
one compact JSON object and map validation failures to the public error code.
Call kdeconnect-cli --list-devices without a shell. For each parsed device,
call busctl --user call org.kde.kdeconnect /modules/kdeconnect/devices/DEVICE org.kde.kdeconnect.device loadedPlugins; parse only plugin names. Return generic
error codes unavailable, device-offline, unsupported-action,
invalid-device, invalid-file, or action-failed, never raw stderr.
For actions, construct exactly one of:
["kdeconnect-cli", "-d", device_id, "--ring"]
["kdeconnect-cli", "-d", device_id, "--send-clipboard"]
["kdeconnect-cli", "-d", device_id, "--share", str(file_path)]
Make the helper executable.
- Step 4: Run unit tests and verify GREEN
Run python3 tests/quickshell/kdeconnect_bridge_test.py.
Expected: all cases PASS with no device name, address, or file path printed by the test runner.
- Step 5: Add and run the redacted live contract
Create tests/quickshell/kdeconnect-helper-contract.sh that runs status and asserts:
jq -e '
.available == true and
(.devices | type == "array" and length >= 1) and
([.devices[] | has("id") and has("name") and has("type") and has("paired") and has("reachable") and has("actions")] | all) and
([.devices[].actions[] | IN("clipboard", "ping", "ring", "share")] | all)
' <<<"$status" >/dev/null
Print only PASS (N paired, N reachable; identities redacted).
- Step 6: Commit Task 1
git add config/dot/quickshell/scripts/panama-kdeconnect \
tests/quickshell/kdeconnect_bridge_test.py \
tests/quickshell/kdeconnect-helper-contract.sh
git commit -m "Add the Panama KDE Connect bridge"
Task 2: Home Assistant helper contract
Files:
- Create:
config/dot/quickshell/scripts/panama-home-assistant - Create:
tests/quickshell/home_assistant_bridge_test.py - Create:
tests/quickshell/home-assistant-helper-contract.sh
Interfaces:
-
Produces:
probe,snapshot,toggle ENTITY_ID, andopen. -
Produces snapshot fields:
ok,configured,generatedAt,entities, anderror. -
Produces entity fields:
id,name,domain,state,available, andactive. -
Configuration precedence:
PANAMA_HOME_ASSISTANT_*, GNOME extension dconf, then Secret Service for the token. -
Step 1: Write failing configuration, normalization, and fake-API tests
Create tests/quickshell/home_assistant_bridge_test.py with a local
ThreadingHTTPServer. Cover:
def test_environment_configuration_wins_over_legacy_sources(self):
env = {
"PANAMA_HOME_ASSISTANT_URL": "https://home.example",
"PANAMA_HOME_ASSISTANT_TOKEN": "fixture-token",
"PANAMA_HOME_ASSISTANT_ENTITIES": "light.kitchen,light.hall",
}
config = bridge.resolve_config(env, legacy=lambda: (_ for _ in ()).throw(AssertionError()))
self.assertEqual(config.entity_ids, ("light.kitchen", "light.hall"))
def test_snapshot_filters_and_preserves_configured_order(self):
raw = [
{"entity_id": "light.hall", "state": "off", "attributes": {"friendly_name": "Hall"}},
{"entity_id": "sensor.private", "state": "1", "attributes": {}},
{"entity_id": "light.kitchen", "state": "on", "attributes": {"friendly_name": "Kitchen"}},
]
result = bridge.normalize_entities(raw, ("light.kitchen", "light.hall"))
self.assertEqual([item["name"] for item in result], ["Kitchen", "Hall"])
self.assertNotIn("sensor.private", json.dumps(result))
def test_toggle_rejects_unconfigured_entity(self):
with self.assertRaisesRegex(ValueError, "entity-not-configured"):
bridge.ensure_configured("light.office", ("light.kitchen",))
def test_authentication_error_is_redacted(self):
self.assertEqual(bridge.public_http_error(401, "sensitive response"), "authentication-required")
The fake server must also assert the request uses
Authorization: Bearer fixture-token, that snapshot calls GET /api/states,
and that toggle calls POST /api/services/homeassistant/toggle with only
{"entity_id":"light.kitchen"}. Do not print the header.
- Step 2: Run the tests and verify RED
Run python3 tests/quickshell/home_assistant_bridge_test.py.
Expected: FAIL because panama-home-assistant does not exist.
- Step 3: Implement configuration and REST boundaries
Implement the exact Config type and these public functions:
@dataclass(frozen=True)
class Config:
base_url: str
token: str
entity_ids: Sequence[str]
def ensure_configured(entity_id: str, configured: Sequence[str]) -> str:
if entity_id not in configured:
raise ValueError("entity-not-configured")
return entity_id
def public_http_error(status: int, body: str) -> str:
del body
return "authentication-required" if status in {401, 403} else "request-failed"
def normalize_entities(raw: list[dict[str, object]], configured: Sequence[str]) -> list[dict[str, object]]:
by_id = {str(item.get("entity_id", "")): item for item in raw}
result = []
for entity_id in configured:
item = by_id.get(entity_id)
if not item or not isinstance(item.get("attributes"), dict):
continue
state = str(item.get("state", "unavailable"))
available = state not in {"unknown", "unavailable"}
result.append({
"id": entity_id,
"name": str(item["attributes"].get("friendly_name") or entity_id.split(".", 1)[1].replace("_", " ").title()),
"domain": entity_id.split(".", 1)[0],
"state": state,
"available": available,
"active": available and state not in {"off", "closed", "idle", "standby"},
})
return result
resolve_config(env, legacy=load_legacy_config) -> Config implements the
documented precedence. load_legacy_config(runner=subprocess.run) -> Config
reads only the named dconf and Secret Service values. request_json
performs one authorized request. collect_snapshot(config) -> dict calls
GET /api/states; toggle(config, entity_id) -> dict validates then calls the
Home Assistant toggle service; main(argv: list[str]) -> int emits compact JSON.
Read config/bash/env only through a non-interactive Bash subprocess that emits
the three named Panama values and nothing else. The legacy path reads only
hass-url, hass-enabled-entities, and secret-tool lookup token_string user_token. Validate the base URL as HTTP(S), strip a trailing slash, reject
entity IDs outside ^[a-z_]+\.[a-z0-9_]+$, deduplicate while preserving order,
and use urllib.request with a 5-second timeout.
Map state as follows:
available = state not in {"unknown", "unavailable"}
active = available and state not in {"off", "closed", "idle", "standby"}
Return only normalized entities. Map 401/403 to authentication-required,
timeouts and transport errors to unreachable, and malformed JSON to
invalid-response.
- Step 4: Run unit tests and verify GREEN
Run python3 tests/quickshell/home_assistant_bridge_test.py.
Expected: all tests PASS and the fake server records no unexpected route.
- Step 5: Add and run the redacted live contract
The contract runs probe and snapshot, validates the schema, and prints only
PASS (configured=true, N favourites; contents redacted). It must pipe JSON to
jq without printing friendly names, entity IDs, URL, or raw errors.
- Step 6: Commit Task 2
git add config/dot/quickshell/scripts/panama-home-assistant \
tests/quickshell/home_assistant_bridge_test.py \
tests/quickshell/home-assistant-helper-contract.sh
git commit -m "Add the Panama Home Assistant bridge"
Task 3: Quickshell service state and Ongoing integration
Files:
- Create:
config/dot/quickshell/services/KdeConnect.qml - Create:
config/dot/quickshell/services/HomeAssistant.qml - Create:
tests/quickshell/control-center-services-contract.sh - Modify:
config/dot/quickshell/services/DeviceEvents.qml - Modify:
config/dot/quickshell/services/Ongoing.qml - Modify:
config/dot/quickshell/shell.qml
Interfaces:
-
KdeConnect:available,devices,preferredPhone,phoneReachable,transferActive,transferFileName,recentExchange,refresh(),sendFile(path),sendClipboard(),ring(),cancelTransfer(),applyFixture(name),clearFixture(). -
HomeAssistant:phase,entities,visibleEntities,configuredCount,stale,busyEntityId,lastError,refresh(),toggleEntity(id),open(),applyFixture(name),clearFixture(). -
IPC targets:
kdeconnectandhome-assistantwith fixture, reset, refresh, action-safe diagnostics, and status functions. -
Step 1: Write the failing live fixture contract
Create a shell test that asserts both IPC targets, then exercises synthetic fixtures only:
qs ipc call kdeconnect fixture reachable >/dev/null
jq -e '.fixture and .reachable and .actionCount == 4 and .transferActive == false' \
<<<"$(qs ipc call kdeconnect status)" >/dev/null
qs ipc call kdeconnect fixture transfer >/dev/null
jq -e '.transferActive and .ongoingCount >= 1' \
<<<"$(qs ipc call kdeconnect status)" >/dev/null
qs ipc call home-assistant fixture ready >/dev/null
jq -e '.fixture and .phase == "ready" and .configuredCount == 7 and .visibleCount == 4' \
<<<"$(qs ipc call home-assistant status)" >/dev/null
qs ipc call home-assistant fixture stale >/dev/null
jq -e '.phase == "degraded" and .stale and .configuredCount == 7' \
<<<"$(qs ipc call home-assistant status)" >/dev/null
Cleanup resets both fixture modes and closes Quick Settings.
- Step 2: Run the contract and verify RED
Run tests/quickshell/control-center-services-contract.sh.
Expected: FAIL because the IPC targets do not exist.
- Step 3: Implement
KdeConnect.qml
Use one status Process, one action Process, and one low-frequency Timer.
Parse one JSON object per completion. Fixture data contains only synthetic names.
Expose preferredPhone as the first reachable type === "phone", then the
first paired phone. Keep action commands as arrays:
actionProcess.command = [helperPath, "send-file", phone.id, path]
actionProcess.running = true
While send-file runs, set transferActive, basename-only
transferFileName, and the device name. On success, set recentExchange and
publish one important StatusEvents completion with an open-path action only
when a local path is safe to reopen. Ring and clipboard completion publish no
event. cancelTransfer() terminates only the owned action process.
- Step 4: Implement
HomeAssistant.qml
Use separate refresh and action Process objects. Preserve entities when a
refresh fails after a successful snapshot and set stale=true. Use one
busyEntityId because the UI allows one Home request at a time. After a
successful toggle, immediately run refresh(). Expose the first four entities
as visibleEntities and retain all configured entities for expansion.
- Step 5: Wire shared activity and transitions
Replace DeviceEvents.qml's private KDE polling process with properties and
change handlers bound to KdeConnect.phoneReachable and the preferred phone's
name. Add this activity to Ongoing.activities:
if (KdeConnect.transferActive) {
result.push({
kind: "phone-transfer",
glyph: "\u{F03F2}",
label: "Sending to " + KdeConnect.transferDeviceName,
detail: KdeConnect.transferFileName,
state: "Sending",
tone: "accent",
action: "Cancel"
});
}
Route Ongoing.invoke("phone-transfer") to KdeConnect.cancelTransfer().
Add typed diagnostic IPC methods without any command that performs a live Home
toggle, Ring, clipboard send, or file send.
- Step 6: Restart Quickshell and run the service contract GREEN
Run the repository's established shell reload command, wait for the current instance to report IPC, then run:
tests/quickshell/control-center-services-contract.sh
Expected: PASS with fixture state only and no QML warnings in the fresh journal.
- Step 7: Commit Task 3
git add config/dot/quickshell/services/KdeConnect.qml \
config/dot/quickshell/services/HomeAssistant.qml \
config/dot/quickshell/services/DeviceEvents.qml \
config/dot/quickshell/services/Ongoing.qml \
config/dot/quickshell/shell.qml \
tests/quickshell/control-center-services-contract.sh
git commit -m "Add Control Center integration services"
Task 4: Approved Control Center interface
Files:
- Create:
config/dot/quickshell/modules/quicksettings/ControlSectionHeader.qml - Create:
config/dot/quickshell/modules/quicksettings/HomeControls.qml - Create:
config/dot/quickshell/modules/quicksettings/HomeTile.qml - Create:
config/dot/quickshell/modules/quicksettings/PhoneControls.qml - Create:
config/dot/quickshell/modules/quicksettings/RecentExchange.qml - Create:
tests/quickshell/control-center-contract.sh - Modify:
config/dot/quickshell/config/Theme.qml - Modify:
config/dot/quickshell/modules/quicksettings/QuickSettings.qml - Modify:
config/dot/quickshell/modules/quicksettings/QuickSettingsPanel.qml - Modify:
config/dot/quickshell/modules/bar/StatusCluster.qml
Interfaces:
-
HomeControls.expanded: booland signaltoggleExpanded(). -
PhoneControls.expanded: booland signaltoggleExpanded(). -
Existing
expandedSectionremains the only expansion owner. -
StatusClusterremains one click target and readsKdeConnect.phoneReachable. -
Step 1: Write the failing panel contract
Assert source and live behavior:
rg -q 'readonly property int controlCenterWidth: 430' config/dot/quickshell/config/Theme.qml
rg -q 'readonly property int controlCenterTopGap: 2' config/dot/quickshell/config/Theme.qml
rg -q 'margins.top: Theme.barHeight + Theme.controlCenterTopGap' config/dot/quickshell/modules/quicksettings/QuickSettings.qml
rg -q 'HomeControls' config/dot/quickshell/modules/quicksettings/QuickSettingsPanel.qml
rg -q 'PhoneControls' config/dot/quickshell/modules/quicksettings/QuickSettingsPanel.qml
rg -q 'visible: KdeConnect.phoneReachable' config/dot/quickshell/modules/bar/StatusCluster.qml
Load ready fixtures, open Quick Settings, and assert hyprctl layers contains
qs-popover-quicksettings. Use IPC status to assert opening Home followed by
Phone leaves only Phone expanded.
- Step 2: Run the contract and verify RED
Run tests/quickshell/control-center-contract.sh.
Expected: FAIL on the missing geometry tokens and components.
- Step 3: Add geometry and composition
Add exact tokens:
readonly property int controlCenterWidth: 430
readonly property int controlCenterTopGap: 2
Set both Quick Settings implicit widths to Theme.controlCenterWidth, set top
margin to Theme.barHeight + Theme.controlCenterTopGap, and retain the current
right margin. On open, call KdeConnect.refresh() and
HomeAssistant.refresh() before restarting the entrance animation.
Place Home after audio/brightness and Phone after Home, before the footer.
Reuse expandedSection values home and phone; do not introduce nested
popovers.
- Step 4: Implement the Home components
HomeTile.qml is a button-like rectangle with entity, busy, and
onActivated. It maps light to a lamp glyph, uses Theme.warn at low alpha
only when entity.active, and shows Unavailable for !entity.available.
HomeControls.qml uses a four-column resting grid from
HomeAssistant.visibleEntities. Expanded mode uses a single-column list of all
HomeAssistant.entities. Include deterministic loading, setup,
authentication-required, unreachable/stale, and empty states. Retry calls
HomeAssistant.refresh(); Open calls HomeAssistant.open().
- Step 5: Implement the Phone components and chooser
PhoneControls.qml renders the approved identity-first card. Show only actions
included in preferredPhone.actions. Use QtQuick.Dialogs.FileDialog with a
single existing-file selection. onAccepted passes the selected local file path
as a distinct argument to KdeConnect.sendFile(); onRejected does nothing.
Disabled/offline copy is Not nearby. Missing KDE Connect copy is
KDE Connect unavailable. RecentExchange.qml is visible only when the
service has a real current-session exchange.
- Step 6: Add reachable-phone status and visual states
Append one phone glyph to the existing StatusCluster only when
KdeConnect.phoneReachable; it has no MouseArea and inherits the parent Pill's
activation. Use Theme.cyan for reachable, no disconnected glyph, and no
animation.
- Step 7: Restart Quickshell and run the panel contract GREEN
Run tests/quickshell/control-center-contract.sh and inspect the fresh shell
journal for error, warning, ReferenceError, and TypeError entries from
the current Quickshell start.
Capture resting, Home-expanded, Phone-offline, transfer-active, and Home-stale
fixture screenshots to /tmp; do not commit screenshots.
- Step 8: Commit Task 4
git add config/dot/quickshell/config/Theme.qml \
config/dot/quickshell/modules/quicksettings \
config/dot/quickshell/modules/bar/StatusCluster.qml \
tests/quickshell/control-center-contract.sh
git commit -m "Build the Panama Control Center"
Task 5: Documentation and full verification
Files:
- Modify:
config/dot/hypr/DESKTOP-PARITY.md - Modify:
config/dot/hypr/README.md
Interfaces:
-
Consumes the complete helper, service, UI, and IPC contracts from Tasks 1–4.
-
Produces an auditable verification result and an up-to-date remaining roadmap.
-
Step 1: Update desktop documentation
Mark Home Assistant favourites and KDE Connect phone integration Live. Document
the 430-pixel Control Center, two-pixel bar attachment, environment/keyring
configuration precedence, BlueBubbles messaging boundary, and current iOS
capabilities. Remove those two items from System UI still worth adding while
retaining printers and the external RustDesk lock-screen test.
- Step 2: Run every focused test
python3 tests/quickshell/kdeconnect_bridge_test.py
python3 tests/quickshell/home_assistant_bridge_test.py
tests/quickshell/kdeconnect-helper-contract.sh
tests/quickshell/home-assistant-helper-contract.sh
tests/quickshell/control-center-services-contract.sh
tests/quickshell/control-center-contract.sh
Expected: every command exits 0; live contracts print counts only.
- Step 3: Run full Panama verification
Run hyprctl configerrors and require empty output. Run every executable
tests/quickshell/*contract.sh in sorted order and require zero failures.
Restart Quickshell from a clean process, verify all IPC targets return, and
inspect only the new process's journal for QML errors or warnings.
- Step 4: Perform read-only live checks
Verify the Control Center reports one reachable phone with the confirmed action names and seven configured Home favourites without printing identities. Open and close the panel, expand Home and Phone, and confirm Wi-Fi, Bluetooth, audio, Focus, Caffeine, Night Light, settings, power, Escape, and outside-click remain usable. Do not trigger an external-device action.
- Step 5: Review the final diff against the spec
Check every Goal, Non-goal, Interaction, Security, Failure, Testing, and
Documentation requirement in
docs/superpowers/specs/2026-08-17-panama-control-center-design.md. Run
git diff --check and confirm no secret-shaped value, live entity response,
device ID, IP address, or absolute personal file path entered tracked content.
- Step 6: Commit documentation and push the feature
git add config/dot/hypr/DESKTOP-PARITY.md config/dot/hypr/README.md
git commit -m "Document the Panama Control Center"
git push origin main
- Step 7: Offer the three explicit live action checks
Ask the user to choose actions from the finished Control Center: toggle one Home light, Ring the iPhone, send clipboard text, or send a harmless chosen file. Treat the visual state and resulting external device behavior as the acceptance signal; do not execute an external action from diagnostics or automation.