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>
This commit is contained in:
archipelago 2026-07-30 18:19:17 -04:00
parent c10f415c6b
commit 71b2703213

View File

@ -1,140 +1,186 @@
# AIUI D-14 Embed Contract
**Written:** 2026-07-30 (Phase 02 Plan 07, Task 1)
**Purpose:** Record where AIUI's source lives, what its embed parameter contract is today,
which side implements each of D-14's two UX defaults, and what deploying the AIUI half
would require — so Task 2 (and any future agent) does not re-run this search.
**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 **not present on this machine and not reachable via the noted build
server at this time.** Search performed and exhausted:
**AIUI's source is now located and was cloned for this plan:**
1. **Sibling-repo checks** (the two places `neode-ui`'s own tooling expects it):
- `neode-ui/package.json`'s `dev:mock` script: `cd ../../AIUI && pnpm dev` (relative to
`neode-ui/`, i.e. `<parent-of-archy>/AIUI`) — degrades to
`echo '[AIUI] Not found at ../../AIUI — chat will show placeholder'` when absent.
- `scripts/setup-aiui-server.sh`: `AIUI_DIST="$PROJECT_DIR/../AIUI/packages/app/dist"`
(relative to the `archy` repo root, i.e. the same sibling directory), with a
documented prerequisite: "AIUI must be built locally first:
`cd AIUI/packages/app && VITE_BASE_PATH=/aiui/ npx vite build`".
- Checked directly: `/home/archipelago/Projects/AIUI` does **not** exist.
`ls /home/archipelago/Projects/` shows only `archy` and various `archy-*`
worktree/backup checkouts, `botfights`, and two unrelated Indeehub projects — no
`AIUI` directory.
- **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.
2. **Broader filesystem search** for any directory literally named `AIUI` (case-insensitive)
containing AIUI's own `package.json`: `find / -maxdepth 6 -iname "AIUI" -type d` returned
only deployed **build output** directories — `/opt/archipelago/web-ui*/aiui` (dozens of
timestamped backup copies), `/opt/archipelago/apps/aiui` (this repo's manifest dir, no
source), `archy`'s own `apps/aiui` and `demo/aiui` (built static assets, mirrors of the
deployed bundle), and installer-ISO copies under `image-recipe/`. None of these are
AIUI's source repository — they are prebuilt `dist/` output, deployed or bundled.
### Original Task 1 search (superseded, kept for history)
3. **ThinkPad build server reachability** (project memory notes `.116` as the primary build
server, where AIUI may live if not here): both ping and SSH to `192.168.1.116` failed —
`ping` returned "Destination Host Unreachable" from the local gateway (`192.168.1.66`),
and `ssh -o BatchMode=yes -o ConnectTimeout=3` timed out after 3s with "Connection timed
out" on port 22. The build server is not reachable from this execution environment right
now (machine likely off or off this network segment).
**Conclusion:** The search is exhausted for this session. AIUI's source cannot be read from
`archy` alone, confirming RESEARCH.md's Open Question 1. Task 2's `<precondition>` — which
requires this section to name a reachable checkout — is **not met**; this plan halts before
any code targeting the AIUI side is written, per the plan's own instruction not to invent a
parameter and ship it.
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) constructs the iframe URL
today with:
- `embedded=true` — always present when a URL is built.
- `hideClose=true` — always present when a URL is built.
- `&mockArchy=1&seed=1` — only appended when `IS_DEMO` is true.
`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).
Because AIUI's own source could not be inspected, the exact file/line where each parameter
is *parsed inside AIUI* cannot be recorded from source, per the instruction not to invent
what wasn't verified. As a diligence step (not a substitute for source access), the
currently-**deployed** AIUI build's static JS bundle was inspected instead — this is a
built artifact, not source, so variable/function names are minified and the full parsing
logic cannot be confirmed with certainty from it. Findings, treated as supporting evidence
only:
Read from AIUI source (`development` branch, commit `9176324` before this plan's changes):
- Bundle inspected: `/opt/archipelago/web-ui/aiui/assets/index-Lh5NfTCq.js` (byte-identical
copy also present in this repo at `demo/aiui/assets/index-Lh5NfTCq.js` and
`web/dist/neode-ui/../aiui`-style deploy mirrors — same build hash everywhere checked).
- `embedded` — read via `new URLSearchParams(window.location.search).has("embedded")`,
stored as `window.__AIUI_EMBEDDED__`. This is a **presence-only** check (any value,
including none, counts as embedded=true). Confirmed present in the bundle.
- `mockArchy` — read via `.has("mockArchy")`, but only takes effect when the embedded flag
is *false* (`&&!embedded` in the minified logic). This does not match how `Chat.vue`'s
demo mode currently sends both `embedded=true` and `mockArchy=1` together — flagging as
an existing inconsistency in the deployed build, unrelated to D-14 and out of this plan's
scope to investigate or fix.
- `hideClose`**zero** occurrences of the literal string anywhere in this build's JS. No
evidence this deployed build reads or honors it at all.
- `seed` — not exhaustively searched this pass; irrelevant to D-14, not pursued further.
No parameter resembling an expanded-state or mobile-initial-view control (see D-14a/D-14b
below) was found anywhere in this bundle.
- **`embedded`** — `packages/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).
- **`mockArchy`** — `packages/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.
- **Status: needs a new AIUI-side parameter** (or a postMessage control, per the plan's
fallback path). No string resembling `expandedState`, `chatExpanded`, `expandChat`, or
similar chat-UI-state control was found in the deployed bundle. The one `expanded` hit in
the bundle is inside a third-party media-player library's ARIA `aria-expanded` attribute
code — unrelated to chat panel state.
- **Which side implements it:** AIUI (the receiving half must read the flag and render
itself in the expanded state on load). neode-ui's half — appending the agreed flag name
to the iframe URL — is inert and forward-compatible even before AIUI reads it, but cannot
usefully ship without the AIUI-side change, and the AIUI-side change itself is blocked:
its source is unreachable this session (see Source Location above).
- **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.
**Default:** on a mobile viewport, AIUI should open on its CHAT view rather than its context
view.
- **Status: needs a new AIUI-side parameter** (or a postMessage control). No string
resembling `mobileView`, `initialView`, `view=chat`, `view=context`, or similar was found
in the deployed bundle.
- **Which side implements it:** AIUI (it owns the chat-view/context-view routing and would
need to read an initial-view flag or a fixed mobile-view-preference flag on load).
Same blocked status as D-14a — the AIUI-side change cannot be made without source access.
- **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` declares:
```yaml
container:
image: localhost/archipelago-aiui:latest
pull_policy: always
```
`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:
AIUI ships as a **prebuilt container image**, not as neode-ui-bundled static assets served
alongside the rest of the dashboard. Shipping any AIUI-side source change requires, in
order:
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.
1. Access to the AIUI source repository (currently unreachable — see Source Location).
2. Rebuilding AIUI's production bundle, per the documented prerequisite in
`scripts/setup-aiui-server.sh`: `cd AIUI/packages/app && VITE_BASE_PATH=/aiui/ npx vite build`.
3. Rebuilding the `localhost/archipelago-aiui:latest` container image from that fresh build
output (the manifest names a `localhost`-scoped image tag, implying a local/on-node
build or push into each target node's local image store — not a registry pull from
outside the fleet).
4. Redeploying/restarting the `aiui` app container on the target node(s) so
`pull_policy: always` picks up the new local image tag.
Per D-15, this phase restricts deployment to the **dev pair only, with no OTA** — so even
once source access becomes available, an AIUI-side D-14 change would be built and deployed
only to the designated dev nodes (per plan 02-08, which owns the deploy step), not pushed
through the normal fleet release train.
**Bottom line:** until AIUI source access is available (e.g. the ThinkPad build server at
`.116` comes back online and is confirmed to hold the AIUI checkout, or the repository is
otherwise made reachable), the AIUI half of D-14 cannot be implemented, built, or deployed
at all. This is a genuine blocking external dependency, not a scope reduction — D-14 is not
dropped, it is blocked pending a fact only the developer can supply.
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.