Files
Mark-VIII/docs/superpowers/specs/2026-08-11-virtual-pager-dock-design.md
T
2026-08-11 20:24:24 -07:00

186 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Virtual Pager Dock — Design Spec
- **Date:** 2026-08-11
- **Status:** Approved
- **Owner:** WiFi Pineapple Pager expansion project
- **Applies to:** `payload/user/general/pager-webui/www/` (front-end only) and
`scripts/dev_proxy.py` (dev config)
## 1. Goal
Add a **Virtual Pager** button in the topbar next to the existing **Terminal**
button in the Pager WebUI (`http://172.16.52.1:8080/`). Clicking it opens a
bottom-docked panel that shows a live, interactive replica of the Pager device:
the physical-device graphic with the real-time screen in the middle and
clickable directional/A/B buttons — matching the virtual pager view of the
Pager's stock UI at `http://172.16.52.1:1471/`.
Behavior mirrors the existing Terminal dock: open/close toggling, docked panel,
connect/disconnect of WebSockets on open/close.
## 2. Stock UI Reference (verified 2026-08-11)
Source: `http://172.16.52.1:1471/` (captured to
`%TEMP%\opencode\pager-virtual-pager.html`, 141,707 bytes).
### 2.1 Connections
- Screen: `ws://<host>:1471/api/pager/display/screen.ws` — streams binary RGBA
framebuffer frames. Requires the daemon `AUTH_<serverid>` cookie; returns
`401 Unauthorized` without it.
- Keys: `ws://<host>:1471/api/pager/input/keys.ws` — text frames; same cookie.
- Both handshakes verified returning `101 Switching Protocols` with a valid
`AUTH_<serverid>=<token>` cookie.
- Cookies are host-scoped (not port-scoped), so a browser that has logged into
`172.16.52.1:8080` already sends the cookie to `172.16.52.1:1471`. This is the
same mechanism the existing Terminal WS uses (direct `:1471` connection, no
backend relay), and requires **no backend changes**.
### 2.2 Screen framebuffer
- Resolution `480 × 222`, stride `480 * 4 = 1920`, expected
`1920 * 222 = 426,240` bytes per frame (4 bytes/pixel RGBA).
- Client renders each frame: copy RGBA into a hidden 480×222 canvas
(`createImageData`/`putImageData`), `canvas.toDataURL('image/png')` into the
`<img id="pager">` screen element.
### 2.3 Buttons / keys
Button image click sends these strings over the keys WS:
| Control | Sent key |
|-----------|------------------|
| LEFT | `ArrowLeft` |
| UP | `ArrowUp` |
| RIGHT | `ArrowRight` |
| DOWN | `ArrowDown` |
| A | `Enter` |
| B | `Escape` |
Physical keyboard: when the pager screen is focused (or body, with no other
control focused) the stock UI sends `e.key` on keydown (no repeats). Buttons
show a brief `.pressed` feedback (brightness filter).
### 2.4 Device graphic
`<table id="pager_ui">` (745 × 531) built from 24 image slices +
`spacer.gif`, button images `LEFT.png`, `UP.png`, `RIGHT.png`, `DOWN.png`,
`A_Button.png`, `B_Button.png`, and a 480×222 screen surface with hidden canvas
and a `#pager_error` overlay. Images are static and fetchable without auth from
`http://172.16.52.1:1471/images/`. Identical files already exist in
`wifipineapplepager/payloads/library/user/remote_access/nautilus/www/images/`
(sizes match byte-for-byte); they will be copied from there and hash-verified
against the stock UI.
Scaling: the stock UI scales the graphic with CSS `zoom` (`scale =
min(1, availableWidth / 745)`), `transform-origin: top center`.
## 3. Design
### 3.1 index.html
- Add `#pager-btn` ("Virtual Pager", `btn ghost`) next to `#terminal-btn`.
- Add `#pager-panel` dock after `#terminal-panel`, mirroring its structure:
- `#pager-bar` — title "Virtual Pager" + `#pager-close` button.
- `#pager` content area containing the pager graphic table with the live
screen, hidden canvas, and error/retry overlay.
- Load `js/pager.js` after `js/terminal.js`.
### 3.2 app.css
- `#pager-btn`: same style as `#terminal-btn`; `.active` state for both.
- `#pager-panel`: fixed bottom dock like `#terminal-panel` but `height: 540px`;
reuses the `term-up` slide-in animation and a shared `.dock` utility where
practical. Content centered; overflow hidden.
- Pager graphic scale-to-fit: a wrapper whose `zoom`/transform scales the
745×531 graphic to the available panel width, never upscaling
(`scale = min(1, availableWidth / 745)`), per §2.4.
- `.pager-btn` cursor/`user-select`/pressed feedback (stock rules).
- `#pager_error` overlay styles (centered, dark overlay, Reconnect button).
### 3.3 js/pager.js (new, mirrors js/terminal.js)
Module `Pager` with:
- `SCREEN_WIDTH = 480`, `SCREEN_HEIGHT = 222`, `FB_STRIDE = 1920`.
- `ensure()` — lazily grab `#pager-panel`, canvas, `<img id="pager">`, build the
graphic table (static innerHTML from bundled images), wire button clicks.
- `toggle()` — show/hide `#pager-panel`, toggle `#pager-btn.active`,
connect/disconnect.
- Screen WS: binary `event.data``new Uint8Array(buffer)`
`renderRGBAFrame(bytes)` (reject frames `< FB_STRIDE * SCREEN_HEIGHT`).
On error/close: hide screen, show error overlay with Reconnect.
- Keys WS: `sendKey(name)` helper; button clicks send the §2.3 strings.
- Keyboard: a `keydown` listener active only while the dock is open; skips when
focus is in an INPUT/TEXTAREA/SELECT or the terminal; captures
ArrowLeft/ArrowUp/ArrowRight/ArrowDown/Enter/Escape (preventDefault on the
arrows to stop page scroll) and sends them via keys WS. Letter shortcuts
(D/C/P/R/L/M) and backtick (terminal toggle) keep working — backtick closes
the pager dock via the replace-on-open behavior.
- `disconnect()` closes both WS.
- `window.addEventListener('resize')` re-applies scale-to-fit while open.
### 3.4 app.js
- Wire `#pager-btn` and `#pager-close` to `Pager.toggle()` (guarded with
`typeof Pager === 'undefined'` like the terminal).
- **Replace-on-open:** a small helper `showDock(name)` that, when opening
`pager`, calls `Term.close()` if `Term` is defined and its panel is open, and
vice versa. Refactor the terminal wiring to go through `showDock('terminal')`
so both directions use the same rule. (`Term` gains a `close()`/`isOpen()`
helper in `terminal.js`.)
- Keyboard shortcut `g` (or none) is not required; the button is the entry
point. Backtick toggles the terminal and closes the pager dock (per
replace-on-open).
### 3.5 config.js / dev_proxy.py
- `config.js`: add `pagerScreenWs: ''`, `pagerKeysWs: ''`.
- `app.js`: read `cfg.pagerScreenWs || ('ws://' + location.hostname +
':1471/api/pager/display/screen.ws')` and the keys equivalent; expose as
`App.pagerScreenWs` / `App.pagerKeysWs`.
- `dev_proxy.py` `_serve_dev_config`: emit both fields pointing at the pager
host (same shape as `terminalWs`).
### 3.6 Assets
- Copy from the nautilus payload into `www/assets/pager/`:
`virtual_pager_01..24.png`, `LEFT.png`, `UP.png`, `RIGHT.png`, `DOWN.png`,
`A_Button.png`, `B_Button.png`, `spacer.gif`.
- Hash-verify each against the stock UI (`http://172.16.52.1:1471/images/…`)
during implementation; if any differ, use the stock bytes instead.
### 3.7 Error handling
- Screen WS error/close: show `#pager_error` ("Lost connection to virtual
pager") with a Reconnect button that calls `connect()`.
- Keys WS is best-effort: if not OPEN, button presses are ignored (stock
behavior).
- If WS construction throws (unreachable daemon), write a message into the
error overlay rather than throwing in app.js.
## 4. Testing
- **Unit (Python):** none required — no backend changes.
- **Static review:** `pager.js` frame renderer bounds check; button→key mapping
table; keyboard guard conditions; replace-on-open logic.
- **On-device smoke (per §8 of the main webui spec):**
1. Log in at `http://172.16.52.1:8080/`; Virtual Pager button present next to
Terminal.
2. Open Virtual Pager: dock slides up, screen streams live, graphic scales to
fit width.
3. Click LEFT/UP/RIGHT/DOWN/A/B — screen responds; physical arrow/Enter/Escape
also drive it; typing in an input/terminal does not.
4. Open Terminal while Pager open → Pager dock closes (replace-on-open) and
vice versa.
5. Kill/reconnect network to the daemon → error overlay + Reconnect recovers.
6. Backtick still toggles the terminal and closes the Pager dock.
## 5. Out of scope
- The stock page's embedded shell terminal and terminal-shortcut grid (the
WebUI already has a Terminal dock).
- The Pager Skinner / virtual-pager theme features.
- Any backend relay for the pager WS (direct `:1471` connection, same as the
Terminal).