Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,822 changes: 1,822 additions & 0 deletions docs/todos/2026-06-19-issue-148-viewer-vue-vite-frontend/plan.md

Large diffs are not rendered by default.

377 changes: 377 additions & 0 deletions docs/todos/2026-06-19-issue-148-viewer-vue-vite-frontend/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,377 @@
# Issue 148 Viewer Vue/Vite Frontend Spec

## Status

Approved source issue: GitHub issue #148 is open and locally valid.

Current phase: design/spec after arena synthesis.

Implementation status: not started.

## Goal

Introduce a typed Vue 3 + Vite frontend for the agentmemory viewer while preserving the existing viewer server/proxy/security model, package runtime behavior, and current visual direction.

## Non-Goals

- Do not replace iii-engine, REST endpoints, stream endpoints, auth, persistence, or the viewer proxy architecture.
- Do not rewrite all viewer tabs in one implementation slice.
- Do not make installed users run a Vite server.
- Do not add new production static asset routes in the first slice.
- Do not visually redesign the viewer from scratch.
- Do not target `https://github.com/rohitg00/agentmemory/` for branch, push, or PR work.

## Current Evidence

The viewer is currently a single static HTML template at `src/viewer/index.html`. The file contains inline CSS, inline browser JavaScript, hash tab routing, API calls, WebSocket logic, auth prompt handling, toasts, graph behavior, replay behavior, i18n application, and per-tab rendering.

`src/viewer/document.ts` loads that template from source or package output and injects:

- `__AGENTMEMORY_VIEWER_NONCE__`
- `__AGENTMEMORY_VERSION__`
- `__AGENTMEMORY_LOCALE__`

`src/viewer/server.ts` owns the runtime server/proxy boundary:

- loopback host resolution and optional non-loopback bind;
- Host header allowlist and DNS-rebinding defense;
- non-loopback inbound bearer validation for proxied API calls;
- `/`, `/viewer`, and `/agentmemory/viewer` viewer serving;
- `/favicon.svg`;
- CORS preflight handling;
- REST API proxying with outbound `AGENTMEMORY_SECRET` bearer.

The root build script currently copies `src/viewer/index.html` directly to `dist/viewer/index.html`.

Existing tests protect viewer behavior at several levels:

- CSP and nonce behavior in `test/viewer-security.test.ts`;
- server/proxy routing in `test/viewer-server-routing.test.ts`;
- bind/host behavior in `test/viewer-host.test.ts`;
- package asset contract in `test/build-package-contract.test.ts`;
- i18n document injection in `test/viewer-i18n.test.ts`;
- VM/string tests for dashboard, timeline, memories, graph, and token-savings behavior.

## Architecture

### Stable Server Boundary

The Node viewer boundary remains authoritative.

`src/viewer/server.ts` must not import Vue, Vite, browser source modules, or dev-server middleware. It continues to serve a static HTML document and proxy REST calls exactly as it does today.

`src/viewer/document.ts` remains small. Its responsibilities stay limited to template lookup, nonce creation, version injection, locale bundle injection, and returning the CSP generated by `buildViewerCsp()`.

`src/auth.ts` keeps the strict CSP shape unless a later explicitly reviewed slice proves a change is necessary. The target CSP posture is:

- `default-src 'none'`;
- nonce-backed script execution;
- `script-src-attr 'none'`;
- no `script-src 'unsafe-inline'`;
- `img-src 'self'`;
- no CDN fonts or external stylesheets.

### Frontend Boundary

Vue/Vite source lives under `src/viewer/app/` in the first slice. It is a browser-only layer and cannot import Node-only modules, `iii-sdk`, filesystem APIs, server modules, or runtime code that depends on them.

Recommended structure:

```text
src/viewer/
app/
index.html
main.ts
App.vue
env.ts
routes.ts
api/
client.ts
validators.ts
types.ts
i18n/
index.ts
realtime/
client.ts
events.ts
state/
viewer.ts
dashboard.ts
components/
ViewerChrome.vue
TabBar.vue
ViewerFooter.vue
ViewerAuthPrompt.vue
ToastHost.vue
FlagBanners.vue
EmptyState.vue
pages/
DashboardPage.vue
GraphPage.placeholder.vue
MemoriesPage.placeholder.vue
TimelinePage.placeholder.vue
SessionsPage.placeholder.vue
LessonsPage.placeholder.vue
ActionsPage.placeholder.vue
CrystalsPage.placeholder.vue
AuditPage.placeholder.vue
ActivityPage.placeholder.vue
ProfilePage.placeholder.vue
ReplayPage.placeholder.vue
styles/
tokens.css
chrome.css
primitives.css
vite.config.ts
```

Use Vue Composition API and plain typed module/composable state for the first slice. Do not add Pinia or Vue Router until a concrete migrated workflow needs them. The current route contract is hash-backed tab state, which a focused `routes.ts` module can preserve with less dependency and behavior churn.

### Routing

The first route implementation preserves existing hash routes:

