Skip to content

feat(api-tox): v2.2.1 Phase 1.2.1 — UX polish (drag-in-and-it-works) - #38

Merged
dreamrec merged 2 commits into
mainfrom
claude/v2.2.1-phase-1.2.1-ux-polish
May 11, 2026
Merged

feat(api-tox): v2.2.1 Phase 1.2.1 — UX polish (drag-in-and-it-works)#38
dreamrec merged 2 commits into
mainfrom
claude/v2.2.1-phase-1.2.1-ux-polish

Conversation

@dreamrec

Copy link
Copy Markdown
Owner

Summary

Drag-in UX polish that emerged from the live-debug session against the v2.2.0 reliability features. Three friction points were silently making the chat-pipe a worse-than-it-needs-to-be experience for new users AND devs. This PR fixes all three.

Verified live in TouchDesigner pre-merge: maintainer rebuilt the .tox + sent yo from a fresh tab; chat round-trip succeeded with zero env-var toggles or pulse rituals.

The three friction points

# Before After
1 TDPILOT_API_INSECURE=1 was a process env var — wiped on every TD restart, so users dropped back into 401-land after every reboot. Authmode is a COMP param (Menu: open / token, default open). Persists in the .toe. Read per-request, no Reloadconfig needed to flip.
2 Pasting a new Apikey value required two follow-up pulses (Saveapikey, Reloadconfig). Users said "I set the key but the agent doesn't see it." parexec listens to valuechange=1; Extension.OnApikeyValueChange delegates to the existing save+reload path. Zero pulses required.
3 After every .tox rebuild the chat panel's CSRF token rotated. Every open browser tab 401'd until the user knew to manually navigate to http://127.0.0.1:9987/ for a fresh one. The 401 catch branch in the chat JS now renders a yellow "Reconnect (reload this tab)" banner with a real <button> that calls window.location.reload(). One-click recovery.

Files

Modified (5 .tox-baked + 1 doc):

  • td_component/build_tdpilot_api_tox.py — adds Authhdr section + Authmode Menu param under the API page; _wire_parexec enables valuechange=1.
  • td_component/tdpilot_api_web_callbacks.py_insecure_mode reads Authmode COMP param first; env var becomes a fallback for back-compat.
  • td_component/tdpilot_api_parexec.pyonValueChange routes only Apikey and Authmode to the extension (every other value change is a no-op).
  • td_component/tdpilot_api_extension.py — new OnApikeyValueChange + OnAuthmodeValueChange methods. The Apikey handler has an empty-string short-circuit to prevent recursion through OnSaveApiKeyPulse's Apikey.val = "" wipe.
  • td_component/tdpilot_api_chat.html — new appendReconnectBanner() JS helper; /send catch branches on the 401 substring to surface the reload-button instead of the generic error.
  • CHANGELOG.md — Phase 1.2.1 section under Unreleased.

New (1 test file): tests/test_v221_authmode_and_autoreload.py17 tests across 5 classes.

Binary update (2): td_component/tdpilot_API.tox + .tox-api-source-hash.json (rebuilt by maintainer in TD; freshness gate green at hash <new>).

Tests

Class # What it pins
TestInsecureModeFromAuthmode 6 Authmode=open/token, whitespace+case normalization, unknown-value falls through to env var; COMP param overrides env var.
TestBackwardsCompatibility 2 Old .tox (no Authmode param) still works via env var; _comp() returning None doesn't raise.
TestAuthGateEndToEnd 3 Open mode lets tokenless /send through; Token mode blocks; Open mode still rejects cross-origin (origin allowlist preserved — no LAN-RCE regression).
TestParexecValueChangeRouting 5 Apikey/Authmode route correctly; other params no-op; missing-extension and raising-handler paths don't crash the cook thread.
TestApikeyEmptyShortCircuit 1 Verifies the empty-string guard prevents the recursion loop through OnSaveApiKeyPulse's post-save wipe.

