Files
archy/.planning/phases/02-ui-performance/02-AIUI-D14.md
T
archipelagoandClaude Fable 5 71b2703213 docs(02-07): resolve AIUI source location and record real D-14 contract
AIUI is now cloned at /home/archipelago/Projects/AIUI
(git.tx1138.com/lfg2025/AIUI, development branch — 17 commits ahead
of main with zero unique main commits). Replaces the exhausted-search
conclusion with the real embed-parameter contract read from source
and records the D-14a/D-14b implementation: chatCollapsed's initial
state now honors a new ?chatExpanded param (chat.ts), and ChatPage.vue
re-asserts mobileTab='chat' on mount when ?mobileChat is present,
guarding against carry-over from AIUI's own module-singleton content-
panel state on an internal remount. Both are static, unconditional
query params — no runtime-varying URL input.

AIUI-side commit lives on feat/d14-embed-defaults (900c0b9) in the
local clone only — anonymous push to origin returned 403 Forbidden.
Needs a maintainer with push rights to land it before any deployed
AIUI build can honor these two parameters.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:19:17 -04:00

12 KiB

AIUI D-14 Embed Contract

Written: 2026-07-30 (Phase 02 Plan 07, Task 1) Amended: 2026-07-30 (Phase 02 Plan 07, Task 2 resume) — the orchestrator supplied the missing fact this document's original Task 1 pass could not obtain from within this environment: AIUI's source repository location. This amendment replaces the "search exhausted" conclusion below with the real contract read directly from source, and records the D-14a/D-14b implementation actually shipped.

Source Location

AIUI's source is now located and was cloned for this plan:

  • Repository: https://git.tx1138.com/lfg2025/AIUI (anonymous git ls-remote succeeds; clone succeeded read-only, push is 403 Forbidden anonymously — see Deployment Impact).
  • Cloned to: /home/archipelago/Projects/AIUI — the exact sibling path neode-ui/package.json's dev:mock script and scripts/setup-aiui-server.sh both already expected (<parent-of-archy>/AIUI), so no changes to either script were needed once the clone existed.
  • Branch used: development, not the repository's default main. Verified via git log main..origin/development vs git log origin/development..main: development is 17 commits ahead of main (including a run of fix(app): security-hardening commits — origin validation, rate limiting, CSP, SSRF protection, dev-server auth — and the feature/content work this session's grep relies on) with zero commits unique to main. main is simply stale here; development is the actual working branch. (AIUI's own CLAUDE.md documents the intended name as dev, but the real remote branch is named development — a doc/reality mismatch noted for AIUI's own maintainers, out of scope to fix from this repository.)
  • ThinkPad build server: confirmed reachable this session at 100.69.68.39 (Tailscale), per the orchestrator-supplied fact. Not needed once the git clone succeeded — recorded here for completeness since Task 1's original search treated it as the fallback path.
  • Hosting note (context only, not acted on): the user noted AIUI hosting may later be ported to "our main vps2." No action taken on this in this plan; recorded for whoever picks up AIUI's deployment next.

Original Task 1 search (superseded, kept for history)