- `#dashboard`
- `#graph`
- `#memories`
- `#timeline`
- `#sessions`
- `#lessons`
- `#actions`
- `#crystals`
- `#audit`
- `#activity`
- `#profile`
- `#replay`

Unknown, empty, or malformed hashes normalize to `dashboard`.

The route module may accept query-style hash parameters for migrated pages, such as `#memories?q=auth&type=pattern`, but it must not require server route changes. History-mode routing is out of scope.

### API Client

`src/viewer/app/api/client.ts` centralizes viewer API behavior now spread through the inline script.

It must preserve these contracts:

- REST base resolution from current location and existing `?port` compatibility.
- WebSocket base resolution from current location and optional `wsPort`.
- `/agentmemory/` prefixing for API calls.
- `Cache-Control: no-cache` for viewer API reads where current behavior depends on freshness.
- 10-second browser-side timeout.
- `sessionStorage` bearer token key `agentmemory-viewer-token`.
- Do not override an explicit caller-supplied `Authorization` header.
- 401 response opens the auth prompt.
- HTTP, network, and timeout errors produce safe escaped toast details.
- Browser code never learns or stores server secrets except the user-provided viewer bearer token.

Prefer small local validators/type guards in the browser first. The repo already has `zod`, but bundling Zod into the viewer is a bundle-size and behavior decision. Use Zod in the browser only if implementation evidence shows local validators are inadequate for important endpoint responses.

### Realtime Boundary

Plan a dedicated realtime module even if the first slice only stubs it.

The module should preserve:

- direct WebSocket stream connection where available;
- fallback stream behavior;
- bounded reconnect behavior;
- polling fallback;
- typed observation/event normalization before page components consume events.

Graph and replay behavior should not be migrated until focused tests cover current canvas/runtime behavior.

### Visual System

The migration preserves the current visual direction before extracting new primitives.

Move existing design tokens and chrome behavior into CSS files:

- paper/off-white and dark theme variables;
- black structural borders;
- red accent;
- compact uppercase tab labels;
- fixed header, tab bar, scrollable content, and footer behavior;
- bottom-right toast placement;
- current auth prompt placement;
- graph container sizing and controls when graph migrates.

Avoid UI frameworks and broad icon/design-system dependencies in the first slice.

## First Implementation Slice

The first slice is a hidden/preview Vue/Vite foundation. It does not replace the shipped production viewer by default.

Build:

- Add Vue/Vite source under `src/viewer/app/`.
- Add `src/viewer/vite.config.ts`.
- Add a `viewer:build` script that creates a preview artifact, such as `dist/viewer-next/index.html`.
- Keep the normal root `build` shipping the existing `dist/viewer/index.html` from `src/viewer/index.html` in the first slice.
- Add package-contract tests that make the preview status explicit.

Frontend:

- Implement `App.vue` shell with current chrome, tab list, theme state, auth prompt, toast host, flag banner area, and footer.
- Implement typed hash routing for the current tab ids.
- Implement typed environment/bootstrap helpers.
- Implement typed API client and local validators.
- Implement Dashboard as the first migrated page against fixtures.
- Add placeholder pages for unmigrated tabs.

Tests:

- Keep existing viewer server/security tests passing.
- Add focused tests for route parsing, API client behavior, local validators, Dashboard rendering, and fixture browser behavior.
- Browser fixture tests must fail on uncaught exceptions and CSP console violations.

Cutover is a later slice. It requires explicit evidence that the Vue artifact can become `dist/viewer/index.html` without loosening server/proxy/security behavior or losing tested viewer behavior.

## Cutover Criteria

Before the Vue app becomes the default shipped viewer:

- The package build emits `dist/viewer/index.html` from the Vite build.
- The emitted production viewer remains a single HTML document or otherwise has an explicitly reviewed static asset/CSP design.
- No new production static routes are required unless separately reviewed.
- `renderViewerDocument()` still injects nonce, version, and locale data.
- Existing CSP tests still pass with no `script-src 'unsafe-inline'`, no inline DOM event handlers, no external scripts/styles/fonts, and no `img-src data:`.
- Browser fixture tests cover initial render, tab navigation, auth prompt, toasts, Dashboard, Memories search route behavior, and any migrated realtime behavior.
- Graph and Replay either remain behind a tested legacy adapter or have focused browser tests.

Strict single-file artifact constraints for the preferred cutover:

- no external Vite asset references;
- no `modulepreload`;
- no source-map leakage;
- no new `/assets/*` route;
- placeholder-compatible nonced script output;
- inline CSS compatible with the existing CSP posture.

## Local Development

The viewer development loop must not require a running personal agentmemory daemon.

Target scripts:

- `viewer:dev`: start Vite against fixture data by default.
- `viewer:dev:real`: start Vite against an explicitly supplied local REST base such as `AGENTMEMORY_VIEWER_DEV_REST=http://127.0.0.1:3111`.
- `viewer:fixtures`: start the fixture REST server used by tests/dev.
- `viewer:test`: run viewer frontend unit/component/browser fixture tests.
- `viewer:build`: build the preview or production viewer artifact for the current slice.