Test plan

  • uv run pytest tests/ --ignore=tests/agent_evals -q1838 passed (1821 prior + 17 new).
  • uv run --extra dev ruff format --check src tests scripts td_component — clean.
  • uv run --extra dev ruff check src tests scripts td_component — clean.
  • uv run python scripts/check_versions.py — in sync at v2.1.5 (no bump; v2.2.0 cuts when Phase 1 is complete).
  • uv run python scripts/check_tox_freshness.py — dpsk4 .tox fresh (not touched by this PR).
  • uv run python scripts/check_tox_api_freshness.py — API .tox fresh post-rebuild.
  • bash scripts/check_no_personal_paths.sh — clean.
  • Live in TD: maintainer dragged in rebuilt .tox + sent yo from a fresh tab. Round-trip succeeded with no auth toggle (proving default Authmode=open works as designed).
  • CI all-green on push.

Security model — explicit migration note

The default chat-pipe webserver auth posture changes from token-required to origin-allowlist-only. This is appropriate for TouchDesigner's typical usage pattern (single-user creative coding on a personal box) and addresses the real friction it was creating, but it is a defaults change worth flagging:

  • Same-machine attackers (other processes on localhost): previously needed to know the rotating session token to drive the chat-pipe. Now can hit POST /send directly. The threat model assumes you trust other code running on your own machine — same assumption already implicit in TouchDesigner's parameter system.
  • Cross-origin browser attacks (any tab in your browser running attacker JS): STILL BLOCKED by the v1.7.1 origin allowlist + v2.1.3 JSON-envelope/CORS-preflight hardening. Open mode bypasses only the token check — the cross-origin gate is untouched.
  • LAN attackers (other machines on the same WiFi): webserverDAT defaults vary across TD builds. If your TD binds to 0.0.0.0 (LAN-reachable), Open mode means anyone on the LAN can drive your chat-pipe. The Authmode = token flip restores the v2.1.3 protection one click away. Users in shared studio / venue / coffee-shop networks should flip it; users on personal laptops are fine on Open.

This trade-off is documented in the COMP's Authhdr section + CHANGELOG. The tdpilot-dpsk4.tox MCP server (port 9985) auth is untouched by this PR.

Why this should ship now (before Phase 1.5 / 2.1)

This is the kind of "rough edge nobody catches until a real user tries the system end-to-end" friction. The live-debug session in this session's earlier context surfaced exactly the failure modes this PR addresses — they're real, they're cheap to fix, and waiting to ship them means every additional Phase 1.x rebuild compounds the same workflow papercut.

🤖 Generated with Claude Code

dreamrec and others added 2 commits May 11, 2026 18:38
Fixes three friction points the live-debug session exposed:

  1. `TDPILOT_API_INSECURE` was a process env var — gone on TD
     restart. Users got 401 after every restart with no clear
     remedy.

  2. Pasting a new `Apikey` required two follow-up pulses
     (`Saveapikey` + `Reloadconfig`) — non-obvious; users said
     "I set the key but the agent doesn't see it".

  3. After every `.tox` rebuild the chat panel's token rotated —
     every open browser tab 401'd until the user knew to navigate
     to `http://127.0.0.1:9987/` for a fresh one.

What's now in
=============

1. **`Authmode` COMP param replaces the env var as source of truth.**
   New Menu param under the API page: `open` (default) / `token`.
   `tdpilot_api_web_callbacks._insecure_mode` reads it first; env-var
   becomes a fallback for back-compat. Per-request read, so flipping
   the param takes effect immediately. Persists in the .toe.

   Default is `open`: tokenless POST /send works. The origin
   allowlist (v1.7.1) still enforces single-machine isolation, so
   even Open mode rejects cross-origin browser CSRF. Suits TD's
   single-user dev / live performance usage profile. Multi-machine
   users flip `Authmode = token` once and the v2.1.3 token model
   kicks back in.

2. **Auto-save + auto-reload on Apikey change.** `tdpilot_api_parexec`
   now listens to `valuechange=1` (build script change). Filter is
   narrow: only `Apikey` and `Authmode` route to the extension;
   every other value change is a no-op. New
   `Extension.OnApikeyValueChange` delegates to `OnSaveApiKeyPulse`
   (which already saves to disk + reloads). Recursion-safe via
   empty-string short-circuit. Drops the "type key, pulse Save,
   pulse Reload" 3-step ritual to one paste.

