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.