← Commit history

Delete BUILD-SKILL.md

John Lauer ·408a74107d ·3mo ago ·parent c8ba77f
1 file changed −152
BUILD-SKILL.mddeleted−152
@@ -1,152 +0,0 @@-----name: adom-tts-build-description: Build, debug, and release a new version of adom-tts. Use when the user says "release adom-tts", "publish adom-tts", "cut a new adom-tts version", "bump adom-tts", "ship adom-tts", "push adom-tts to the wiki", or when debugging playback failures (silent clips, double-plays, no-playback verdicts).------# Build & release adom-tts--A release syncs five places to the same version X.Y.Z:-`VERSION`, `Cargo.toml`, `package.json`, `page.json`, `adom-tts --version`.--## Architecture (know this before touching playback code)--- **One shared queue** (`~/.adom/tts/queue/*.json`) fed by every thread's-  `say --play`/`play`. Entries carry a **byte-snapshot** of the mp3 (+ name,-  label, words) so callers reusing a filename can't corrupt a queued clip.-- **One drainer** (`adom-tts __drain`), guarded by `~/.adom/tts/drainer.lock`-  = `{pid}\n{version}`. It runs a **persistent HTTP server on one port** for-  its whole life; the webview tab loads ONCE and swaps clips via `/poll`-  (never re-navigates — re-navigation was the "opened but silent" race).-- **Version-supersede protocol:** a drainer from a NEWER binary overwrites the-  lock; the old drainer notices `!i_own_lock()` within 250ms, abandons its-  current clip, exits, and leaves the tab. This is how every thread converges-  on the latest code after a deploy — **never kill drainers manually** (below).-- **Playback truth:** the page beacons telemetry AND piggybacks it on `/poll`;-  the drainer logs `clip_end` verdicts (`ok/partial/no-playback/error`) to-  `~/.adom/tts/events.jsonl`. `--confirm`/`check` read that log — they never-  touch the contended surface, which is why they're reliable.-- **Resilience:** clips buffer up to 10 min waiting for a live page-  (sustained-liveness gate: 3 consecutive polls), retry 3× on `no-playback`,-  download-then-play from a local blob (hotspot-proof), and the drainer adopts-  surface env (`~/.adom/tts/env.json`) so a sanitized-shell spawn can't poison-  playback.--## Release steps--1. **Bump — ANY code change gets a new version.** Same-version content drift-   broke supersede once (binaries differed, version matched, old code kept-   running). Bump all four files:-   ```bash-   echo "X.Y.Z" > VERSION-   sed -i 's/^version = .*/version = "X.Y.Z"/' Cargo.toml-   sed -i 's/"version": "[0-9.]*"/"version": "X.Y.Z"/' package.json page.json-   ```--2. **GATE on tests, then build via `./build.sh` (never bare `cargo build`).**-   v1.9.9 shipped with a failing test because the publish script didn't gate —-   it had to be deprecated. `build.sh` also refreshes `bin/adom-tts` so the-   packaged binary can never lag the version (the 1.4.1-shipped-1.0.5 bug):-   ```bash-   cargo test --release 2>&1 | grep -q "test result: ok" || { echo "TESTS FAILED — do not ship"; exit 1; }-   ./build.sh-   ./target/release/adom-tts --version   # must print X.Y.Z-   ./target/release/adom-tts health      # service-tts reachable-   ```--3. **Deploy: make this build the ONLY binary on the container.** Compare by-   BYTES (`cmp`), not version string — same-version drift is invisible to a-   version check. Overwrite in place (never delete another thread's sandbox):-   ```bash-   install -m 0755 target/release/adom-tts ~/.local/bin/adom-tts-   NEW=$(pwd)/target/release/adom-tts-   for f in $(find /home/adom /usr/local/bin /tmp -maxdepth 4 -name adom-tts -type f 2>/dev/null); do-     case "$f" in */.bash_completion.d/*) continue;; esac-     cmp -s "$NEW" "$f" || install -m 0755 "$NEW" "$f"-   done-   ```-   The RUNNING drainer self-supersedes on the next `say --play` (version-   handoff). Other containers pull via `adompkg self-update` / adom-hook.--4. **Ralph-test the webview before publish** — render it yourself, look at it:-   ```bash-   CHROME=$(ls ~/.cache/puppeteer/chrome/*/chrome-linux*/chrome | head -1)-   # bake VERSION + inject a fake clip via applyClip(), then:-   "$CHROME" --headless --no-sandbox --hide-scrollbars --window-size=500,820 \-     --screenshot=/tmp/skinny.png file:///tmp/preview.html   # skinny = the 20% column-   "$CHROME" --headless --no-sandbox --window-size=900,620 --screenshot=/tmp/wide.png file:///tmp/preview.html-   ```-   Gotchas that produced real bugs:-   - Headless Chrome clamps the window to **500px min width** — a 360px-     screenshot is a 500px layout CLIPPED, not a narrow render. Probe-     `innerWidth`/`scrollWidth` in-page instead of trusting the image.-   - **Media queries must be LAST in the stylesheet** — equal-specificity base-     rules later in the file silently override them (real bug: skinny reflow-     never applied).-   - Test REAL event paths with `el.dispatchEvent(new Event('playing'))`, not-     by calling the handler — the play/pause icon was broken for 9 versions-     because `setAttribute('d')` targeted the `<svg>` instead of its `<path>`-     (silent no-op), and nothing exercised the event.--5. **Publish with `adompkg`, then re-push `page.json` LAST** (publish clobbers-   the hero link every time):-   ```bash-   rm -rf /tmp/adom-tts-pub && mkdir -p /tmp/adom-tts-pub-   tar cf - --exclude=target --exclude=.git --exclude='*.tgz' --exclude=.adom-cache . | tar xf - -C /tmp/adom-tts-pub-   ( cd /tmp/adom-tts-pub && ADOMPKG_REGISTRY=https://wiki.adom.inc adompkg publish --org adom --yes )-   # then POST page.json via /api/v1/pages/adom-tts/files to restore the hero-   ```-   Versions are immutable. If a bad version ships: fix, bump, publish, then-   `adompkg deprecate adom/[email protected] --reason "..."`.--6. **Sync skills everywhere** (in-repo + external copies):-   ```bash-   cp SKILL.md skills/adom-tts/SKILL.md; cp BUILD-SKILL.md skills/adom-tts-build/SKILL.md-   for d in ~/gallia ~/.claude ~/.codex; do cp SKILL.md "$d/skills/adom-tts/SKILL.md"; done-   ```--7. **Prove playback end-to-end, don't claim it:**-   ```bash-   adom-tts say "release X.Y.Z verification" --out /tmp/rel.mp3 --thread "<thread>" --play --confirm-   # must print: ✓ CONFIRMED ... played fully to the human-   ```--## Debugging playbook (every failure today yielded one of these)--- **Start with `adom-tts logs` + `adom-tts status`** — verdicts, per-clip-  timelines, lock pid/version, queue. Raw log: `~/.adom/tts/events.jsonl`.-- **"Clip played twice / flipping audio"** → two drainers. Check-  `cat ~/.adom/tts/drainer.lock` vs live `__drain` processes. Cause is almost-  always an old-version drainer that hasn't been superseded — deploy + one-  `say` fixes it. Also check `player_reopen` events: a reopen RELOADS the tab-  and restarts a clip, so reopen must only ever fire for clips that never-  started (`reached == 0`).-- **"Synth ok but verdict no-playback"** → the page isn't connected. Check-  `open_surface_failed` errors in the log. If `adom-cli hydrogen workspace get`-  works in YOUR shell but fails for the drainer → **env mismatch**: diff-  `tr '\0' '\n' < /proc/<drainer-pid>/environ` against `env`. Sanitized-shell-  spawns lack `VSCODE_PROXY_URI` (adom-cli hard-requires it, and `webview_url`-  falls back to unreachable 127.0.0.1). The env self-heal (env.json) fixes-  this class; `drain_env_missing` in the log means the snapshot is also empty.-- **"Same clip heard again later"** → check clip IDs/durations in the log-  first: two different clips named `reply.mp3` from different threads LOOK-  like a repeat. Byte-snapshots + `clip_retry` events tell the real story.-- **Intermittent long-say failures, health OK** → duration-correlated proxy-  timeout. Keep synth requests small (chunk ≤360 chars, sequential) instead-  of raising timeouts.-- **NEVER `pkill`/`killall`, and never grep-kill.** A `grep adom-tts __drain`-  loop matches ITS OWN shell command line and kills the session (happened —-  exit 144). If you truly must kill a drainer (last resort — prefer-  version-supersede), match argv precisely via `/proc/<pid>/cmdline` split on-  NUL, e.g. in python. Killing drainers mid-playback during live use spawns-  racing replacements and makes audio WORSE — deploy a new version instead.-- **Don't churn the player while the user is listening.** Diagnose from logs;-  the system self-heals (supersede, retries, buffering). Manual intervention-  during live playback was itself a cause of user-audible chaos today.--## Service-tts coupling--`adom-tts` is a client — the server is `service-tts`. If you need new server-behavior (streaming, job-poll for long bodies, new endpoints), cut a-`service-tts` release too — see its `BUILD-SKILL.md`. Client-side mitigations-(chunking, retry, fail-fast timeout) live in `synth_chunk`/`split_into_chunks`-in `src/main.rs`.