Files
Panama/docs/superpowers/specs/2026-08-17-panama-control-center-design.md
T
Gabriel Brown d96863b687 Convert British spellings to American across the repo
colour -> color, behaviour -> behavior, centre -> center, favourite ->
favorite, and about twenty other pairs, applied consistently across
comments, docs, error/UI copy, and a handful of QML identifiers that
used the British spelling as their actual name: SystemSettings'
serialiseValue/serialiseTable/normaliseGradient, Displays'
normaliseModes, Wallpaper's normalisePolicy, SettingsBackup's
serialiseHomeState, DateTime's ntpSynchronised property, Clipboard's
_normalise helper, and ShortcutCapture's cancelled signal (with its
onCancelled handler in ShortcutsPage.qml). Every call site and the two
tests that assert on the literal source text (settings-ownership and
settings-backup-live contracts) were updated in lockstep.

Left untouched: config/dot/espanso/match/packages/misspell-en/ is a
vendored third-party autocorrect dictionary -- its entries are typo
corrections, not our prose, and rewriting them would fight the
package's own purpose (and any future re-sync from upstream).

The already-American `favorites` property (Home page pinned
accessories) was never actually misspelled -- only nearby comments and
error strings said "favourites" -- so no data migration was needed
there.

Claude-Session: https://claude.ai/code/session_01E6TJUAh41HaP25MVHWkhRZ
2026-08-19 08:07:55 -04:00

13 KiB

Panama Control Center design

Summary

Panama will expand the existing top-right Quick Settings popover into a complete Control Center. Wi-Fi, Bluetooth, Focus, Caffeine, Night Light, audio, account, settings, and power remain in place. Home Assistant favorites and lightweight iPhone continuity join that same surface instead of creating separate windows or permanent bar widgets.

The approved visual direction is A · Continuity Card, integrated into the Control Center. The panel keeps Panama's Prism glass and Tokyo Night Moon language, grows to 430 logical pixels, and begins two pixels below the bar. Home is a first-class control section; KDE Connect is deliberately smaller because the paired iPhone exposes only a useful subset of KDE Connect.

Goals

  • Make the top-right popover feel like a complete desktop Control Center rather than a small collection of connectivity toggles.
  • Toggle the existing Home Assistant favorites without opening a browser.
  • Preserve the user's seven enabled Home Assistant lights and use Home Assistant friendly names and live states.
  • Show the paired iPhone's real reachability and expose Send File, Send Clipboard, and Ring actions.
  • Represent phone transfers in Ongoing while active and as quiet recent exchanges after completion.
  • Keep credentials, Home Assistant responses, clipboard contents, and local file paths out of Git and shell logs.
  • Leave BlueBubbles as the messaging implementation rather than manufacturing an inferior KDE Connect messaging surface.

Non-goals

  • Panama will not add SMS or iMessage controls. BlueBubbles already owns that experience.
  • Panama will not show an iPhone battery percentage until the device reports a real value. The current KDE Connect battery interface returns -1.
  • Panama will not expose remote input, device locking, remote commands, or phone notifications when the paired iPhone does not advertise those capabilities.
  • Panama will not become a full Home Assistant dashboard or entity editor.
  • Panama will not store a Home Assistant token in tracked files, QML, fixtures, process arguments, or logs.
  • The first release does not pair or unpair KDE Connect devices inside Panama. KDE Connect's own pairing UI remains the safe handoff.

Verified system state

KDE Connect currently reports one paired iPhone. Its live D-Bus object exposes clipboard, find-my-phone, ping, share, and battery plugins. The battery plugin has no value, and the remote-input object exposes no callable control methods. Panama must derive action visibility from advertised capabilities rather than from generic KDE Connect marketing claims.

The existing GNOME Home Assistant extension points to the user's Home Assistant instance and has seven enabled light entities: all lights, kitchen lights, two individual lights, bedroom, living room, and hall. It stores the long-lived access token in Secret Service under token_string=user_token; it does not keep the token in dconf.

Interaction model

Clicking the existing right-side status cluster or pressing Super+S opens the same Control Center. No new global shortcut is added.

