docs(02-07): record live-testing follow-up round (overlay, theme, key fallback, CLI path)

This commit is contained in:
archipelago 2026-07-30 20:20:49 -04:00
parent faf4a75ddf
commit 5b37426fab

View File

@ -238,3 +238,172 @@ None required for this plan's own scope — the AIUI-side commit is now upstream
---
*Phase: 02-ui-performance*
*Completed: 2026-07-30*
## Addendum: Live-Testing Follow-Up Round (2026-07-30, post-approval)
After Task 3's checkpoint was approved, the user live-tested the embedded Chat/AIUI panel
against the restored `:8100` mock session (iframe pointed at the local AIUI dev server,
`http://100.69.68.39:5173`, running `feat/d14-embed-defaults`) and reported four issues.
Each was root-caused rather than patched over. This addendum records the fixes; no new plan
was created per the coordinator's direction.
### 1. Loading overlay never dismissed, blocking the interface
**Root cause (AIUI, `services/archyBridge.ts`):** `archyBridge.init()` used
`window.location.origin` — this **iframe's own** origin — as the postMessage target origin
for messages sent **to** the parent, and as the validation origin for messages received
**from** the parent. Both are wrong: they should be the **parent's** origin. This worked by
coincidence only when AIUI is served same-origin as its host (production's `/aiui/` proxy)
and silently broke the entire bridge — including the initial `'ready'` message — the moment
AIUI runs on a different origin than its embedding page (any dev setup with a separate AIUI
dev server, exactly this test).
**Fix:** `archyBridge.ts` now derives the parent's real origin from `document.referrer`
(the standard, cross-origin-safe way an iframed document learns its embedding parent's URL),
falling back to `window.location.origin` only if `document.referrer` is unavailable.
**Defense in depth (archy, `Chat.vue`):** even with the root cause fixed, the loading overlay
must never be able to wedge the UI regardless of AIUI/backend state. Two changes: the overlay
now has `pointer-events: none` (it has no interactive content, so it should never have blocked
clicks reaching the iframe underneath), and a bounded 8s timeout unconditionally dismisses it
if `'ready'` never arrives — the timeout does not fabricate a successful connection; the
connected indicator still reflects reality. Covered by two new tests in
`chatAiuiEmbed.test.ts` (fires at exactly 8s, does not fire prematurely).
### 2. Background rendered white, then flat black (not the branded look)
**Root causes (AIUI):** three compounding issues, found via source inspection:
- `useTheme.ts`'s `initTheme()` decides light/dark from `localStorage` or the OS's
`prefers-color-scheme`, with zero awareness of being embedded — an embedding browser/OS
with no dark preference (common in headless/automated or freshly-provisioned contexts)
landed on `'light'`.
- `useArchy.ts`'s `onThemeUpdate` callback applied Archy's reported accent color but silently
**ignored** the `mode` field Archy always sends as `'dark'` — the theme-sync loop was
incomplete.
- `main.css`'s `body` had **no explicit `background-color` at all** — so `ChatPage.vue`'s
embedded `background: transparent` fell through to the browser's white UA default, not to
anything dark.
- **First-pass fix** (dark bg only): forced `setTheme('dark')` in `App.vue`'s `onMounted`
when embedded (before any handshake completes — must not depend on the postMessage round
trip), wired `useArchy.ts`'s theme callback to apply `theme.mode`, and gave `body` an
explicit `#0a0a0a` (`html.light body``#faf9f6`) background. This fixed "white" but
produced a flat black canvas.
- **Second-pass fix** (live user follow-up: "black is fine while loading, but the real
background still never shows"): `ChatPage.vue`'s embedded branch was deliberately opting
out of the same background-image treatment (`bg-intro-3.jpg` cover image) the standalone
dark-mode app uses, substituting flat `transparent` instead. Reordered the style/overlay
conditionals so `isDark` takes priority over `isEmbedded` — the embed now renders the
**exact same** branded background image + readability overlay as standalone, with the
`#0a0a0a` body color serving only as the natural progressive-load fallback before the image
paints, matching the user's explicit ask.
### 3. New feature: auto-open Settings when there's no working AI credential
**Design constraint:** must not misfire for the documented production path, where a
node-side proxy (not a personal key) is expected to just work — see the API key research
below.
**Implementation (AIUI, `useAI.ts` + `ChatWindow.vue`):** a new one-shot `needsApiKey` signal,
set only on a **narrow** set of failure signatures (401/403, "api key"/"unauthorized" text, or
a proxy-unreachable failure) from a `sendMessage`/`regenerateLastResponse`/`editAndResend`
attempt using the `claude`/`openrouter` providers (never `mock`) — deliberately excluding
generic/transient errors (rate limits, momentary network blips) so Settings doesn't pop up for
a problem Settings can't fix. `ChatWindow.vue` watches the signal and opens the existing
`SettingsModal`, resetting the signal immediately after (a pulse, not sticky state, so a later
retry that fails the same way can re-trigger it).
### 4. Chat CLI fallback crashed with `ENOENT`
**Root cause (AIUI, `server/claude-proxy.ts`):** the local dev proxy's CLI fallback (used when
no `ANTHROPIC_API_KEY`/`ANTHROPIC_TOKEN` is configured) spawned a **hardcoded**
`~/.local/bin/claude` path — broke with `spawn ENOENT` on this machine, where the `claude` CLI
actually lives under the active `nvm` Node install's `bin/` directory. The user unblocked
themselves with a symlink; that symlink is version-pinned and brittle, so a proper fix was
still needed.
**Fix:** `resolveClaudeBin()` now tries, in order: an optional `CLAUDE_BIN` env override →
`command -v claude` (a real `PATH` lookup, the same way a user would resolve it themselves) →
the historical hardcoded path (for anyone relying on it) → the bare command name (letting
`spawn()` itself attempt a `PATH` search at process-start time as a last resort). The
`ENOENT` error handler now names three concrete fixes (install the CLI, put it on `PATH`, or
set `ANTHROPIC_API_KEY`/`ANTHROPIC_TOKEN`) instead of a bare `Spawn error: ...` message.
**Verified live:** restarted the local `claude-api-proxy` (port 3141) after the fix; confirmed
a full send → spawn → response round trip both directly against the proxy and through Vite's
`/api/claude` proxy path (the exact path the embedded iframe uses) — response: `"pong"` to a
scripted prompt, streamed via SSE as expected.
### API key provisioning research (user request: "load our key on my nodes, never in the repo/ISO")
Documented how AIUI resolves an AI provider credential today, and audited archi-dev-box
without ever printing a key value:
- **Client-side (browser):** `useSettingsStore().settings.claudeApiKey`, plain-text in
`localStorage['aiui-settings']`**flagging, not fixing:** this is a pre-existing violation
of AIUI's own `CLAUDE.md` invariant ("API keys ... never in localStorage"), out of this
round's scope. A separate encrypted IndexedDB vault also exists but is unreachable in the
embedded-in-Archy context specifically, since Archy's embed deliberately skips the
passphrase prompt that vault depends on (`App.vue`: `!archy.isEmbedded.value` guard).
- **Server-side proxy — already node-local on archi-dev-box, confirmed present, nothing to
provision:** `systemctl cat claude-api-proxy.service` shows a systemd unit
(`/etc/systemd/system/claude-api-proxy.service`) running `/opt/archipelago/claude-api-proxy.py`,
loading its credential via `EnvironmentFile=/var/lib/archipelago/secrets/claude-api-proxy.env`
— confirmed present, mode `0600`, owned `archipelago:archipelago` (file existence/permissions
checked via SSH; **no key value was ever printed, logged, or committed**). Listens on
`127.0.0.1:3142`; nginx's `/aiui/api/claude/` location proxies to it. **This already
satisfies "node-local, never in the repo/ISO" for archi-dev-box's production-style `/aiui/`
path — no new engineering needed there.**
- **Finding:** this live configuration has evolved past what `scripts/setup-aiui-server.sh` (in
this `archy` repo, cited in `02-AIUI-D14.md`'s Deployment Impact section) documents — that
script describes patching nginx to proxy directly to `api.anthropic.com` with a
header-injected key; the actual deployed config instead proxies to this separate
`claude-api-proxy.service`. Flagging the doc/reality drift, not fixing the script (out of
scope for this round).
- **Repo-local dev workflow (`pnpm dev`):** `packages/app/server/claude-proxy.ts` reads
`ANTHROPIC_API_KEY`/`ANTHROPIC_TOKEN` from a git-ignored `.env.local` (confirmed excluded by
AIUI's `.gitignore`; loader checks both the monorepo root and `packages/app/`) — already
satisfies "never in the repo" by construction; falls back to the local `claude` CLI when
absent (fixed above).
- **What's actually missing is provisioning, not a new mechanism** — both known deployment
shapes already have a working, repo/ISO-clean, node-local secret path:
- **archi-dev-box:** already provisioned; nothing for 02-08 to do for the Claude-key path
specifically. 02-08's actual remaining job is unchanged from the main summary above
(rebuild the AIUI image from `development`, redeploy) and does not touch this secret at
all (it lives at the nginx/systemd layer, independent of the AIUI container image).
- **Framework PT** (the user's other personal machine): needs a one-time, manual,
machine-local step only the user can do — create a git-ignored `.env.local` (repo root or
`packages/app/`) with their own `ANTHROPIC_API_KEY`, or run `claude setup-token` for the
OAuth/Max option. Not something I can perform without access to that machine or their
credential.
- Building a new orchestrator-level `generated_secrets` manifest entry for AIUI (the
`container::secrets`/`core/` pattern `CLAUDE.md` documents for other apps) was
**deliberately not pursued** — it would duplicate a mechanism that already works, and
touching the orchestrator is out of this phase's own D-12 constraint ("NOTHING touching
the orchestrator"). If a future phase wants to formalize archi-dev-box's manual
`claude-api-proxy.service` setup into the standard manifest/secrets pattern, that's a
distinct, larger piece of work belonging to its own plan.
### Commits (this addendum)
- **archy:** `faf4a75d``fix(02-07): loading overlay can never wedge the Chat/AIUI UI permanently`
(`neode-ui/src/views/Chat.vue`, `neode-ui/src/views/__tests__/chatAiuiEmbed.test.ts`)
- **AIUI** (`/home/archipelago/Projects/AIUI`, branch `development`, pushed upstream): `6e8b96d`
`fix(app): embed round-trip fixes — origin, dark bg, key fallback, CLI path`
(`services/archyBridge.ts`, `App.vue`, `composables/useArchy.ts`, `styles/main.css`,
`pages/ChatPage.vue`, `composables/useAI.ts`, `components/chat/ChatWindow.vue`,
`server/claude-proxy.ts`)
### Verification
- archy: `npm run test` — 95 files / 774 tests pass (774 = the prior 772 + 2 new timeout
tests); `npm run type-check` clean; `npm run build` clean.
- AIUI: `vue-tsc --noEmit` clean; `vitest run` — 332/335 pass (3 pre-existing, unrelated
failures — song-extraction count mismatches and a web-search system-prompt assertion —
confirmed present identically before this round's changes, not introduced by them);
production build (`vite build`) clean, with `chatExpanded`/`mobileChat` and the CLI-fallback
logic present in the built assets.
- Live: full send → spawn → response round trip confirmed via curl against both the proxy
directly and the `/api/claude` path the embedded iframe actually uses.
- Visual confirmation of the overlay/background/settings-modal fixes in an actual browser is
left to the user (this environment has no browser to drive) — the underlying root causes
were fixed with verified reasoning and, where checkable via HTTP, confirmed live.