docs: add session handoff for toolbar refresh deploy

Captures: what shipped (Workspace dark refresh + compose bar in
static/toolbar.js, deployed to /opt/sethmux/), the manual-deploy gotcha
that left 3 prior fixes undeployed for a month, the Caddy-not-ttyd
serving path for static assets, and the mobile acceptance checklist
that's the only remaining gate.

Validator score: 88/100 (READY).
This commit is contained in:
Mortdecai
2026-04-24 19:56:48 -04:00
parent b8a7810bfd
commit 8a9d89bed5
@@ -0,0 +1,170 @@
# Handoff: sethmux mobile toolbar — Workspace dark refresh + compose bar deploy
## Session Metadata
- Created: 2026-04-24 19:52:10
- Project: /home/claude/bin/sethmux
- Branch: main
- Session duration: ~30 min
### Recent Commits (for context)
- b8a7810 chore: archive design handoff bundle for toolbar refresh
- 2b375b0 chore: add detect-secrets baseline
- 3b06ce7 fix: keep tmux selection alive after mouse-drag in copy mode
- 8e70875 feat: Workspace dark refresh + mobile compose bar
- 451a055 docs: document mobile autocomplete-disable fix
- 6a27828 chore: ignore .backup/ directories
## Handoff Chain
- **Continues from**: None (fresh start)
- **Supersedes**: None
## Current State Summary
Shipped the design handoff in `claude-design/SethMux_4-24-26.zip`: re-skinned the mobile toolbar to Google Workspace dark vocabulary (keeping `#D35400` accent) and added a third "compose bar" row that gives Gboard/iOS keyboards a real `<input>` to operate on, sidestepping xterm.js's per-keystroke input model. Replaced `static/toolbar.js` with the design's byte-for-byte version, deployed to `/opt/sethmux/toolbar.js`, and pushed 6 clean conventional commits to git.sethpc.xyz/Seth/sethmux. The deployed file is live; **browser-side acceptance testing on a phone is the only remaining check** — Seth should reload mux.sethpc.xyz on a mobile device, hard-refresh past browser cache, and walk the acceptance checklist in `claude-design/design_handoff_sethmux_toolbar/README.md`.
## Codebase Understanding
### Architecture Overview
Sethmux is `ttyd` + `tmux` glued together with a hand-rolled mobile toolbar. The serving stack:
```
mobile browser → Caddy (mux.sethpc.xyz, auth via Authentik) →
/toolbar.js, /manifest.json, /icon-*.png → file_server from /opt/sethmux/
/api/* → reverse_proxy localhost:7684 (notify-server.py)
/ → reverse_proxy localhost:7683 (ttyd)
```
ttyd serves only `--index /opt/sethmux/index.html` and the websocket — **toolbar.js is served by Caddy directly**, NOT ttyd. That's why a `cp` to `/opt/sethmux/toolbar.js` is enough to deploy (no daemon restart needed; static asset, ETag-revalidated).
### Critical Files
| File | Purpose | Relevance |
|------|---------|-----------|
| `static/toolbar.js` | The mobile toolbar — buttons, compose bar, key sending, selection mode | The entire UI surface for mobile. **The only file that ships from the design bundle.** |
| `/opt/sethmux/toolbar.js` | Deployed copy (root-owned, served by Caddy) | Manual `cp` from `static/`. Currently matches the design hash `6aee4388...`. |
| `claude-design/design_handoff_sethmux_toolbar/README.md` | Spec — colors, geometry, behaviors, acceptance checklist | Source of truth if you need to verify what was supposed to ship. |
| `static/index.html` | ttyd custom index (xterm.js wrapper) | NOT changed this session. Keep in mind for future toolbar↔terminal coupling. |
| `config/tmux.conf` | tmux session config | One drag-select fix landed this session. |
| `systemd/sethmux.service` | ttyd unit on port 7683, runs as user `rdp` | Don't restart for static-asset changes; only for ttyd/tmux config changes. |
| `DECISIONS.md` | Project-local decision log (created this session) | **Read first** when resuming — has the rationale for the compose-bar design and what was rejected. |
| `AUTOCOMPLETE_FIX.md` | Pre-existing rationale for `disableMobileAutocomplete()` | Still load-bearing; the new toolbar.js preserves that fix verbatim. |
| `.secrets.baseline` | detect-secrets allowlist | Required by global pre-commit hook to avoid SRI hashes being flagged as secrets. |
### Key Patterns Discovered
- **Send-to-stdin chain in `toolbar.js#send()`** — tries `term._core.coreService.triggerDataEvent(k)` first, falls back to `term._core._onData.fire(k)`, then `term.input(k)`. This shape is the canonical way to inject keystrokes into a hosted xterm.js when you don't own its event loop. Preserved in the new design.
- **MutationObserver for late-binding xterm DOM** — xterm.js creates `.xterm-helper-textarea` after page load. The observer is the only reliable hook to set `autocomplete=off` on it. Same observer also runs `relayout()` so terminal pane height tracks toolbar height when the compose row toggles.
- **Sethmux sits behind Authentik** — unauthenticated curl gets 302'd. So *automated* HTTP checks against `mux.sethpc.xyz` need an auth token; for static-asset deploys, file-hash check on disk is the practical verification.
## Work Completed
### Tasks Finished
- [x] Read context/handoff/project files (`claude-design/SethMux_4-24-26.zip` + project state)
- [x] Verified design preserves all recent fixes (tabIndex=-1, _core data-event chain, helper-textarea hardening)
- [x] Replaced `static/toolbar.js` with design version (hash `6aee4388...`)
- [x] Deployed to `/opt/sethmux/toolbar.js` with backup of old file at `/opt/sethmux/.backup/toolbar.js.<ts>`
- [x] Backed up old `static/toolbar.js` to `static/.backup/toolbar.js.<ts>`
- [x] Created project-local `DECISIONS.md` with the design rationale + rejected alternatives
- [x] Added `.gitignore` entry for `.backup/`
- [x] Generated `.secrets.baseline` to allowlist SRI integrity hashes
- [x] 6 clean commits pushed to git.sethpc.xyz/Seth/sethmux
### Files Modified
| File | Changes | Rationale |
|------|---------|-----------|
| `static/toolbar.js` | Full replacement with design version | Workspace dark visual + compose bar |
| `/opt/sethmux/toolbar.js` | `cp` from `static/` | Deploy |
| `config/tmux.conf` | `MouseDragEnd1Pane`: `copy-selection-and-cancel``copy-selection` | Keep copy mode active after drag |
| `.gitignore` | Added `.backup/` | Don't track local backups |
| `DECISIONS.md` | Created | Project-local decision log per global convention |
| `AUTOCOMPLETE_FIX.md` | Committed (was untracked) | Documents the helper-textarea fix preserved in new toolbar |
| `.secrets.baseline` | Created | Allowlist SRI hashes for pre-commit hook |
| `claude-design/` | Added (8 files) | Archive design handoff bundle in-repo |
### Decisions Made
| Decision | Options Considered | Rationale |
|----------|-------------------|-----------|
| Compose bar instead of patching xterm.js IME handling | Fork xterm.js to handle `inputType: "insertReplacementText"` events | Compose bar is smaller, preserves upstream xterm.js, one-shot string send is more obviously correct than interleaved IME state machine |
| Manual deploy via `cp` | Auto-deploy on push, rsync watcher | Static asset; explicit `cp` keeps in-progress edits from accidentally shipping |
| Six separate commits, not one squash | Single bundled commit | Per Gitea convention: no batching unrelated concerns. Each commit is a record. |
| Baseline SRI hashes via `.secrets.baseline` | Inline `pragma: allowlist secret` per line, or `--no-verify` bypass | Baseline is precise (only allowlists *current* findings) and discoverable in repo. Per global "escalation brake" rule, never `--no-verify`. |
## Pending Work
## Immediate Next Steps
1. **Mobile browser acceptance test** — Seth needs to load `mux.sethpc.xyz` on his phone, hard-refresh (or new private tab) to bust the toolbar.js cache, and walk the checklist in `claude-design/design_handoff_sethmux_toolbar/README.md` (lines 134146). Specifically: confirm Workspace-dark colors render, `Type` button toggles compose row, Enter in compose flushes + clears, Save button fills green for 1500ms, Sel button toggles to "Done" with terminal dim, no console errors.
2. **Test mobile autocorrect end-to-end** — open compose bar on Gboard or iOS, type a sentence using swipe + autocorrect predictions, hit Send. The whole point of the work is that this should now feel native; if it doesn't, escalate to the "nuclear option" deferred in DECISIONS.md (`inputmode="none"` on helper textarea + on-screen keyboard toggle).
3. **Decide on `rdp_guac.txt`** — 200KB Claude Code session-transcript dump, untracked. Either move it under `docs/reference/` if useful, or `rm` it. Not committed deliberately.
### Blockers/Open Questions
- [ ] None blocking. Acceptance test is the only gate.
### Deferred Items
- **Forking xterm.js for `insertReplacementText` handling** — rejected; compose bar wins. See DECISIONS.md.
- **`inputmode="none"` on helper textarea** — deferred; only revisit if compose bar doesn't fix the autocorrect UX in real-world testing.
- **Auto-deploy from `static/` to `/opt/sethmux/`** — explicit `cp` stays for now; revisit if deploy drift becomes a recurring pain.
## Context for Resuming Agent
## Important Context
- **Deployments are MANUAL.** Editing `static/toolbar.js` does not ship until `sudo cp static/toolbar.js /opt/sethmux/toolbar.js`. The Mar 26 → Mar 28 drift this session uncovered (3 toolbar fixes committed but never deployed) suggests this footgun has been hit before.
- **toolbar.js is served by Caddy from `/opt/sethmux/`, not by ttyd.** The systemd unit's `--index /opt/sethmux/index.html` only sets ttyd's index page; everything else under `/opt/sethmux/` is `file_server`'d by Caddy. Consequence: no daemon restart on toolbar changes.
- **The pre-commit hook (`detect-secrets-hook`, configured at `~/.config/git/hooks/pre-commit`) flags SRI hashes as base64 high-entropy strings.** When adding new HTML with `integrity="sha384-..."` script tags, regenerate baseline with `detect-secrets scan --all-files --exclude-files '\.git/|\.secrets\.baseline$' > .secrets.baseline` before committing. NEVER use `--no-verify` — global rule.
- **The compose bar and the helper-textarea hardening are complementary, not redundant.** Compose bar = autocorrect-friendly typing surface. Helper-textarea hardening = prevents Gboard from corrupting per-keystroke chord/arrow taps. Both stay.
- **Authentik blocks unauthenticated curl** to mux.sethpc.xyz. To verify deploys via HTTP, you'd need an auth token; otherwise, file-hash on disk is the verification path.
### Assumptions Made
- The design's `toolbar.js` is high-fidelity per its README, so byte-for-byte replacement is correct (vs. cherry-picking changes).
- Browser cache will bust on hard-refresh; no cache-busting query string was added.
- `rdp_guac.txt` is unrelated session content (first 2 lines confirmed it's a Claude Code transcript header), so it stays out of the commits.
### Potential Gotchas
- **Don't restart `sethmux.service` for static-asset changes** — pointless, and it'll boot users out of their tmux session.
- **`/opt/sethmux/` is root-owned** — deploys need `sudo cp`. Ownership is intentional (ttyd runs as user `rdp`, not `claude`).
- **`hal-terminal.service` (port 7685) is a separate fork** with its own `static/` at `/home/claude/bin/hal/terminal/static/`. Don't confuse it with sethmux.
- **The `.backup/` dirs are local-only** (gitignored). Don't expect them to round-trip across hosts.
- **DECISIONS.md is project-local**; cross-cutting decisions go to `~/bin/DECISIONS.md`. Don't conflate.
## Environment State
### Tools/Services Used
- `gitea` CLI — pushes to `https://git.sethpc.xyz/Seth/sethmux.git` with token from `~/.config/gitea/token`
- `detect-secrets` — at `/home/claude/.local/bin/detect-secrets`, used for `.secrets.baseline` regeneration
- `sudo cp` — required for `/opt/sethmux/` writes
- `systemctl` — only used to verify `sethmux.service` and `hal-terminal.service` running; nothing was restarted
### Active Processes
- `sethmux.service` (ttyd on `:7683`, user `rdp`, attaches `tmux -t sethmux`) — running, **untouched** this session
- `sethmux-notify.service` (notify-server.py on `:7684`) — running, untouched
- `hal-terminal.service` (separate fork on `:7685`) — running, untouched
### Environment Variables
- `HOMELAB_PASSWORD` — used elsewhere in `~/bin`, not relevant to this session
- No new env vars added
## Related Resources
- Design spec: `claude-design/design_handoff_sethmux_toolbar/README.md` — has the acceptance checklist
- Design previews (visual reference, not production): `claude-design/design_handoff_sethmux_toolbar/preview-{desktop,mobile}.html`
- Decision rationale: `DECISIONS.md` (project-local)
- Helper-textarea fix rationale: `AUTOCOMPLETE_FIX.md`
- Repo: https://git.sethpc.xyz/Seth/sethmux
- Live URL (auth-gated): https://mux.sethpc.xyz
---
**Security Reminder**: Before finalizing, run `validate_handoff.py` to check for accidental secret exposure.