The resting panel is ordered by frequency and scope:

  1. Wi-Fi, Bluetooth, Caffeine, and Night Light toggle grid.
  2. Focus and audio controls.
  3. Home favorites.
  4. Phone continuity.
  5. User, Panama Settings, and power footer.

The panel starts two logical pixels beneath the bar instead of using the current double bar-gap offset. It stays right-aligned and retains outside-click and Escape dismissal. Its content scrolls when an expanded device list exceeds the usable monitor height; the layer-shell window itself never extends beyond the work area.

Only one detail section is expanded at a time. Wi-Fi, Bluetooth, sink, source, Home, and Phone all participate in the existing expandedSection state so the panel remains predictable.

Home section

The resting Home card shows the first four enabled favorites in their saved order. Each tile contains a domain-appropriate glyph, a friendly name, and a plain state such as On, Off, or Unavailable. Enabled lights use a quiet amber light treatment; Panama blue remains reserved for desktop selection and focus.

Clicking a toggleable tile calls Home Assistant and temporarily disables only that tile. The state is refreshed from Home Assistant after the request rather than being permanently changed optimistically. Success is reflected visually without a toast or Signal Glass event. A failure restores the last known state and displays a concise inline error inside the Home section.

The trailing N accessories action expands a full list of configured favorites inside the Control Center. No unconfigured Home Assistant entity is shown. An Open Home Assistant action launches the configured base URL.

If Home Assistant is unreachable, Panama retains the last good states for the current shell session, marks them stale, and offers Retry. If no URL or token is available, the section becomes one setup row rather than disappearing or breaking the rest of Control Center.

Phone section

The Phone card follows approved mock A at a smaller scale:

  • device silhouette, friendly name, and Nearby on Wi-Fi or Not nearby;
  • Send File, Send Clipboard, and Ring as compact icon actions;
  • one recent transfer row only when a real exchange occurred in the current shell session; and
  • a Device Settings handoff to Panama Settings connectivity controls.

The bar adds a small phone glyph only while at least one paired phone is reachable. It is status, not a second click target; the existing status cluster continues to open Control Center.

Send File opens a graphical file chooser. Confirming a file starts a KDE Connect share process. Canceling the chooser has no side effect and produces no event. The action accepts regular local files only; URLs and directories are not inferred from arbitrary text.

Send Clipboard tells KDE Connect to send the current clipboard through its own clipboard plugin. Panama never reads, stores, previews, or logs clipboard text for this action. Ring invokes find-my-phone only when that plugin is advertised.

Phone actions are disabled while the device is unreachable. Connection and disconnection remain ambient Status Events under the existing global curation policy. A completed transfer may appear as a quiet recent row and an important Signal Glass event; canceling or failing a transfer produces a concise warning. Ring and clipboard success do not produce global events.

Architecture

KDE Connect service

Add services/KdeConnect.qml as the shell-facing state owner. It exposes the paired devices, preferred phone, reachability, advertised actions, current transfer, and recent exchange. It supervises small allow-listed helper commands; UI components never assemble shell command strings.

Add scripts/panama-kdeconnect with a JSON contract:

  • status reports paired devices, reachability, type, and supported actions;
  • send-file DEVICE FILE validates a regular file and shares it;
  • send-clipboard DEVICE invokes the KDE Connect clipboard plugin; and
  • ring DEVICE invokes find-my-phone.

The helper uses kdeconnect-cli and the session D-Bus service. It emits no certificate fingerprints, IP addresses, clipboard contents, or file contents. Status refreshes when Control Center opens and on a low-frequency fallback timer. The existing DeviceEvents.qml reads this service instead of launching its own independent KDE Connect probe.

Transfer state is contributed to Ongoing.qml. A running share shows filename, device name, and a Cancel action when the child process can still be stopped. No fake percentage is shown because KDE Connect's public CLI does not provide reliable byte progress for this device.

Home Assistant bridge

Add scripts/panama-home-assistant, a small local bridge with four commands:

  • probe reports configuration and API availability without entity details;
  • snapshot returns only configured favorites and normalized state;
  • toggle ENTITY_ID calls the Home Assistant service for an allow-listed configured entity; and
  • open launches the configured Home Assistant URL.

