Archipelago — open-source initial import

This commit is contained in:
Archipelago
2026-08-12 10:55:49 +00:00
commit adcd0f7ce8
1826 changed files with 403825 additions and 0 deletions
@@ -0,0 +1,325 @@
---
phase: 02-ui-performance
plan: 07
type: execute
wave: 4
depends_on: ["02-04"]
files_modified:
- .planning/phases/02-ui-performance/02-AIUI-D14.md
- neode-ui/src/views/Chat.vue
- neode-ui/src/views/__tests__/chatAiuiEmbed.test.ts
autonomous: false
requirements: [PERF-02]
user_setup:
- service: aiui
why: "D-14's two UX defaults are implemented in AIUI, whose source is a sibling repository not present in this checkout. The neode-ui side can only pass the flags; something must read them."
dashboard_config:
- task: "Confirm where the AIUI source checkout lives and make it reachable from the machine executing this phase (project notes record the ThinkPad at .116 as the primary build server; neode-ui's dev:mock script and scripts/setup-aiui-server.sh both expect it at ../../AIUI relative to neode-ui)"
location: "Developer's own machine / build server"
must_haves:
truths:
- "Switching away from the Chat tab and back leaves the AIUI panel loaded — the iframe is not re-created and does not reload"
- "The iframe src is stable for the lifetime of the Chat view instance: no runtime-varying value (viewport width, connection state, timestamp) is part of the URL"
- "The AIUI chat opens in its expanded state rather than requiring the user to expand it (D-14a)"
- "On a mobile viewport, AIUI opens on its chat view rather than on its context view (D-14b)"
- "The mechanism carrying both defaults across the neode-ui to AIUI boundary is recorded in writing, including which side implements which half"
- "The existing postMessage origin validation still rejects messages from any origin other than the AIUI URL's own"
- "The connected state established by AIUI's ready message is not reset when the Chat tab is deactivated, since that message is not re-sent on re-entry"
- statement: "Neither D-14 default regresses AIUI's desktop layout or its non-embedded standalone mode"
verification: backstop
prohibitions:
- "MUST NOT achieve perceived speed by removing behavior or hiding state — no suppressing the refresh indicator, no dropping a fetch the surface needs, no disabling a feature to win the metric"
- "MUST NOT widen what the embedded AIUI iframe is granted or trusted as a side effect of passing presentation flags — this phase changes how AIUI opens, never what it may reach"
artifacts:
- path: ".planning/phases/02-ui-performance/02-AIUI-D14.md"
provides: "The recorded AIUI source location, its embed-parameter contract, and which side implements each D-14 default"
- path: "neode-ui/src/views/Chat.vue"
provides: "Stable embed URL carrying the D-14 presentation flags"
- path: "neode-ui/src/views/__tests__/chatAiuiEmbed.test.ts"
provides: "Assertions that the embed URL is stable and carries both flags, and that origin validation is unchanged"
key_links:
- from: "neode-ui/src/views/Chat.vue"
to: "the AIUI application"
via: "embed flags appended to the iframe URL query string built by the aiuiUrl computed"
pattern: "aiuiUrl"
- from: "neode-ui/src/views/Chat.vue"
to: "vue onActivated / onDeactivated"
via: "the message listener and ContextBroker follow activation, established in plan 02-04"
pattern: "onDeactivated"
---
<objective>
Finish the Chat tab: make the embedded AIUI panel survive tab switches without reloading,
and land the two AIUI UX defaults CONTEXT.md folded into this phase (D-14).
Purpose: PERF-02 and D-14. Chat's per-switch cost is unlike every other tab's — it is not
an RPC fan-out, it is a full re-creation of an embedded application. Plan 02-04 made the
Chat view instance survive; this plan makes sure nothing in the URL construction quietly
undoes that. Adding a query parameter that varies at runtime would change the iframe's
`src`, force a reload on every re-render, and hand back the entire win — which is exactly
the risk D-14's two flags introduce, since one of them is about mobile.
D-14 is a locked decision and is delivered here in full. It has one genuine external
dependency: AIUI's source is a sibling repository that RESEARCH.md verified is not present
in this checkout, so the receiving half of the contract cannot be read from `archy` alone.
Task 1 resolves that before any code is written, and Task 2 will not start until it has.
Output: a recorded neode-ui-to-AIUI embed contract, a stable embed URL carrying both
defaults, and a Chat tab that keeps its loaded panel across tab switches.
</objective>
<execution_context>
@$HOME/.claude/gsd-core/workflows/execute-plan.md
@$HOME/.claude/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02-ui-performance/02-CONTEXT.md
@.planning/phases/02-ui-performance/02-RESEARCH.md
@.planning/phases/02-ui-performance/02-PATTERNS.md
@.planning/phases/02-ui-performance/02-04-SUMMARY.md
@.planning/codebase/CONVENTIONS.md
@CLAUDE.md
</context>
<tasks>
<task type="auto">
<name>Task 1: Locate AIUI and record the embed contract</name>
<files>.planning/phases/02-ui-performance/02-AIUI-D14.md</files>
<read_first>
- `neode-ui/src/views/Chat.vue` lines 61-125 — the `aiuiUrl` computed at line ~74 builds the query string today (`embedded=true`, `hideClose=true`, and in demo mode `mockArchy=1&seed=1`), and `onAiuiMessage` at line ~92 validates the message origin against that URL before accepting a `ready` message.
- `neode-ui/package.json` — the `dev:mock` script expects AIUI at `../../AIUI` relative to `neode-ui` and degrades to a placeholder when it is absent.
- `neode-ui/scripts/setup-aiui-server.sh` — the other place the sibling-repo path is encoded.
- `apps/aiui/manifest.yml` — describes a prebuilt container image (`localhost/archipelago-aiui:latest`) with no source in this checkout.
- `neode-ui/src/services/contextBroker.ts` — the existing postMessage channel between neode-ui and AIUI; if AIUI honours a message-based control rather than query parameters, this is where that would ride.
- `.planning/phases/02-ui-performance/02-RESEARCH.md` open question 1 — the full statement of what is known and unknown about AIUI's location and parameter support.
</read_first>
<action>
Settle where AIUI's source is and what it accepts, and write the answer down before
any code is written.
Search for the checkout: check `../../AIUI` relative to `neode-ui` (that is,
`<parent-of-archy>/AIUI`), then search the filesystem more broadly for a directory
containing AIUI's own `package.json`. Project notes record the ThinkPad at `.116` as
the primary build server, so if the source is not on this machine it may be there —
check whether it is reachable before concluding it is unavailable.
If the source is found, grep it for how it reads embed configuration: search for the
existing parameters `embedded`, `hideClose`, `mockArchy` and `seed` to find the
parameter-parsing site, then determine whether anything already controls (a) the
chat's expanded versus collapsed initial state and (b) the initial view on a mobile
viewport (chat versus context). Record the exact parameter names, accepted values,
and the file and line where each is read.
If the source is not found, record that plainly with the paths searched. Do not
invent a parameter name and ship it — a flag nothing reads is a change that looks
done and does nothing.
Also inspect the running container path: `apps/aiui/manifest.yml` points at a prebuilt
image, so an AIUI-side change requires a rebuild and republish of that image. Record
what shipping an AIUI-side change would actually involve, because D-15 restricts this
phase to the dev pair with no OTA.
Write `.planning/phases/02-ui-performance/02-AIUI-D14.md` recording: the source
location (or the searched paths and the conclusion); the embed-parameter contract as
it exists today; for each of D-14's two defaults, whether it is already supported,
needs a new AIUI-side parameter, or needs a postMessage control; which side implements
each half; and what deploying the AIUI half requires. Commit it.
This document is the contract Task 2 builds against and is the artifact a future agent
reads instead of re-running this search.
</action>
<verify>
<automated>D=/home/archipelago/Projects/archy/.planning/phases/02-ui-performance/02-AIUI-D14.md; test -f "$D" || exit 1; for s in '## Source Location' '## Embed Parameter Contract' '## D-14a' '## D-14b' '## Deployment Impact'; do grep -qF "$s" "$D" || { echo "missing: $s"; exit 1; }; done; echo OK</automated>
</verify>
<acceptance_criteria>
- `.planning/phases/02-ui-performance/02-AIUI-D14.md` exists and contains all five required headings
- `## Source Location` states either an absolute path to the AIUI checkout or the list of paths searched and the conclusion that it is unreachable
- `## Embed Parameter Contract` lists every embed parameter AIUI reads today with the file and line where each is parsed, or states that the parsing site could not be inspected and why
- `## D-14a` and `## D-14b` each state one of: already supported by parameter X, needs a new AIUI-side parameter, or needs a postMessage control — and name which side implements it
- `## Deployment Impact` states what shipping the AIUI half requires, given that `apps/aiui/manifest.yml` points at a prebuilt image and D-15 limits this phase to the dev pair with no OTA
- No parameter name appears in `neode-ui/src/views/Chat.vue` at the end of this task: `git diff --name-only HEAD -- neode-ui/src/views/Chat.vue | wc -l` prints 0
- The document is committed
</acceptance_criteria>
<done>Where AIUI lives, what it accepts, which side implements each D-14 default, and what deploying it costs are all written down and committed — or the search is documented as exhausted so a human can point at the checkout.</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Stable embed URL carrying both D-14 defaults</name>
<precondition>`.planning/phases/02-ui-performance/02-AIUI-D14.md` exists, is committed, and its `## Source Location` section names a reachable AIUI checkout rather than recording the search as exhausted (Task 1 outcome)</precondition>
<files>neode-ui/src/views/Chat.vue, neode-ui/src/views/__tests__/chatAiuiEmbed.test.ts</files>
<read_first>
- `.planning/phases/02-ui-performance/02-AIUI-D14.md` — the contract written by Task 1; it names the parameters and which side implements each default
- `neode-ui/src/views/Chat.vue` lines 61-125 — the `aiuiUrl` computed, the `onAiuiMessage` origin check, and the `onActivated` / `onDeactivated` handling added by plan 02-04
- `.planning/phases/02-ui-performance/02-04-SUMMARY.md` — the bucket table for `Chat.vue`, so this task does not undo the placements made there
- `neode-ui/src/services/contextBroker.ts` — if Task 1 concluded a postMessage control is needed instead of a query parameter, this is the channel it rides
- `neode-ui/src/composables/useDemoIntro.ts``IS_DEMO` is already a build-time-ish input to `aiuiUrl`; confirm whether it can change at runtime before treating it as stable
</read_first>
<behavior>
- `aiuiUrl` returns the same string across re-renders of a mounted Chat view, including after a deactivate and reactivate cycle
- `aiuiUrl` does not change when the viewport is resized across the mobile breakpoint
- The returned URL carries the D-14a expanded-state flag and the D-14b mobile-initial-view flag exactly as named in `02-AIUI-D14.md`
- The existing `embedded` and `hideClose` parameters and the demo-mode parameters are unchanged
- `onAiuiMessage` still rejects a message whose origin differs from the embed URL's origin
- `aiuiConnected` remains true across a deactivate and reactivate cycle, since AIUI's ready message is not re-sent
</behavior>
<action>
Implement the neode-ui half of D-14 using exactly the parameter names and values
recorded in `02-AIUI-D14.md`. Append them in the existing `aiuiUrl` computed,
following the string-concatenation convention already there, for both the
`VITE_AIUI_URL` branch and the production/demo branch.
The load-bearing constraint is URL stability. The Chat view's instance now survives
tab switches, and the iframe only keeps its loaded state while its `src` stays
byte-identical. Any input to `aiuiUrl` that can change at runtime — a reactive
viewport width, a connection flag, a timestamp, a random value — would change the
`src`, force a full AIUI reload on the next render, and give back the entire benefit
of keeping the tab alive. So D-14b's mobile default must not be expressed as a
reactive viewport read in this computed. Pass a mobile-initial-view flag whose value
is fixed for the view instance and let AIUI decide from its own viewport, or resolve
the viewport once at setup time into a non-reactive constant. Record which of the two
you chose and why in the SUMMARY.
Do not change the origin-validation logic in `onAiuiMessage`. These are presentation
flags; nothing here widens what the embedded application may reach, and the origin
check is what keeps that true.
Do not reset `aiuiConnected` on deactivate. AIUI sends its `ready` message once after
load; a reset would leave the panel showing a disconnected state forever after the
first tab switch. Plan 02-04 already flagged this — confirm it holds.
If `02-AIUI-D14.md` records that a default is implemented on the AIUI side, make that
change in the AIUI checkout too, keeping it as small as the default itself: change the
initial state, do not restructure AIUI's layout. Commit it in that repository and
record the commit reference in this plan's SUMMARY. Deploying it follows D-15 — dev
pair only, no OTA — and plan 02-08 owns the deploy.
Create `neode-ui/src/views/__tests__/chatAiuiEmbed.test.ts` covering the six behaviors
above. The stability assertions are the important ones: read `aiuiUrl` twice across a
simulated resize and across a deactivate/reactivate cycle and assert string equality.
</action>
<verify>
<automated>cd neode-ui && npm run test -- src/views/__tests__/chatAiuiEmbed.test.ts && npm run test && npm run type-check && npm run build</automated>
</verify>
<acceptance_criteria>
- `npm run test -- src/views/__tests__/chatAiuiEmbed.test.ts` exits 0 with all six behaviors covered
- A test asserts `aiuiUrl` is string-equal before and after a simulated viewport resize across the mobile breakpoint
- A test asserts `aiuiUrl` is string-equal before and after a deactivate/reactivate cycle
- A test asserts the returned URL contains both D-14 flag names exactly as recorded in `02-AIUI-D14.md`, plus the pre-existing `embedded=true` and `hideClose=true`
- A test asserts a message from a foreign origin does not set `aiuiConnected`
- A test asserts `aiuiConnected` survives a deactivate/reactivate cycle
- `npm run test` exits 0, `npm run type-check` exits 0, `npm run build` exits 0
- The built bundle carries the new flags: `grep -rl "hideClose" web/dist/neode-ui/assets | head -1` prints a file, and the same file also matches the D-14 flag names
- If an AIUI-side change was required, the SUMMARY records its repository path and commit reference
</acceptance_criteria>
<done>The AIUI panel opens expanded, opens on chat on mobile, and its embed URL is provably stable across resize and reactivation so the iframe never reloads on a tab switch.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 3: Confirm the AIUI panel persists and both D-14 defaults hold on desktop and mobile</name>
<what-built>
The Chat tab's AIUI panel now survives tab switches without reloading, and the two
D-14 defaults are in place: the chat opens expanded, and on a mobile viewport AIUI
opens on its chat view rather than its context view. The embed URL is proven stable
across viewport resize and across deactivate/reactivate, which is what keeps the
iframe from reloading.
</what-built>
<how-to-verify>
1. From the repo root run `./scripts/dev-start.sh` and open the :8100 dev preview
pointed at archi-dev (password `password123`).
2. Open the Chat tab and wait for the AIUI panel to finish loading.
3. Expected: the chat is already expanded — you should not have to expand it yourself.
4. Switch to another main tab, then back to Chat. Expected: the panel is still loaded
exactly as you left it, including anything you typed. No loading spinner, no
flash, no scroll back to the top of the conversation.
5. Resize the browser window across the mobile breakpoint while on the Chat tab.
Expected: the panel does not reload.
6. In a mobile viewport (device toolbar, or on a phone against the same preview), open
the Chat tab fresh. Expected: it opens on the CHAT view, not on the context view.
7. Switch away and back on mobile. Expected: still on chat, still loaded, still
expanded.
8. Confirm the AIUI panel still functions — send a message and get a response — so the
presentation flags did not disturb the connection.
</how-to-verify>
<resume-signal>Type "approved", or describe what you saw: which step, desktop or mobile, what happened instead.</resume-signal>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| neode-ui → embedded AIUI iframe | Configuration crosses out of the trusted app into an embedded application via the URL query string |
| embedded AIUI iframe → neode-ui | AIUI posts messages back into the host window; only same-origin-as-the-embed-URL messages may be honoured |
| AIUI container image → fleet nodes | An AIUI-side change ships as a rebuilt prebuilt image, not as a neode-ui asset |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-02-05 | Spoofing | `onAiuiMessage` origin validation in `Chat.vue` | medium | mitigate | Task 2 forbids touching the origin check and requires a test asserting a foreign-origin message does not set `aiuiConnected`; the flags added are presentation-only |
| T-02-18 | Elevation of Privilege | Embed flags widening what the iframe is trusted with | high | mitigate | This plan's second prohibition scopes the change to how AIUI opens, never what it may reach. No permission, token, credential or capability parameter is added; only initial-view and expanded-state flags recorded in `02-AIUI-D14.md` |
| T-02-19 | Information Disclosure | Sensitive values leaking into an iframe URL, which appears in referrer headers and browser history | high | mitigate | The two flags are boolean-shaped presentation values. Task 1's contract document is the review point: if a proposed parameter carries anything identity- or session-bearing, it must not ship in the query string |
| T-02-20 | Tampering | An AIUI-side change reaching the fleet outside the release train | medium | mitigate | D-15 restricts this phase to the dev pair with no OTA; Task 1 records what shipping the AIUI half requires and plan 02-08 owns the deploy under that constraint |
| T-02-SC | Tampering | npm/pip/cargo installs | high | mitigate | No package-manager installs are in scope on the neode-ui side. If the AIUI checkout requires an install to build, that install runs in AIUI's own repository under its own lockfile; do not add a dependency to `neode-ui/package.json` in this plan |
</threat_model>
<artifacts_this_phase_produces>
## Artifacts this phase produces
Created or changed by this plan — new API, not drift:
- `.planning/phases/02-ui-performance/02-AIUI-D14.md` — the recorded AIUI source location and embed-parameter contract
- `neode-ui/src/views/__tests__/chatAiuiEmbed.test.ts`
- Two new embed query parameters on the AIUI iframe URL, named in `02-AIUI-D14.md` (D-14a expanded state, D-14b mobile initial view)
- Possibly a corresponding change in the AIUI sibling repository, referenced by commit in the SUMMARY
Created elsewhere in Phase 02: `shouldKeepAlive()`, `KEEP_ALIVE_PATHS`, `KEEP_ALIVE_MAX`,
`DashboardRouterView.vue`, `RefreshIndicator.vue`, `resources.clearAll()`,
`useCachedResource.test.ts`, `keepAliveTabs.test.ts`, `keepAliveLifecycle.test.ts`,
`meshTabCache.test.ts`, `serverTabCache.test.ts`, `homeTabCache.test.ts`,
`secondaryScreenCache.test.ts`, `resourcesClear.test.ts`,
`e2e/perf/{surfaces,measure,surface-perf.spec}.ts`,
`.planning/phases/02-ui-performance/{02-FINDINGS.md,02-PERF-BASELINE.json,02-PERF-AFTER.json}`.
</artifacts_this_phase_produces>
<assumptions_and_flagged_items>
## Assumptions & Flagged Items
- **PERF-02 edge-probe row (spec-less fallback):** returned `unclassified` / `unresolved`. FLAGGED, not auto-backstopped and not dropped; surfaced here for human review. Resolved in substance by this plan's `must_haves.truths`, with the AIUI-layout-regression truth carried as a `verification: backstop` marker because it depends on an application whose source is outside this repository.
- **FA-E (RESEARCH open question 1, unresolved at plan time):** AIUI's source is not present in this checkout — `neode-ui`'s `dev:mock` script and `scripts/setup-aiui-server.sh` both expect it at `../../AIUI`, and RESEARCH.md verified no such directory exists on the machine that ran the research. Project notes record the ThinkPad at `.116` as the primary build server, so it may live there. This is a genuine missing-information constraint, not a difficulty judgment. Task 1 resolves it and Task 2 carries a `<precondition>` that halts if it could not be resolved — D-14 is a locked decision and is not deferred or reduced, it is blocked on a fact only the developer can supply.
- **Open:** whether either D-14 default is already supported by an existing AIUI parameter is unknown. `Chat.vue` already passes `embedded`, `hideClose`, `mockArchy` and `seed`, so a parameter mechanism exists; whether it covers expanded-state and mobile-initial-view is what Task 1 determines.
- **Open:** whether D-14b is better expressed as a fixed flag AIUI interprets against its own viewport, or as a viewport resolved once at setup, is decided in Task 2 and recorded. Both satisfy the URL-stability constraint; a reactive viewport read does not.
</assumptions_and_flagged_items>
<verification>
- `cd neode-ui && npm run test` exits 0
- `cd neode-ui && npm run type-check` exits 0
- `cd neode-ui && npm run build` exits 0 and the D-14 flags appear in `web/dist/neode-ui/assets`
- `.planning/phases/02-ui-performance/02-AIUI-D14.md` is committed with all five headings
- The human-verify checkpoint is approved on both desktop and mobile viewports
</verification>
<success_criteria>
- The AIUI panel survives tab switches with no reload, proven by a string-equality test on the embed URL and confirmed by eye
- The chat opens expanded and, on mobile, opens on the chat view
- The embed URL contains no runtime-varying value
- Origin validation is unchanged and the connected state survives deactivation
- The AIUI-side contract is written down, so no future agent repeats the search
</success_criteria>
<output>
Create `.planning/phases/02-ui-performance/02-07-SUMMARY.md` when done. It MUST record:
the AIUI source location; the exact parameter names and values shipped; which side
implements each D-14 default and, if AIUI-side, the repository path and commit reference;
how D-14b was expressed without introducing a runtime-varying URL input; and what
deploying the AIUI half requires under D-15.
</output>