aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers/specs/2026-11-09-deck-it-design.md
blob: 3fbc7c882a826c8d308ee5bafdc427c7a8e7e15e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# deck-it — design

**Date:** 2026-11-09
**Status:** approved (design), pending implementation plan

## Intent

An always-on tablet dashboard, stream-deck, and second monitor for this desktop:
Slackware-current, Hyprland with the Lua config (hypr-theme), UDT-themed. It
reuses tabdeck's plumbing (Python stdlib HTTP server, a web page in the tablet's
browser over `adb reverse`, and a virtual monitor streamed with wayvnc) but
replaces tabdeck's fixed stats/keys/mull surface with a module system, and swaps
tabdeck's Arch/systemd assumptions for Slackware's.

## Goals

1. Always-on touch control on the Android tablet over USB; Wi-Fi optional.
2. Second monitor: virtual Hyprland output + wayvnc, shown in AVNC.
3. Stream-deck control of OBS: recording, scenes, audio channels, transitions,
   sources.
4. Read-only dashboards: system stats, music/volume, windows/workspaces/launch,
   claude-code + opencode usage (via llm-cost-dashboard), sb-builder progress.
5. Modular: a new app is a new module, with no core edits.
6. UDT-themed: the palette is read, never restated; a scheme switch recolours
   the tablet.

## Non-goals

- Not a replacement for quickshell / rofi-scripts. They stay for the static
  desktop approach; this is the fast always-on surface. Existing commands
  (playerctl, wpctl, hyprctl, rofi scripts) are reused, not reimplemented.
- Not a general automation engine. Only declared actions run.
- No cloud, no account, no app-store install.
- Assistant control: deferred. A module slot is reserved.

## Anti-goals (failures to avoid)

- The tablet can ever run an arbitrary command.
- A scheme or wallpaper change leaves the tablet stale or desynced.
- Adding a module means editing the core server.
- Anything requires systemd.

## Constraints

- Slackware-current: SysV init, eudev, no systemd. Hyprland with the Lua config.
- Python stdlib only, with one exception: `websocket-client` for obs-websocket.
- Tablet: Android + AVNC + a Chromium browser.
- UDT is the single source of theme truth.

## Module contract

A module contributes:
- **Backend** — a `make(cfg) -> Module` factory returning a `Module` with a
  `state()` callable (GET) and an `actions` dict of allow-listed verbs (POST).
  The client sends `{module, action, ...}`; the server maps ids to code and the
  client never sends a command.
- **Frontend** — `static/modules/<name>.js`, a plain object
  `{title, interval, mount(el), update(state)}` rendered into the shell grid.

The core provides the HTTP server, auth/trust, the adb tunnel, theming, and the
module registry. Modules are enabled in config under `[modules]`.

## Theming

At startup `deckit/theme.py` reads:
- `~/.config/rofi/udt/palette.rasi` — the structural palette, entries
  `name: #rrggbb[aa]` (base, mantle, crust, surface0-2, text, subtext0-1,
  overlay0-2, red, green, yellow, teal, blue, lavender, peach, mauve, termblue,
  scrim).
- `~/.cache/udt/udt-accent.rasi` — `* { accent: #rrggbb; }`.

It serves `/theme.css` with `:root { --udt-<name>: #...; --udt-accent: #...; }`.
The frontend styles only through those variables. When UDT regenerates a scheme
(`install.sh`) or accent (`udt-accent` on wallpaper change), the server re-reads
on an mtime poll and a page refresh picks it up. A missing palette falls back to
a built-in Catppuccin Macchiato set so the page always renders. Fonts: Noto Sans
for UI, Inconsolata Nerd Font Mono for numbers.

## Slackware wiring

No systemd. `deck-it` is a bash launcher with `start` (default), `stop`, `pair`,
`wifi`, `install`, `uninstall`, `env`, `setup-display`.

- Start from Hyprland via hypr-theme's `exec-once` (the compositor must be up for
  the virtual output and wayvnc); `install` prints the exact line to add.
- Plug-in autostart: an eudev rule in `/etc/udev/rules.d/70-deck-it.rules` with a
  non-blocking `RUN+` that setsids `deck-it start`. A login-time check covers an
  already-connected tablet.
- No `loginctl`; lock/leave stays in the existing rofi powermenu.

## Second monitor

Reuse tabdeck's `setup_display`: create a headless Hyprland output sized to the
tablet (`hyprctl output create headless`, then the Lua `hl.monitor` table), run
wayvnc with TLS + a random password in a private dir, and tunnel the VNC port to
the tablet with `adb reverse tcp:5900 localfilesystem:<run>/vnc.sock`. AVNC is
opened with the login in the link. wayvnc works on Hyprland, which implements
the wlroots screencopy and virtual-input protocols it needs.

## Security

Reuse tabdeck's model unchanged in spirit:
- Unix socket in a 0700 `$XDG_RUNTIME_DIR/deck-it`, exposed only by `adb reverse`.
- Token cookie (HttpOnly, SameSite=Strict) unlocks the API.
- Host/Origin checks block DNS rebinding and cross-site writes.
- Client sends ids, never commands. OBS's password and the OBS connection stay
  server-side.

## Per-module specifics

| Module | Source | Actions |
|---|---|---|
| stats | /proc, sysfs (Intel xe: temp, fan, VRAM) | none (read) |
| music | playerctl + wpctl | play/pause, prev/next, volume ±/mute |
| windows | hyprctl clients/activewindow/workspaces | workspace switch, focus/close, launch (config commands) |
| obs | obs-websocket v5 client | set scene, toggle record/stream, input volume/mute, set/trigger transition, toggle source |
| llm | relay llm-cost-dashboard `/api/summary`, `/api/today`, `/api/claude_limits`, `/api/claude_session`, `/api/opencode_agents` | none (read) |
| sbbuilder | `ssh <host> sbb-remote jobs` (read-only) | none (read) |
| assistant | deferred | — |

## Build order

1. Core — scaffold, config, theming, module registry, server + auth, frontend
   shell, launcher, second monitor.
2. First modules — stats, music, windows (proves the contract).
3. OBS — the stream-deck.
4. Read-only feeds — llm + sbbuilder.
5. Assistant — deferred.

## Open calls (settled in the plan)

- OBS client: `websocket-client` is the single non-stdlib dependency.
- sb-builder bridge: `sbb-remote jobs` over ssh; no change to sb-builder.