Files
sethmux/.claude/handoffs/2026-05-03-221356-toolbar-deploy-confirmed-mobile-only-visual.md
Mortdecai 345bf5ea15 docs: add continuation handoff — toolbar deploy confirmed live, visual gated to ≤900px
Closes the open gate from 2026-04-24-195210 handoff. Captures:
- two-host deploy topology trap (steel141 vs caddy CT) and corrected workflow
- viewport-gated visual: toolbar is display:none above 900px, so desktop
  reloads were byte-for-byte identical regardless of toolbar.js version
- diagnostic chain that ruled out cache, service worker, and CDN before
  finding the viewport gate
- remaining work: real-device mobile acceptance test only

Validator score: 90/100 (READY).
2026-05-03 22:17:43 -04:00

179 lines
14 KiB
Markdown
Raw Permalink 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.
# Handoff: sethmux toolbar deploy confirmed live — visual gated to ≤900px viewport
## Session Metadata
- Created: 2026-05-03 22:13:56
- Project: /home/claude/bin/sethmux
- Branch: main
- Session duration: continuation of 2026-04-24 deploy session, ~30 min of follow-up debugging
### Recent Commits (for context)
- 06c1ebb fix(docs): correct deploy topology — static assets live on caddy CT, not steel141
- 8a9d89b docs: add session handoff for toolbar refresh deploy
- 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
## Handoff Chain
- **Continues from**: [2026-04-24-195210-toolbar-workspace-dark-refresh-deploy.md](2026-04-24-195210-toolbar-workspace-dark-refresh-deploy.md)
- **Supersedes**: None (prior handoff is still accurate; this one closes its open gates)
## Current State Summary
The 2026-04-24 toolbar refresh + compose bar work is **fully shipped and verified live** on `mux.sethpc.xyz`. The previous handoff left one open gate: "mobile-browser acceptance test." That gate is partially closed — the deployed file matches the design hash, the file is reachable through Caddy, and direct fetch of `https://mux.sethpc.xyz/toolbar.js` returns the new content. What's *not* yet confirmed is the actual mobile-device feel of the compose bar interacting with Gboard/iOS autocorrect; that requires a real phone session by Seth and is the only remaining work.
A long detour mid-session uncovered two real gotchas that are now committed to docs: (1) the deploy is **two-host split** (Caddy CT serves static assets from its own filesystem; steel141 serves the index page and runs ttyd) — initial deploy went only to steel141, leaving the live site unchanged for ~30 minutes. (2) The toolbar is **gated behind `@media (max-width:900px)`**, so on a desktop viewport the page is byte-for-byte identical regardless of toolbar.js version. This is what caused the "looks the same after clearing cookies and site data" symptom: Seth was reloading on a Linux desktop browser ≥ 900px wide where the toolbar is `display:none` either way.
## Codebase Understanding
## Architecture Overview
Two-host serving topology, both happen to use the path `/opt/sethmux/`:
```
Browser
Caddy on caddy CT (192.168.0.185)
├── /toolbar.js, /manifest.json, /icon-*.png → file_server from /opt/sethmux/ on caddy CT
├── /api/* → reverse_proxy 192.168.0.141:7684 (notify-server.py on steel141)
└── / → reverse_proxy 192.168.0.141:7683 (ttyd on steel141)
└── ttyd serves --index /opt/sethmux/index.html (steel141 disk)
```
Auth (`import google_auth` in the Caddyfile vhost) wraps everything including `/toolbar.js`, but content is served directly off Caddy's disk — not proxied to an upstream. This is critical: the same path `/opt/sethmux/` exists on both hosts; only the caddy-CT copy of `toolbar.js` is what users actually see.
DNS: `mux.sethpc.xyz` is a CNAME to `sethpc.xyz`, which is an A record to `71.178.159.217` (Seth's home WAN). Traffic flows directly to the caddy CT's exposed port. **No CDN, no Cloudflare** — header `server: Caddy` confirms.
## Critical Files
| File | Purpose | Relevance |
|------|---------|-----------|
| `static/toolbar.js` | Source of truth for the mobile toolbar — Workspace dark + compose bar | Hash `6aee4388...`. Must `scp` to caddy CT to ship. |
| `caddy:/opt/sethmux/toolbar.js` | What Caddy actually serves | Must match the source hash above. As of 2026-04-25 00:16 UTC, it does. |
| `/opt/sethmux/toolbar.js` (steel141) | Unused — but kept in sync for parity | Updating this alone does **nothing** to live. Easy trap. |
| `/opt/sethmux/index.html` (steel141) | ttyd's `--index` — the page shell that loads `/toolbar.js` via `<script src>` | Lives on steel141 because ttyd is here. NOT on caddy CT. |
| `/etc/caddy/Caddyfile` (caddy CT) | The vhost block for `mux.sethpc.xyz` | `import google_auth` + path-handle blocks for static assets + catch-all reverse proxy. |
| `DECISIONS.md` | Project decision log | Has the two-host topology now (commit `06c1ebb`). Read before any deploy. |
| `claude-design/design_handoff_sethmux_toolbar/README.md` | Spec + acceptance checklist | Lines 134146 are the mobile acceptance criteria. |
| `.secrets.baseline` | detect-secrets allowlist | Required so SRI integrity hashes don't trip the global pre-commit hook. Regenerate with `--all-files` if adding more SRI tags. |
## Key Patterns Discovered
- **`import google_auth` in Caddyfile applies before path-handle blocks**, so OAuth gates `/toolbar.js` even though it's a static asset. Unauthenticated curl gets 302 → `auth.sethpc.xyz/oauth2/start` → Google. To verify the file directly, log in via browser and visit `/toolbar.js` as a URL.
- **Caddy default access logging is errors-only.** Successful 200s for `/toolbar.js` don't appear in `journalctl -u caddy`. To debug serving issues, watch for 5xx errors only — absence of a 200 in logs is normal, not diagnostic.
- **Mobile gating is a single CSS rule:** `@media (max-width: 900px) { #mb { display: flex } }`. Outside that breakpoint, `#mb` is `display:none`. Implication: any visual change to the toolbar is invisible at desktop widths. Test with DevTools device emulation (Ctrl+Shift+M, set width 375px) or on a real phone.
- **The PWA is "installable" but not offline-capable.** No service worker registration anywhere (no `navigator.serviceWorker` in `index.html`, no `sw.js`, manifest is bare-bones). So a "clear cookies and site data" really does drop all client-side cache for this origin — no PWA cache to chase.
## Work Completed
### Tasks Finished (this continuation)
- [x] Diagnosed "still looks the same" — initially suspected browser cache (wrong), then service worker (none), then CDN (none), finally found the deploy went to wrong host
- [x] Found `caddy:/opt/sethmux/toolbar.js` was still the Mar 26 file (`b578baeb...`, 4938 bytes)
- [x] `scp static/toolbar.js caddy:/opt/sethmux/` — now hash `6aee4388...` (matches design)
- [x] Backed up old caddy-CT file to `caddy:/opt/sethmux/.backup/toolbar.js.<ts>`
- [x] Updated `DECISIONS.md` with the correct two-host deploy topology (commit `06c1ebb`)
- [x] Amended prior handoff with the wrong-path correction (also in `06c1ebb`)
- [x] Stopped `sethmux.service` on steel141 to confirm Seth was hitting live infra (got 502 — confirmed), restarted immediately
- [x] Confirmed via direct URL fetch (Seth's browser) that `/toolbar.js` returns the new file
- [x] Final root cause of "no visible change": **viewport > 900px** — toolbar is mobile-only, identical on desktop regardless of version
## Files Modified
| File | Changes | Rationale |
|------|---------|-----------|
| `caddy:/opt/sethmux/toolbar.js` | scp from `static/toolbar.js` | Actual deploy that ships to users |
| `DECISIONS.md` | Added two-host deploy topology + commands | Don't repeat the wrong-host trap |
| `.claude/handoffs/2026-04-24-195210-...md` | Amended "Important Context" with the wrong-path correction | Diagnostic record so the trap is documented even if DECISIONS.md is missed |
## Decisions Made
| Decision | Options Considered | Rationale |
|----------|-------------------|-----------|
| Document the wrong-path in DECISIONS.md + handoff amendment + dedicated commit | Silent-fix the deploy and move on | Per global "no wrong-path coverup" rule. Future-self needs to know the trap exists. |
| Leave steel141:/opt/sethmux/toolbar.js as the new version (parity) | Revert to old to make the asymmetry obvious | Parity is less surprising. The file is harmless either way; only caddy CT's copy is served. |
| Don't add cache-busting query string to `<script src>` yet | Add `?v=<git-hash>` and bump on each deploy | The cache theory turned out wrong — viewport gating was the issue. No-cache header / version-string is unnecessary churn until a real cache problem appears. |
| Don't change the 900px breakpoint | Lower to e.g. 600px so desktop windows ~700-900px wide also see the toolbar | Out of scope. The design explicitly targets ≤900px and the toolbar is mobile-first. |
## Pending Work
## Immediate Next Steps
1. **Mobile acceptance test on a real device** — open `mux.sethpc.xyz` on your phone, walk the checklist in `claude-design/design_handoff_sethmux_toolbar/README.md` lines 134146. Particular attention to: `Type` button toggles compose row, Enter sends + clears, autocorrect/swipe in compose input actually works on Gboard/iOS, Save fills green for 1500ms, Sel toggles to "Done" and dims terminal. If everything passes, this work stream is genuinely done.
2. **If autocorrect doesn't feel native in the compose input**, escalate to the deferred "nuclear option" in DECISIONS.md (`inputmode="none"` on helper textarea + on-screen keyboard toggle). Don't escalate without a concrete failure mode — the compose bar is meant to make autocorrect work; if it fails, that's diagnostic info the design needs.
3. **Decide on `rdp_guac.txt`** — still untracked, 200KB Claude Code transcript dump. Either move under a `docs/` subdir or `rm`. Not blocking anything.
## Blockers/Open Questions
- [ ] None blocking. Real-device test is the only outstanding action.
### Deferred Items
- **Cache-Control `no-cache` header on `/toolbar.js`** — was going to add this when I thought the issue was browser caching. It wasn't, so no change made. Could be added defensively for future deploys, but the file is small and ETag-revalidated; only worth doing if a real cache-staleness incident occurs.
- **Auto-deploy script** — manual `scp` to caddy CT is the friction that caused this whole detour. Worth a `make deploy` or git post-commit hook that pushes to `caddy:/opt/sethmux/`. Not done yet.
## Important Context
- **`/opt/sethmux/` exists on TWO hosts. Only the caddy-CT copy of `toolbar.js`, `manifest.json`, `icon-*.png` is served to users.** Steel141's copy of `toolbar.js` is purely cosmetic (parity with the source repo) — modifying it alone has zero user-visible effect. This is the single biggest trap in this codebase. The Mar 26 → Apr 24 "drift" of three undeployed toolbar fixes was the same trap firing for ~30 days unnoticed.
Correct deploy:
```
scp static/toolbar.js static/manifest.json static/icon-*.png caddy:/opt/sethmux/
sudo cp static/index.html /opt/sethmux/ # steel141, only when index changes
sudo cp notify-server.py /opt/sethmux/ # steel141, only when notify changes
```
- **The toolbar is mobile-only.** Any "I deployed but nothing changed" report needs viewport check first: ask viewport width, or have the user open DevTools device emulation. On Linux/Mac/Win desktop browsers above 900px wide, both old and new toolbars are `display:none` — the page is byte-for-byte identical at the visual layer.
- **`import google_auth` wraps `/toolbar.js`.** Direct curl from outside (or even from inside the caddy CT with `Host: mux.sethpc.xyz`) returns 302/empty without OAuth cookies. Verification path for "is the new file actually served" is **logged-in browser opening the URL directly**, not curl.
- **Caddy access logs are errors-only by default.** Don't expect to see a 200 for `/toolbar.js` in `journalctl -u caddy`. Absence of an error means it served fine.
## Assumptions Made
- The design's `toolbar.js` matches what the visual mockups demonstrate. (Verified by hash equality with `claude-design/design_handoff_sethmux_toolbar/toolbar.js`.)
- Seth's browser sessions to `mux.sethpc.xyz` carry valid OAuth cookies after his prior login. (Implicit — direct URL fetch worked.)
- The 900px breakpoint is intentional and not subject to change. (Per design spec.)
## Potential Gotchas
- **Don't restart `caddy.service` on the caddy CT** for static-asset changes. Caddy reads files on each request; no reload needed. Restarting Caddy interrupts every other sethpc.xyz subdomain (50+ services).
- **Don't restart `sethmux.service` on steel141** for static-asset changes either. Only restart when changing ttyd args (in the unit file) or `index.html`.
- **Don't run `git commit --no-verify`.** The pre-commit hook runs detect-secrets-hook against `.secrets.baseline`. SRI integrity hashes (`integrity="sha384-..."`) trip it as high-entropy strings. Regenerate baseline with `detect-secrets scan --all-files --exclude-files '\.git/|\.secrets\.baseline$' > .secrets.baseline` instead.
- **Watching mux.sethpc.xyz logs is harder than expected.** Caddy errors-only means a 200 for /toolbar.js never shows. To trace a request end-to-end, either add a `log` directive to the vhost (don't do this casually — log volume is high), or use browser DevTools Network tab.
## Environment State
### Tools/Services Used
- `gitea` CLI — `~/bin/gitea`, pushes to `https://git.sethpc.xyz/Seth/sethmux.git` with token from `~/.config/gitea/token`
- `ssh caddy` (192.168.0.185, CT 600 on node-241) — has direct write access to `/opt/sethmux/`; deploys go via `scp ... caddy:/opt/sethmux/`
- `ssh pve241` — Caddy CT's host node (only relevant if Caddy itself needs systemd-level work, which is rare)
- `detect-secrets` — at `/home/claude/.local/bin/detect-secrets` for baseline regeneration
### Active Processes (sethmux-relevant)
- `sethmux.service` on steel141 — ttyd on `:7683`, attaches `tmux -t sethmux`, user `rdp`. Running.
- `sethmux-notify.service` on steel141 — `notify-server.py` on `:7684`. Running.
- `caddy.service` on caddy CT — vhost for `mux.sethpc.xyz` + ~50 other subdomains. Running.
- `hal-terminal.service` on steel141 — separate fork, port `:7685`, own static dir. Unrelated; do not touch.
### Environment Variables
- `HOMELAB_PASSWORD` — used elsewhere in `~/bin`, not needed for sethmux deploys (key auth to `caddy` works)
- No new env vars added or required this session
## Related Resources
- Design spec + acceptance checklist: `claude-design/design_handoff_sethmux_toolbar/README.md`
- Project decision log: `DECISIONS.md`
- Helper-textarea autocomplete fix rationale: `AUTOCOMPLETE_FIX.md`
- Repo: https://git.sethpc.xyz/Seth/sethmux
- Live URL (auth-gated): https://mux.sethpc.xyz
- Caddyfile: `ssh caddy "cat /etc/caddy/Caddyfile"` — search the `mux.sethpc.xyz` vhost block
---
**Security Reminder**: Before finalizing, run `validate_handoff.py` to check for accidental secret exposure.