3. **Stale-token reconnect banner in the chat panel.** New
   `appendReconnectBanner()` JS helper in
   `tdpilot_api_chat.html`. When `fetch('/send')` returns 401, the
   panel renders a yellow message with a real `<button>` that calls
   `window.location.reload()` — re-fetches `GET /` which already
   bakes the current token into the served HTML. Bookmark-friendly
   `http://127.0.0.1:9987/` always serves a working panel.

Tests (17 new in tests/test_v221_authmode_and_autoreload.py)
============================================================

- TestInsecureModeFromAuthmode (6): Authmode=open/token/whitespace/
  unknown-falls-through; COMP param beats env var.
- TestBackwardsCompatibility (2): old .tox without Authmode param
  still works via env var; `_comp()` returning None doesn't raise.
- TestAuthGateEndToEnd (3): Open mode lets tokenless /send through;
  Token mode blocks; Open mode still rejects cross-origin.
- TestParexecValueChangeRouting (5): Apikey/Authmode route correctly;
  other params no-op; missing-extension and raising-handler don't
  crash the cook thread.
- TestApikeyEmptyShortCircuit (1): verifies the empty-string guard
  prevents recursion through OnSaveApiKeyPulse's `Apikey.val=""` wipe.

Local sweep
===========

- pytest: 1838 passed (1821 prior + 17 new).
- ruff format + check: clean.
- check_versions: in sync at v2.1.5 (no bump on this PR; v2.2.0
  cuts when Phase 1 is complete).
- check_tox_freshness (dpsk4): fresh — that .tox not touched.
- check_tox_api_freshness (chat-pipe): EXPECTED FAIL until user
  rebuilds the .tox via the canonical Textport recipe.

Files modified for the .tox bake:
  - build_tdpilot_api_tox.py (adds Authhdr + Authmode param;
    enables valuechange=1 on parexec)
  - tdpilot_api_web_callbacks.py (Authmode-based _insecure_mode)
  - tdpilot_api_parexec.py (onValueChange routing)
  - tdpilot_api_extension.py (OnApikeyValueChange + OnAuthmodeValueChange)
  - tdpilot_api_chat.html (appendReconnectBanner + 401 catch branch)

Migration note: default auth posture changes from token-required to
origin-allowlist-only. Users sharing .toes across machines who
relied on the v2.1.3 token gate must explicitly set
Authmode = token on the COMP for that deployment.

MCP-server tox (port 9985) untouched — TD_MCP_SHARED_SECRET still
required for the MCP path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dreamrec
dreamrec merged commit e6cc19c into main May 11, 2026
6 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ebc76845c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +167 to +169
value = str(comp.par.Authmode.val or "").strip().lower()
if value in ("open", "token"):
return value == "open"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Gate open auth mode behind the same exec clamp

Authmode=open now makes _insecure_mode() return True by default, which removes the X-TDPilot-Token requirement on /send, but the runtime hardening in TDPilotAPIExt._build_runtime still clamps TD_MCP_EXEC_MODE only when the env var TDPILOT_API_INSECURE is set. In practice this commit introduces a no-token path while leaving full exec enabled, so any local process (and LAN clients if the webserver is exposed) can drive agent turns that can reach td_exec_python with full privileges. The 2.1.3 mitigation needs to key off effective auth mode, not just the legacy env var.

Useful? React with 👍 / 👎.

dreamrec added a commit that referenced this pull request May 11, 2026
First milestone of the v2.2.0→v3.0 roadmap (see docs/ROADMAP.md).
Phase 1 ships in full; the chat-pipe agent is now safe to leave
unsupervised on complex builds, two failure modes have automatic
recovery, and drag-and-go finally works for new users out of the box.