Fixture mode must not read private daemon state, `~/.agentmemory`, secret files, package-manager credential config, or user environment dumps. Fixture data should be checked-in sanitized response shapes under `test/fixtures/viewer/`.

## Testing Strategy

### Existing Tests To Preserve

- `test/viewer-security.test.ts`
- `test/viewer-host.test.ts`
- `test/viewer-server-routing.test.ts`
- `test/viewer-i18n.test.ts`
- `test/build-package-contract.test.ts`

### New Focused Tests

- Route tests for hash parsing, invalid hash fallback, tab active state, and back/forward sync.
- API client tests with mocked `fetch` for bearer attachment, explicit authorization preservation, 401 auth prompt signaling, timeout abort, escaped error details, and REST/WS URL derivation.
- Validator tests for tolerant endpoint parsing.
- Dashboard component/page tests using fixtures for success, partial failure, malformed collections, missing IDs, escaped metadata, and empty states.
- Viewer build tests asserting preview artifact constraints: placeholders, no external Vite assets, no `modulepreload`, no source-map leakage, no inline DOM handlers.
- Browser fixture tests against local HTTP, not `file://`, using the real viewer server where feasible.

### Fixture Endpoints For Dashboard

The first fixture server should provide minimal deterministic responses for:

- `/agentmemory/health`
- `/agentmemory/config/flags`
- `/agentmemory/sessions`
- `/agentmemory/memories?latest=true&limit=500`
- `/agentmemory/graph/stats`
- `/agentmemory/audit?limit=5`
- `/agentmemory/semantic`
- `/agentmemory/procedural`
- `/agentmemory/relations`
- `/agentmemory/lessons`
- `/agentmemory/crystals`
- `/agentmemory/insights`

It should also support controlled 401, 500, timeout, and malformed-response cases.

## Dependency Intake

Expected dependency candidates:

- `vue`
- `vite`
- `@vitejs/plugin-vue`
- browser-test tooling only if the selected test runner requires it

Deferred unless concrete need appears:

- Pinia
- Vue Router
- UI component libraries
- graph/chart libraries
- icon packages
- single-file Vite plugins
- browser-bundled Zod

Before dependency changes:

- Verify current stable versions from official/package metadata.
- Pin direct dependencies exactly.
- Preserve `pnpm-workspace.yaml` hardening.
- Record dependency decisions as accept, reject, replace, pin differently, or defer.
- Review lifecycle scripts and build approvals before accepting them.
- Review lockfile churn for non-registry sources, unexpected maintainers, native install scripts, and broad transitive additions.

Required gates if dependency or lockfile surfaces change:

- OSV scan.
- Manual lockfile review.
- Semgrep for frontend/build/security-sensitive changes.
- Staged Gitleaks before commit.

## Verification For PR Readiness

Expected verification, unless blockers are recorded:

- Focused viewer Vitest tests.
- Viewer build command for the active slice.
- `corepack pnpm run build`.
- `corepack pnpm test`.
- `corepack pnpm run lint`.
- `git diff --check`.
- Semgrep.
- OSV if manifests/lockfiles changed.
- `gitleaks protect --staged --redact` before commit.

If `corepack pnpm exec`, `corepack pnpm run`, or `corepack pnpm test` is blocked by pnpm ignored-build hardening, follow repo instructions: run `corepack pnpm install --frozen-lockfile --ignore-scripts`, then retry the repo-native command. Do not approve dependency builds or treat direct `node_modules/.bin/*` calls as normal verification unless the pnpm command remains blocked and the blocker is recorded.

## Rejected Alternatives

Big-bang full viewer rewrite is rejected because current viewer behavior has accumulated many safety and regression tests, especially around auth, i18n, dashboard resilience, memories search, graph behavior, replay behavior, and escaping.

Using Vite as the production server is rejected because installed users run the packaged Node CLI, and `startViewerServer()` owns production security behavior.

Adding production static asset routes in the first slice is rejected because it expands server responsibilities and requires CSP/cache/path-hardening review.

History-mode routing is rejected because current server routes and API path layout are hash-route friendly.

Pinia and Vue Router are rejected for the first slice because typed composables and hash routing cover the current requirement with less dependency risk.

Using `v-html` or quick `innerHTML` ports is rejected for migrated Vue components. User/API strings should render through Vue text interpolation or explicit escaping.

## Terminal Workflow Contract

This issue is currently valid. If implementation reaches PR merge readiness, the terminal approval request must explicitly bundle:

1. merging the PR into `origin/main`;
2. archiving this Codex thread after successful merge.

After a successful merge, call `set_thread_archived({ archived: true })` before final handoff. If merge approval, merge, or archival is unavailable, report a blocker.

If later evidence shows the issue is invalid, stale, duplicate, or already fixed, the closure approval request must explicitly bundle GitHub issue closure and archiving this Codex thread after successful closure.
Loading
Loading