23 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | user_setup | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-ui-performance | 07 | execute | 4 |
|
|
false |
|
|
|
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.
<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_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 Task 1: Locate AIUI and record the embed contract .planning/phases/02-ui-performance/02-AIUI-D14.md - `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. 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.
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.
<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 contractneode-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'smust_haves.truths, with the AIUI-layout-regression truth carried as averification: backstopmarker 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'sdev:mockscript andscripts/setup-aiui-server.shboth 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.116as 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.vuealready passesembedded,hideClose,mockArchyandseed, 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>
<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>