The initial pass searched the sibling path (<parent-of-archy>/AIUI — not present at that time), a broader filesystem sweep (found only prebuilt dist/ output under /opt/archipelago/web-ui*/aiui, this repo's apps/aiui/demo/aiui, and installer-ISO copies — never source), and the ThinkPad at 192.168.1.116 (ping/ssh both failed at that time — host unreachable from this environment on that address/path). That search was genuinely exhausted with the facts available at the time; the blocker was resolved externally (see Amended note above), not by anything different this pass did on the same inputs.

Embed Parameter Contract

neode-ui/src/views/Chat.vue's aiuiUrl computed (lines ~74-82) sends, unchanged by this plan:

  • embedded=true — always present.
  • hideClose=true — always present.
  • &mockArchy=1&seed=1 — only when IS_DEMO (build-time env var, stable for the life of the bundle).

Read from AIUI source (development branch, commit 9176324 before this plan's changes):

  • embeddedpackages/app/src/main.ts:34-35. Read once, before router init, via new URLSearchParams(window.location.search).has('embedded') (presence-only check) and stored on window.__AIUI_EMBEDDED__ so it survives SPA navigation. Matches the deployed build's minified behavior exactly (independently confirmed by the diligence pass on the built bundle before source access — see history above).
  • mockArchypackages/app/src/composables/useArchy.ts:83-96. Read via import.meta.env.VITE_MOCK_ARCHY === 'true' || new URLSearchParams(...).has('mockArchy'), but the mock-data branch is only entered if (useMock && !embedded) — i.e. mutually exclusive with embedded in this codebase today. This confirms the inconsistency flagged during the original diligence pass: Chat.vue's demo mode sends embedded=true and mockArchy=1 together, but AIUI only honors mockArchy when NOT embedded. Pre-existing, unrelated to D-14, not fixed here (out of this plan's file scope — flagged for whoever owns the demo-mode AIUI experience next).
  • hideClose — confirmed absent from AIUI's source entirely (grep -rn "hideClose" packages/ returns nothing). AIUI does not read or honor this parameter at all today. Not part of D-14; not investigated further.
  • seed — confirmed absent from AIUI's source entirely (no URLSearchParams read anywhere references it). Chat.vue's &seed=1 in demo mode currently does nothing on the AIUI side. Pre-existing, unrelated to D-14, not fixed here.

D-14a

Default: the AIUI chat should start EXPANDED rather than requiring the user to expand it.

  • Root cause, found in source: packages/app/src/stores/chat.ts:132 (before this plan's change): const chatCollapsed = ref(localStorage.getItem('aiui-chat-collapsed') !== 'false') — defaults to collapsed (true) on any fresh/unset localStorage, i.e. exactly the behavior CONTEXT.md describes ("requiring the user to expand it"). chatCollapsed gates ChatWindow.vue's PromptIndex (collapsed quick-pick view) vs. the full virtualized message list (expanded view) — confirmed at ChatWindow.vue:35 / ChatWindow.vue:249. Once a user manually expands, the choice is written back to localStorage (chat.ts:196-198) and persists — so this bug only bites on a genuinely fresh localStorage, matching the reported "starts collapsed" default.
  • Status: implemented, on the AIUI side, gated by a new query parameter?chatExpanded. Not something the deployed build already read (see original diligence pass); a source change was required and made.
  • Which side implements it: AIUI reads the flag; neode-ui sends it. Implementation: chatCollapsed's initial ref now checks new URLSearchParams(window.location.search).has('chatExpanded') first, defaulting to false (expanded) when present, before falling back to the existing localStorage check. Never written back to localStorage — it is a one-time initial-state override for this session only, so a later manual collapse/expand while embedded still persists exactly as before, and the standalone (non-embedded) app's own default is completely untouched (it never sends ?chatExpanded, so its localStorage-based behavior is unchanged).
  • Why a new parameter rather than reusing the existing embedded flag: embedded is already overloaded (transparent background, mock-Archy gating, passphrase-prompt skip); tying chatCollapsed's default to it directly would mean every embedded session forces expanded with no way to decouple the two concerns later. A dedicated flag keeps the contract self-documenting and matches this plan's acceptance criteria (the flag name must be discoverable in the built neode-ui bundle).

D-14b

Default: on a mobile viewport, AIUI should open on its CHAT view rather than its context view.

  • Root cause, found in source: packages/app/src/pages/ChatPage.vue:469 already initializes mobileTab to 'chat' (const mobileTab = ref<'chat' | 'content' | 'context'>('chat')) — so a truly fresh mount already defaults correctly. The reported bug is a carry-over, not a wrong default: panelOpen, selectedFilm/selectedBook/etc., and hasDetailOpen (computed from those) are module-scope singleton refs in useContentPanel.ts (ref() calls at module top level, not inside the composable function), shared across every mount of ChatPage.vue within the same AIUI SPA session. If a user previously opened a detail item (making hasDetailOpen true, which ChatPage.vue's own watch(hasDetailOpen, ...) at line ~507 flips mobileTab to 'context' for), and ChatPage.vue later remounts within the same iframe session (e.g. an internal AIUI navigation to /guide and back) without those refs resetting, hasDetailOpen is still true on the new mount and the "opens on chat" default never gets a chance to hold — it's immediately superseded by state carried over from before. Note this is now less likely to surface at all going forward, since Task 2's URL-stability fix (see D-14 URL stability, below) keeps the iframe itself from reloading on an Archipelago tab switch — but it can still happen from AIUI's own internal navigation, independent of Archipelago.
  • Status: implemented, on the AIUI side, gated by a new query parameter?mobileChat.
  • Which side implements it: AIUI reads the flag; neode-ui sends it. Implementation: a new onMounted hook in ChatPage.vue re-asserts mobileTab.value = 'chat' when ?mobileChat is present and isMobile.value is true, at mount time only. This does not touch the panelOpen/hasDetailOpen watchers that drive normal tab-switching in response to actual user taps after the initial paint — those are unchanged, so a user can still navigate to the content/context tabs exactly as before; only the initial tab on a (re)mount is affected.
  • Why AIUI decides from its own viewport rather than neode-ui passing a computed boolean: per Task 2's URL-stability constraint, aiuiUrl cannot embed a reactive viewport read (see below). ?mobileChat is sent unconditionally and is unconditionally interpreted by AIUI against its own isMobile computed (windowWidth.value < 1024, read from the iframe's own window.innerWidth) — this is the "let AIUI decide from its own viewport" option named in the plan's Task 2 action, chosen over resolving neode-ui's outer viewport once at setup time, since AIUI's iframe has its own layout breakpoint and is the side that actually owns the mobile/desktop layout switch.

D-14 URL Stability (how neode-ui sends both flags)

Both ?chatExpanded and ?mobileChat are appended unconditionally in Chat.vue's aiuiUrl computed — static strings, not derived from any runtime-varying input (no viewport read, no connection state, no timestamp). This preserves the property Task 2 exists to protect: the iframe src stays byte-identical across re-renders, viewport resizes, and deactivate/reactivate cycles, so the AIUI panel never reloads on an Archipelago tab switch.

Deployment Impact

apps/aiui/manifest.yml still declares a prebuilt container image (localhost/archipelago-aiui:latest, pull_policy: always) — this plan's AIUI-side change does not alter that; shipping it still requires:

  1. Merging the AIUI-side commit. Changes are committed locally in the cloned checkout at /home/archipelago/Projects/AIUI on branch feat/d14-embed-defaults (based on development), commit 900c0b9. Anonymous push to origin failed with 403 Forbidden (git push origin feat/d14-embed-defaults) — this account has read-only access to git.tx1138.com/lfg2025/AIUI. The branch and commit exist only in this local checkout until someone with write access pushes it (or opens a PR from a fork/mirror they do have write access to) and merges it into development.
  2. Rebuilding AIUI's production bundle from the merged source: cd AIUI/packages/app && VITE_BASE_PATH=/aiui/ npx vite build (per scripts/setup-aiui-server.sh's documented prerequisite).
  3. Rebuilding the localhost/archipelago-aiui:latest container image from that build output (a local/on-node image tag, not a registry pull).
  4. Redeploying/restarting the aiui app container on the target node(s) so pull_policy: always picks up the new local image.

Per D-15, deployment is dev pair only this phase, no OTA — plan 02-08 owns the deploy step under that constraint, once the AIUI-side commit is actually mergeable (i.e., once a maintainer with push rights lands feat/d14-embed-defaults on development and it's rebuilt/redeployed). Until that push/merge happens, the deployed AIUI build on any node (including archi-dev-box) will NOT carry these two parameters — neode-ui's side is fully forward-compatible with that (both flags are inert query-string additions the current deployed AIUI build simply ignores, per the confirmed absence of hideClose-style unknown params breaking anything), but the two visual defaults (expanded start, mobile-chat-first start) will not be observable against any currently-deployed AIUI build until the AIUI commit ships.