Configuration resolution is intentionally layered:

  1. PANAMA_HOME_ASSISTANT_URL, PANAMA_HOME_ASSISTANT_TOKEN, and optional PANAMA_HOME_ASSISTANT_ENTITIES from the gitignored config/bash/env;
  2. the existing GNOME extension's hass-url and enabled-entity dconf values;
  3. the existing Secret Service item with token_string=user_token.

This preserves the current working setup without copying a token. Explicit Panama environment variables can replace the compatibility fallbacks later. The bridge sends the token only in the Home Assistant Authorization header and redacts request failures before returning them to QML.

Add services/HomeAssistant.qml to own normalized entities, loading and stale state, per-entity busy state, and inline errors. It refreshes when Control Center opens, after a toggle, and at a bounded interval while the panel is visible. It does not continuously poll while the panel is closed.

Control Center components

Keep QuickSettings.qml as the only layer-shell window and QuickSettingsPanel.qml as composition. Add focused presentation components under modules/quicksettings/:

  • ControlSectionHeader.qml for consistent quiet labels and trailing actions;
  • HomeControls.qml and HomeTile.qml;
  • PhoneControls.qml; and
  • RecentExchange.qml.

The panel width becomes a named Control Center geometry token rather than changing the shared popover width used by unrelated surfaces. Existing Wi-Fi, Bluetooth, audio, Focus, Caffeine, Night Light, footer, keyboard focus, and outside-click behavior remain intact.

Security and privacy

  • The existing Secret Service token remains the preferred compatibility source; no migration requires printing or copying it.
  • Environment configuration is allowed only in the already gitignored config/bash/env file.
  • Helpers never echo tokens, Authorization headers, clipboard text, entity response bodies, IP addresses, or full local file paths to logs.
  • Home Assistant commands accept only entity IDs present in the configured favorites snapshot.
  • KDE Connect device IDs and file paths are passed as separate process arguments, never interpolated into a shell command.
  • A user click is required for every Home toggle, phone ring, clipboard send, file chooser, and file transfer.

Failure behavior

  • KDE Connect absent: Phone shows KDE Connect unavailable with a Settings handoff; Control Center remains usable.
  • Paired phone offline: identity remains visible, actions disable, and no repeated disconnect toast is emitted.
  • File chooser canceled: no error and no history entry.
  • Transfer failed: Ongoing clears, recent history does not claim success, and a concise warning is shown.
  • Home Assistant unavailable: last good state remains visibly stale and Retry is offered.
  • Invalid or expired token: Home shows Authentication required without exposing the response body.
  • One malformed entity is omitted without suppressing healthy favorites.

Testing and verification

Focused automated coverage will prove:

  1. KDE Connect status normalization and capability filtering through synthetic command fixtures.
  2. File validation, allow-listed device actions, canceled chooser behavior, and transfer lifecycle without sending a real file.
  3. Home Assistant configuration precedence, response normalization, allow-listed toggles, authentication redaction, stale-state retention, and malformed-entity isolation against a local fake API.
  4. Control Center structure, exclusive detail expansion, Home/Phone empty and failure states, Ongoing contribution, status glyph visibility, and the two-pixel top attachment through contract tests.
  5. Fresh Quickshell construction with no QML errors or warnings and valid Hyprland configuration.
  6. A live read-only smoke test that reports only the paired/reachable phone count, supported-action names, Home Assistant availability, and configured entity count.
  7. Visual captures of resting, expanded Home, phone offline, transfer active, and Home Assistant failure states.

A real Ring, clipboard send, Home light toggle, or file transfer is not part of automated verification because each changes an external device. Those final checks require an explicit user action from the finished Control Center.

Documentation updates

  • Update config/dot/hypr/DESKTOP-PARITY.md to mark Home Assistant favorites and KDE Connect phone integration live.
  • Document Control Center behavior, credential resolution, and iOS limitations in config/dot/hypr/README.md.
  • Keep BlueBubbles listed as the messaging application and KDE Connect as the continuity/file-transfer layer.