Bundled in this release
=======================

  - 1.1 Auto-rollback on error regression (PR #34 + #36)
  - 1.2 Cycle detection in tool chains (PR #37)
  - 1.2.1 Drag-and-go UX polish — Authmode COMP param,
    auto-save+auto-reload on Apikey change, 401 reconnect
    banner (PR #38)
  - 1.2.2 Build scripts auto-mirror .tox into main repo on rebuild,
    eliminating stale-symlink footgun (PR #39)
  - PR #35 paired source-file-list refactor (build-script + freshness-
    check now share a single source of truth)

136 new tests since v2.1.5. Total suite at release: 1848 passing.

Headline behaviour change for end users
=======================================

Default chat-pipe webserver auth posture changes from token-required
to origin-allowlist-only (Authmode=open default). The origin
allowlist still rejects cross-origin browser CSRF, so this is safe
on TouchDesigner's typical single-user dev/perform usage profile.
Users sharing .toe files across machines should set Authmode=token
on the COMP for that deployment.

The MCP-server tdpilot-dpsk4.tox (port 9985) auth model is UNCHANGED
— TD_MCP_SHARED_SECRET still required.

Bumps + rebuilt artifacts
=========================

All 10 versioned files (7 manifests + 3 doc titles) bumped from
2.1.5 to 2.2.0 (scripts/check_versions.py enforces sync). API_VERSION
in td_component/callbacks/_header.py also bumped; this invalidated
both .tox source-hashes, both rebuilt inside TouchDesigner via the
canonical recipe. Auto-mirror (Phase 1.2.2, just landed) propagated
the rebuilt .tox files to the main repo's td_component/ automatically.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dreamrec
dreamrec deleted the claude/v2.2.1-phase-1.2.1-ux-polish branch May 11, 2026 22:48
dreamrec added a commit that referenced this pull request May 19, 2026
…38)

* feat(api-tox): v2.2.1 Phase 1.2.1 — UX polish (drag-in-and-it-works)

Fixes three friction points the live-debug session exposed:

  1. `TDPILOT_API_INSECURE` was a process env var — gone on TD
     restart. Users got 401 after every restart with no clear
     remedy.

  2. Pasting a new `Apikey` required two follow-up pulses
     (`Saveapikey` + `Reloadconfig`) — non-obvious; users said
     "I set the key but the agent doesn't see it".

  3. After every `.tox` rebuild the chat panel's token rotated —
     every open browser tab 401'd until the user knew to navigate
     to `http://127.0.0.1:9987/` for a fresh one.

What's now in
=============

1. **`Authmode` COMP param replaces the env var as source of truth.**
   New Menu param under the API page: `open` (default) / `token`.
   `tdpilot_api_web_callbacks._insecure_mode` reads it first; env-var
   becomes a fallback for back-compat. Per-request read, so flipping
   the param takes effect immediately. Persists in the .toe.

   Default is `open`: tokenless POST /send works. The origin
   allowlist (v1.7.1) still enforces single-machine isolation, so
   even Open mode rejects cross-origin browser CSRF. Suits TD's
   single-user dev / live performance usage profile. Multi-machine
   users flip `Authmode = token` once and the v2.1.3 token model
   kicks back in.

2. **Auto-save + auto-reload on Apikey change.** `tdpilot_api_parexec`
   now listens to `valuechange=1` (build script change). Filter is
   narrow: only `Apikey` and `Authmode` route to the extension;
   every other value change is a no-op. New
   `Extension.OnApikeyValueChange` delegates to `OnSaveApiKeyPulse`
   (which already saves to disk + reloads). Recursion-safe via
   empty-string short-circuit. Drops the "type key, pulse Save,
   pulse Reload" 3-step ritual to one paste.

3. **Stale-token reconnect banner in the chat panel.** New
   `appendReconnectBanner()` JS helper in
   `tdpilot_api_chat.html`. When `fetch('/send')` returns 401, the
   panel renders a yellow message with a real `<button>` that calls
   `window.location.reload()` — re-fetches `GET /` which already
   bakes the current token into the served HTML. Bookmark-friendly
   `http://127.0.0.1:9987/` always serves a working panel.

Tests (17 new in tests/test_v221_authmode_and_autoreload.py)
============================================================

- TestInsecureModeFromAuthmode (6): Authmode=open/token/whitespace/
  unknown-falls-through; COMP param beats env var.
- TestBackwardsCompatibility (2): old .tox without Authmode param
  still works via env var; `_comp()` returning None doesn't raise.
- TestAuthGateEndToEnd (3): Open mode lets tokenless /send through;
  Token mode blocks; Open mode still rejects cross-origin.
- TestParexecValueChangeRouting (5): Apikey/Authmode route correctly;
  other params no-op; missing-extension and raising-handler don't
  crash the cook thread.
- TestApikeyEmptyShortCircuit (1): verifies the empty-string guard
  prevents recursion through OnSaveApiKeyPulse's `Apikey.val=""` wipe.

Local sweep
===========

- pytest: 1838 passed (1821 prior + 17 new).
- ruff format + check: clean.
- check_versions: in sync at v2.1.5 (no bump on this PR; v2.2.0
  cuts when Phase 1 is complete).
- check_tox_freshness (dpsk4): fresh — that .tox not touched.
- check_tox_api_freshness (chat-pipe): EXPECTED FAIL until user
  rebuilds the .tox via the canonical Textport recipe.

Files modified for the .tox bake:
  - build_tdpilot_api_tox.py (adds Authhdr + Authmode param;
    enables valuechange=1 on parexec)
  - tdpilot_api_web_callbacks.py (Authmode-based _insecure_mode)
  - tdpilot_api_parexec.py (onValueChange routing)
  - tdpilot_api_extension.py (OnApikeyValueChange + OnAuthmodeValueChange)
  - tdpilot_api_chat.html (appendReconnectBanner + 401 catch branch)

Migration note: default auth posture changes from token-required to
origin-allowlist-only. Users sharing .toes across machines who
relied on the v2.1.3 token gate must explicitly set
Authmode = token on the COMP for that deployment.

MCP-server tox (port 9985) untouched — TD_MCP_SHARED_SECRET still
required for the MCP path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: rebuild tdpilot_API.tox for Phase 1.2.1 UX polish

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dreamrec added a commit that referenced this pull request May 19, 2026
First milestone of the v2.2.0→v3.0 roadmap (see docs/ROADMAP.md).
Phase 1 ships in full; the chat-pipe agent is now safe to leave
unsupervised on complex builds, two failure modes have automatic
recovery, and drag-and-go finally works for new users out of the box.

Bundled in this release
=======================

  - 1.1 Auto-rollback on error regression (PR #34 + #36)
  - 1.2 Cycle detection in tool chains (PR #37)
  - 1.2.1 Drag-and-go UX polish — Authmode COMP param,
    auto-save+auto-reload on Apikey change, 401 reconnect
    banner (PR #38)
  - 1.2.2 Build scripts auto-mirror .tox into main repo on rebuild,
    eliminating stale-symlink footgun (PR #39)
  - PR #35 paired source-file-list refactor (build-script + freshness-
    check now share a single source of truth)

136 new tests since v2.1.5. Total suite at release: 1848 passing.

Headline behaviour change for end users
=======================================

Default chat-pipe webserver auth posture changes from token-required
to origin-allowlist-only (Authmode=open default). The origin
allowlist still rejects cross-origin browser CSRF, so this is safe
on TouchDesigner's typical single-user dev/perform usage profile.
Users sharing .toe files across machines should set Authmode=token
on the COMP for that deployment.

The MCP-server tdpilot-dpsk4.tox (port 9985) auth model is UNCHANGED
— TD_MCP_SHARED_SECRET still required.

Bumps + rebuilt artifacts
=========================

All 10 versioned files (7 manifests + 3 doc titles) bumped from
2.1.5 to 2.2.0 (scripts/check_versions.py enforces sync). API_VERSION
in td_component/callbacks/_header.py also bumped; this invalidated
both .tox source-hashes, both rebuilt inside TouchDesigner via the
canonical recipe. Auto-mirror (Phase 1.2.2, just landed) propagated
the rebuilt .tox files to the main repo's td_component/ automatically.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant