From 8a9d89bed51e49ecee9a4bdf76f8f8bb9fa76e3e Mon Sep 17 00:00:00 2001 From: Mortdecai Date: Fri, 24 Apr 2026 19:56:48 -0400 Subject: [PATCH] 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). --- ...0-toolbar-workspace-dark-refresh-deploy.md | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 .claude/handoffs/2026-04-24-195210-toolbar-workspace-dark-refresh-deploy.md diff --git a/.claude/handoffs/2026-04-24-195210-toolbar-workspace-dark-refresh-deploy.md b/.claude/handoffs/2026-04-24-195210-toolbar-workspace-dark-refresh-deploy.md new file mode 100644 index 0000000..a2667d6 --- /dev/null +++ b/.claude/handoffs/2026-04-24-195210-toolbar-workspace-dark-refresh-deploy.md @@ -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 `` 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.` +- [x] Backed up old `static/toolbar.js` to `static/.backup/toolbar.js.` +- [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 134–146). 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.