diff --git a/.planning/phases/02-ui-performance/02-07-SUMMARY.md b/.planning/phases/02-ui-performance/02-07-SUMMARY.md index ea1c64f5..54ee1fd5 100644 --- a/.planning/phases/02-ui-performance/02-07-SUMMARY.md +++ b/.planning/phases/02-ui-performance/02-07-SUMMARY.md @@ -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.