Skip to content

Phase 5 client split: extract @openpalm/client SPA + ui-kit#559

Merged
itlackey merged 62 commits into
mainfrom
claude/ui-runtime-modes-phases-1-4
Jul 8, 2026
Merged

Phase 5 client split: extract @openpalm/client SPA + ui-kit#559
itlackey merged 62 commits into
mainfrom
claude/ui-runtime-modes-phases-1-4

Conversation

@fwdslsh-dev

Copy link
Copy Markdown
Collaborator

Summary

Implements Phase 5 of the UI runtime modes refactor: splits the monolithic UI into two independent artifacts—a shared presentational library (@openpalm/ui-kit) and a new client-side SPA (@openpalm/client)—while refactoring the host UI (@openpalm/ui) to use the extracted kit and serve as the control plane only.

Key Changes

New Packages:

  • packages/ui-kit/ — Presentational components, icons, and design tokens (Stillness theme) extracted from packages/ui. Consumed by both host and client apps.
  • packages/client/ — Standalone SPA (SvelteKit + static adapter) for direct connection management and chat. Runs on port 3890, talks directly to OpenCode/guardian endpoints with per-connection credentials. No proxy, no host APIs.

Host UI Refactoring (packages/ui):

  • Extracted theme tokens and common components to @openpalm/ui-kit; imports via @import '@openpalm/ui-kit/theme/tokens.css'
  • Moved /admin/* routes to /host/* and /api/admin/* to /api/host/* (Phase 4 cleanup)
  • Moved /admin/endpoints/api/connections (connection management API)
  • New RuntimeContext v2 (lib/runtime-context.svelte.ts) — single source of truth for capability resolution (replaces legacy feature flags)
  • New resolveLanding() — unified landing matrix for root/splash routing (Phase 3)
  • New lib/server/features.ts — computes server-side runtime context from request event
  • Refactored AssistantTab.svelte to use new /api/assistant/persona endpoint

Client App (packages/client):

  • Pure offline-first SPA: connection store backed by IndexedDB (async storage abstraction)
  • Direct transport layer (lib/transport/index.ts) — talks to connection's base URL with stored credentials
  • /connections — connection CRUD + active selection (no host API calls)
  • /chat — session list/create + message send via direct transport (SSE event wiring)
  • Boot flow (lib/boot.ts) — loads runtime-config.json, seeds locked/default connections, probes health
  • Zero-dependency static server (bin/serve.mjs) spawned by CLI/Electron

CLI & Electron Integration:

  • New openpalm admin command (Phase 1.5, openpalm admin — serve the host UI from the CLI (host-ui mode, Phase 1.5) #556) — serves host UI with OP_ENABLE_ADMIN=1
  • New openpalm client-serve command — runs client static server standalone
  • Electron main.ts starts both UI and client servers; window prefers client at http://127.0.0.1:3890/chat
  • Assistant container entrypoint updated to serve client instead of full UI

Test Coverage:

  • 40+ RED test files pinning contracts for all phases (P5a–P5e)
  • Hygiene tests: ui-kit has no app coupling, no admin code in client, no feature flags in components
  • Transport tests: request shaping, SSE parsing, health probes
  • Connection store tests: CRUD, active selection, offline reads
  • Landing resolution tests: root/splash routing logic

Documentation:

  • docs/technical/ui-runtime-modes-plan.md — revised with Phase 5 as-built (Phases 5–6 re-scoped)
  • docs/technical/phase-5-completion-guide.md — P5a–P5e completion checklist
  • docs/technical/ui-client-split-assessment.md — decision record for client split
  • docs/technical/ui-route-map.md — host app route inventory (Phase 4)

Implementation Details

  • Design tokens: Stillness theme CSS custom properties live in ui-kit/src/lib/theme/tokens.css; both apps @import it
  • Storage abstraction: Client connection store uses interface-based backend (IndexedDB in browser, in-memory in tests)
  • **Async throughout

https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9

claude added 30 commits July 6, 2026 05:33
Add ui-client-split-assessment.md: decision record ratifying the
host/client split — packages/ui stays the host control plane;
chat/connections extract to a thin static packages/client (+ shared
packages/ui-kit); PWA ships with two install origins (localhost via
harness/CLI, official hosted URL with central TLS).

Revise ui-runtime-modes-plan.md accordingly:
- Phases 1-4 unchanged; new Phase 1.5 (openpalm admin / host-ui mode)
- Phases 5-6 re-scoped to the client extraction; new Phase 6.5
  (guardian TLS + CORS workstream, gates the phone story)
- [as-landed] corrections: skeleton resolution is a five-strategy
  chain retaining Electron extraResources; tools use the baked
  package.json + bun update pattern (supersedes tools.json design);
  entrypoint install/start_ui already scaffolded, with the
  OPENCODE_API_URL vs OP_OPENCODE_URL wiring gap recorded
- Simplicity guardrails: TLS never a manual user task on default
  install paths; two UI packages max; pairing is paste-or-scan

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
TDD red suite for RuntimeContext v2 (plan §6.1-§6.4, §4.3):

- lib/server/features.vitest.ts: computeServerRuntimeContext env→hostMode
  mapping (OP_UI_HOST_MODE > OP_INSIDE_ELECTRON > OP_ENABLE_ADMIN >
  pwa-static baseline), ServerRuntimeContext contract shape (version 2,
  security, activeConnectionMode), and per-hostMode serverCapabilities.
  RED: 15 tests (computeServerRuntimeContext is not exported yet).
  CHARACTERIZATION (passes pre-change, must stay green): 3 tests pinning
  computeFeatureFlags().admin under OP_INSIDE_ELECTRON=1 / OP_ENABLE_ADMIN=1.

- lib/runtime-context.vitest.ts: resolveCapabilities full matrix per §6.2
  (electron=ALL; host:stack:read+browser=all minus electron-only;
  connections:single→chat+assistant-settings; pwa baseline→connections+
  chat+pwa:install; grantedCapabilities union+dedupe; standalone-pwa
  restriction per §4.2), hasCapability via the runtimeContext store, and
  §4.3 matrix rows end-to-end. RED: whole file (module does not exist).

- routes/api/runtime/server.vitest.ts: GET /api/runtime is public (200
  with no/garbage session cookie), returns JSON ServerRuntimeContext with
  the contract-version handshake field. RED: whole file (+server.ts does
  not exist).

- routes/layout.server.vitest.ts: layout load returns serverRuntimeContext
  (RED: 3 tests) and keeps the features.admin derived alias
  (CHARACTERIZATION: 3 tests, green pre-change).

Verified red for the right reasons: 21 failed tests / 2 failed suites, all
"Cannot find module" / "not a function" / missing-key assertions; server
project otherwise at baseline (940 passed incl. the 6 characterization
tests). ui:check reports 9 errors, all in these test files, all missing
Phase 1 exports — resolves when the implementation lands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
…ime (#509)

Phase 1 of the ui-runtime-modes plan (docs/technical/ui-runtime-modes-plan.md
§6.1-§6.4): replace the single features.admin boolean with a capability-based
ServerRuntimeContext while keeping zero behavior change for existing routes.

- lib/types.ts: UiHostMode, Capability, ServerRuntimeContext (contract
  version 2), ClientDisplayMode, ConnectionKind, ActiveConnectionContext,
  ClientContext, RuntimeContext per plan §6.1. FeatureFlags kept as the
  documented derived alias.
- lib/server/features.ts: computeServerRuntimeContext(event). hostMode
  resolution: OP_UI_HOST_MODE (explicit) > OP_INSIDE_ELECTRON=1 →
  electron-host > OP_ENABLE_ADMIN=1 → host-ui > pwa-static baseline.
  Per-mode serverCapabilities (host modes carry host:*, never
  connections:single; assistant-container is connections:single + chat +
  assistant-settings:*; pwa-static is connections:* + chat + pwa:install).
  computeFeatureFlags() is now derived from hostMode — identical results
  for every env combination in use today.
- lib/runtime-context.svelte.ts: resolveCapabilities(serverCaps, clientCtx)
  verbatim per plan §6.2 — the ONLY place capability logic lives — plus the
  reactive runtimeContext store, hasCapability(cap) (UX only; APIs enforce
  capabilities server-side), and initializeRuntimeContext().
- lib/client-context.ts: detectClientDisplayMode() per plan §6.3,
  browser-side only.
- routes/+layout.server.ts: returns serverRuntimeContext alongside the
  features alias. routes/+layout.svelte: initializes the client context in
  onMount (client-only — never server-computed, and never mutating the
  module-level store during SSR) and derives effectiveCapabilities.
- routes/api/runtime/+server.ts: public GET (no auth) returning the
  ServerRuntimeContext — the contract-version handshake for future
  hosted clients.

Turns the 21-test red suite from 483b4bf green without modifying it; the 6
characterization tests (features.admin alias) stay green. Verified against a
live dev server: /api/runtime returns 200 with no/garbage session cookie in
pwa-static and host-ui modes; / and /login routing unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
TDD red suite for `openpalm admin` (host-ui mode; plan Phase 1.5, §4.1,
§8.3) in packages/cli/src/commands/admin.test.ts:

- Registration + help. RED: 2 tests — 'admin' missing from the main.ts
  subCommands map; `renderUsage(mainCommand)` COMMANDS table has no admin
  row.
- Serve mode, driven through the command via citty runCommand against a
  seeded temp OP_HOME (no module mocks: real lib + cli-state; fetch
  answers /health ready and fails registry pulls non-fatally; Bun.spawn
  captured so nothing real launches). RED: 3 tests, all failing with
  "Cannot find module .../src/commands/admin.ts":
  * spawned UI child env carries OP_ENABLE_ADMIN=1 + OP_UI_HOST_MODE=
    host-ui, binds 127.0.0.1 with pinned loopback ORIGIN, prints the URL,
    opens the browser by default;
  * loopback enforcement — OP_ALLOW_REMOTE_SETUP=1 is ignored (loopback
    HOST/ORIGIN, no HOST_HEADER/PROTOCOL_HEADER) and neutralized in the
    child env (isRemoteSetupAllowed(childEnv) === false) so the respawned
    `openpalm ui` child and the UI's remote-setup relaxations cannot
    re-derive a remote bind; --no-open spawns no opener;
  * with an empty OP_HOME (not installed) the command still serves the
    admin-enabled UI — the UI's existing guard lands on /setup; the CLI
    must not demand `openpalm install` or reimplement wizard logic.
- CHARACTERIZATION (green pre-change, must stay green): 2 tests pinning
  the bare `openpalm` serve path's spawn env — loopback + pinned ORIGIN
  with NO admin/mode env introduced, and OP_ALLOW_REMOTE_SETUP still
  producing the 0.0.0.0 + HOST_HEADER remote bind. Loopback-always is
  scoped to the admin command only.

Verified red for the right reasons: 5 fail / 2 pass in the new file;
full CLI suite 82 pass / 6 fail = these 5 + the 1 known uid-0
install-flow env failure. biome lint: no findings in the new file;
packages/cli tsc --noEmit exit 0 (red-state safe via computed-specifier
dynamic import); ui:check 0 errors 0 warnings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
Register a new `admin` subcommand that serves the existing UI through the
existing startUIServer supervisor with the admin capability enabled in the
spawned UI child (OP_ENABLE_ADMIN=1 + OP_UI_HOST_MODE=host-ui), prints the
URL, and opens the browser (reusing the existing openBrowser helper;
--no-open suppresses it).

Loopback-only ALWAYS (plan §8.3): admin mode ignores OP_ALLOW_REMOTE_SETUP
when deriving the bind (HOST=127.0.0.1 with a pinned loopback ORIGIN, no
HOST_HEADER/PROTOCOL_HEADER) and neutralizes the flag in the child env
(OP_ALLOW_REMOTE_SETUP=0) so neither the respawned `openpalm ui` child nor
the UI server's own remote-setup relaxations can re-derive a remote bind.
The bare serve path keeps its current behavior (characterization tests pin
it): loopback by default, 0.0.0.0 + Host-header origin under
OP_ALLOW_REMOTE_SETUP.

On a machine with no install the command still serves — resolveServeState()
tolerates a not-installed OP_HOME so the UI's existing setup guard takes
over; the CLI reimplements no wizard logic. No new auth mechanism: the UI's
existing op_session password auth applies unchanged.

Makes the Phase 1.5 red suite (admin.test.ts) green without modifying it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
Update the how-it-works harness UI section: Electron (electron-host mode),
`openpalm admin` (host-ui mode, loopback-only, refuses non-loopback bind
config), and the dev-only OP_ENABLE_ADMIN=1 escape hatch. Drops the stale
`openpalm ui serve` reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
TDD red suite for connection management out of /admin (plan
ui-runtime-modes-plan.md Phase 2, issue #486):

- routes/api/connections/server.vitest.ts: /api/connections is guarded
  server-side by the connections:manage capability (plan §6.4, §8.5) —
  403 JSON in assistant-container mode even with a VALID admin session
  (capability-based, not session-based), 200 in host-ui mode, reachable
  in pwa-static mode (no host-admin mode required), lists the
  env-derived default connection, never serializes stored passwords;
  POST guarded the same way (403 assistant-container / 201 host-ui).
  RED: all 8 tests — "Cannot find module .../+server.js" (route module
  does not exist yet; loaded via a computed-specifier dynamic import so
  svelte-check stays green while red).

- lib/server/endpoints-connection-kind.vitest.ts: ConnectionKind
  defaulting when reading a legacy endpoints.json fixture (plan §6.6) —
  records without a kind key default to 'remote-opencode' on read
  (listEndpoints + getActiveEndpoint); the env-derived default entry is
  'local-opencode'. RED: 4 tests ("expected undefined to be
  'remote-opencode'/'local-opencode'" — kind read via a cast helper so
  svelte-check stays green). CHARACTERIZATION (green pre-change, must
  stay green): 3 tests — an explicitly persisted kind passes through on
  read (unknown on-disk keys already survive; Phase 2 must not strip or
  override it), writes keep the on-disk schema keys activeId/endpoints
  (never 'connections'), and legacy records load with no data
  migration.

- routes/admin/endpoints/page.vitest.ts: /admin/endpoints becomes a
  redirect alias to /connections for 0.13.0 (Phase 2 step 5), pinned as
  a universal load in +page.ts throwing redirect() with location
  '/connections' and a 3xx status — same convention as the existing
  routes/+page.ts → /splash redirect. RED: both tests ("Cannot find
  module .../+page.js"; computed-specifier dynamic import keeps
  svelte-check green).

- lib/endpoints-state-hygiene.vitest.ts: source-level hygiene test for
  the untangle step (Phase 2 step 6) — the connections store (follows
  the file across the endpoint→connection rename: endpoints-state or
  connections-state) must have NO static, type, side-effect, or dynamic
  import matching chat-state or chat/*. RED: 1 test (found
  './chat/chat-state.svelte.js'). CHARACTERIZATION (green): 1 test — the
  store module exists in $lib.

Verified red for the right reasons: server project 15 failed / 992
passed across 106 files; the only failing files are these four, all
failures are module-resolution or missing-feature assertions (no syntax
errors). ui:check: 0 errors 0 warnings (red-state safe via
computed-specifier dynamic imports and cast helpers). biome lint:
baseline unchanged (16 warnings 1 info, none in the new files); eslint
clean on the new files. CLI suite 87/1 (the 1 = known uid-0
install-flow env failure); lib suite 845 pass / 10 skip / 17 fail (the
known uid-0 set) — parity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
…ints store (#486)

Phase 2 step 1-2 of docs/technical/ui-runtime-modes-plan.md: the internal
model is renamed endpoint → connection (plan §6.6) and ConnectionEntry
gains an optional `kind` field. Kind is defaulted at READ time only —
legacy user records (no kind on disk) read as 'remote-opencode', the
env-derived default entry and the Electron-local entry as
'local-opencode', and an explicitly persisted kind passes through
untouched. endpoints.json keeps its name and on-disk schema keys
(activeId/endpoints); no data migration, files are never rewritten on
read.

The endpoint-language exports remain as aliases of the renamed
functions/types for the pre-Phase-2 /admin routes and tests; they go
away with /admin/* in Phase 4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
Phase 2 step 3 (plan §6.4 API namespace table): /api/connections,
/api/connections/[id] and /api/connections/active delegate to the same
lib/server/endpoints.ts module as the legacy /admin/endpoints routes but
are guarded by a new requireCapability() helper instead of the
host-admin namespace. The guard is enforced SERVER-SIDE (plan §8.5) and
is capability-based, not session-based: assistant-container mode gets a
403 JSON envelope even with a valid admin session, while host-ui,
electron-host and pwa-static (which advertise connections:manage) pass
through to the existing admin-session auth. requireCapability() only
reads the capability set advertised by computeServerRuntimeContext —
capability logic itself stays in one place (plan §8.6).

Stored connection passwords are never serialized (hasPassword only),
matching the /admin/endpoints publish() contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
…-events (#486)

Phase 2 step 6 (plan §6.11): break the endpoints-state ↔ chat-state
bidirectional import — a hard prerequisite for the Phase 5 client
extraction. The connections store no longer imports chat modules; it
emits through the new $lib/connection-events channel and the chat store
subscribes from its own side:

- registerActivationGuard: chat vetoes mid-generation switches with the
  same user-facing message as before ("Wait for the current reply…").
- onConnectionActivated: chat.onEndpointChanged() runs on activation,
  awaited by the emitter so a failed handoff still rolls the activeId
  back (behavior preserved from the direct call).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
…dpoints (#486)

Phase 2 steps 3-5 (plan ui-runtime-modes-plan.md):

- New /connections page — the existing endpoint management UI restyled
  minimally in connection language, backed by the capability-guarded
  /api/connections/* routes so it is reachable in every mode that
  advertises connections:manage (host-ui, electron-host, pwa-static),
  without the host-admin /admin namespace.
- lib/api/endpoints.ts rewritten in connection language and pointed at
  /api/connections/*; the connections store consumes it. AssistantEndpoint
  stays as a type alias until Phase 5 relocates the module.
- /admin/endpoints is now a 0.13.0 redirect alias: a universal load in
  +page.ts throws redirect(302, '/connections') (same convention as the
  routes/+page.ts → /splash redirect); the sibling JSON +server routes
  stay for out-of-process callers this release. The hooks /admin gate
  honors the alias in non-admin modes too, so stale links land on
  /connections instead of /chat.
- Chat garden + settings drawer links now point at /connections.
  (EndpointList's link is left on /admin/endpoints deliberately — its
  browser test pins the href and cannot run in this environment; the
  alias redirect keeps it working. Migrate with Phase 3 chrome work.)
- hooks.server.ts exempts /connections from launch routing; the runtime
  context route pointers advertise connections: '/connections' for
  host-capable and pwa-static modes.

Host mode behavior is unchanged: host admin stays loopback-only and the
admin session still gates the connection API after the capability check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
…486)

The Phase 2 step-3 link migration (/admin/endpoints -> /connections)
covered chat/+page.svelte and SettingsDrawer.svelte but missed the
identical link in the chat EndpointSwitcher popover, leaving it
dependent on the 0.13.0 alias redirect that Phase 4 removes. Update the
href and the colocated browser test's assertion to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
TDD red suite for capability-driven landing + navigation + chrome
untangle (plan ui-runtime-modes-plan.md Phase 3, §6.5; issue #555):

- lib/resolve-landing.vitest.ts: resolveLanding(ctx, launchState) full
  §6.5 matrix — host:setup rows (migration pending → /attention with
  precedence over every local state; not_installed/setup_incomplete →
  /setup; installed_offline → /admin; installed_broken →
  /admin?tab=diagnostics; running → /chat), assistant-container →
  /chat always (local state, connections, and migration ignored),
  pwa-static → /connections/new when 0 connections else /chat, and the
  gate being CAPABILITY-driven (host-ui ctx without host:setup in
  effectiveCapabilities falls through to /chat). Pins that
  resolveLanding is PURE (reads ctx.effectiveCapabilities, never the
  global store) so hooks.server can call it per-request. Phase 3 keeps
  the host landing at /admin — HOST_ADMIN_LANDING in the test mirrors
  the implementation's TODO constant; Phase 4 flips both to /host.
  RED: all 17 tests ("resolveLanding module not found" — module does
  not exist; computed-specifier dynamic import keeps svelte-check
  green while red).

- hooks.server.landing.vitest.ts: root + /splash document navigations
  route through the resolved landing — / with not_installed → /setup
  and installed_offline → /admin (both currently /splash); /splash is
  no longer exempt and redirects pre-auth to the resolved landing
  (/chat when running, /admin when offline, /setup when
  not_installed); the /splash route files are deleted; /attention
  exists. RED: 7 tests (redirect location mismatches — currently
  /splash or /login?redirectTo=%2Fsplash — plus the two route-split
  file assertions). CHARACTERIZATION (green pre-change, must stay
  green): 1 test — / with a healthy running stack redirects to /chat.

- lib/features-admin-hygiene.vitest.ts: no .svelte component reads
  features.admin / featuresService.admin (plan §8.6; the alias may
  survive only in hooks.server pending Phase 4). RED: 1 test (offender
  lib/components/chrome/Navbar.svelte:70). CHARACTERIZATION (green):
  1 walker-sanity test.

- lib/components/chrome/chrome-untangle-hygiene.vitest.ts: the chrome
  module(s) the admin surface mounts (resolved from
  routes/{admin,host}/+{page,layout}.svelte imports, so the test
  follows the Phase 3 chrome split and the Phase 4 rename) import no
  chat components or chat stores. RED: 1 test (4 offenders:
  Navbar.svelte → components/chat/{EndpointSwitcher,SessionPicker,
  VoiceControl}.svelte + $lib/chat/navigation.js). CHARACTERIZATION
  (green): admin surface mounts a chrome module.

- routes/chat/page-imports.vitest.ts: chat/+page.svelte does not
  import the $lib/api.js barrel (direct domain-client imports like
  $lib/api/chat.js stay allowed). RED: 1 test (offender '$lib/api.js').
  CHARACTERIZATION (green): the chat page exists.

Verified red for the right reasons: server project 27 failed / 1011
passed across 111 files; the only failing files are these five and
every failure is a missing-module or missing-feature assertion (no
syntax errors). ui:check: 0 errors 0 warnings (red-state safe).
biome: clean on the new files (baseline elsewhere unchanged); eslint:
clean on the new files. CLI suite 87 pass / 1 known uid-0 env failure;
lib suite 845 pass / 10 skip / 17 known uid-0 failures — parity.

No production code changed. Playwright e2e specs are deliberately not
added here: browsers cannot run in this container and the Phase 3
route split they would smoke-test lands with the implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
…sh into /attention (#555)

Plan ui-runtime-modes-plan.md Phase 3 steps 1-2 (§6.5):

- New $lib/resolve-landing.ts: pure resolveLanding(ctx, launchState) — reads
  ctx.effectiveCapabilities (never the global store) so hooks.server can call
  it per-request. LaunchState derives from the launch-status probes that fed
  /splash (LocalStackState + connections + a migration gate). The host
  landing stays '/admin' behind the HOST_ADMIN_LANDING TODO constant; Phase 4
  flips that one value to '/host'.
  One documented delta from the §6.5 pseudocode: the host not_installed row
  lands on /chat when an accessible connection exists, preserving the
  authoritative #440 remote-only rule (nothing local to set up is no reason
  to block a working remote) — mirrors the pwa-static row's shape.

- New $lib/server/landing.ts: resolveRequestLanding(event) collects the
  launch facts (moved from hooks.server.ts, same 5s cache; _resetLaunchCache
  is re-exported from hooks for the existing test import site), resolves
  capabilities with the 'browser' baseline (display mode is client-only by
  design, §6.3 — resolveCapabilities() remains the only capability logic,
  §8.6), and runs resolveLanding(). The pre-Phase-3 nuance is preserved: an
  interrupted install with running containers lands on /setup, not chat.

- hooks.server.ts: the launch-routing guard now redirects document
  navigations through the resolved landing instead of the binary
  recommendedRoute → '/chat'|'/splash' table. /splash is no longer exempt:
  the ROUTE is removed and the path 302s pre-auth to the resolved landing for
  this release only.

- Route split: routes/splash/* deleted; routes/attention/+page.svelte is the
  migration/blocking surface (nothing produces migration 'pending' yet — the
  gate is wired ahead of the first blocking migration); /connections/new
  aliases to /connections?new=1 for the pwa-static zero-connections landing;
  routes/+page.ts's static /splash redirect becomes +page.server.ts resolving
  the same landing for client-side navigations.

- hooks.server.vitest.ts: 'first-run document navigation routes to splash
  instead of setup' pinned the pre-Phase-3 landing; the ratified Phase 3
  matrix (red-suite hooks.server.landing.vitest.ts pins the same scenario)
  sends not_installed to /setup, so the expectation is updated — the
  companion rule (not_installed + accessible remote → /chat) still passes
  unmodified. e2e/setup-guard.pw.ts's loose landing regex swaps splash for
  the new landings (Playwright cannot run in this container; not executed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
…m chat (#555)

Plan ui-runtime-modes-plan.md Phase 3 steps 3-4:

- chrome/Navbar.svelte becomes the chat-free shell: brand + the chat/host
  utility button + theme toggle, with a children snippet for surface
  controls. Destinations come from runtimeContext.routes and visibility from
  hasCapability('host:stack:read') — the legacy admin feature flag is gone
  from every component (hygiene test lib/features-admin-hygiene.vitest.ts);
  the alias survives only in server code (hooks.server.ts /
  +layout.server.ts, whose layout-data contract stays pinned) pending
  Phase 4. lib/features.svelte.ts (client featuresService) is deleted.

- New chrome/ChatNavbar.svelte: the conversation-surface composition — shell
  plus EndpointSwitcher, SessionPicker, VoiceControl, ModeSwitch and the
  session-aware brand href. /advanced and /connections mount it; /admin
  mounts the shell directly, so the admin surface no longer drags chat
  components/stores into the host bundle (chrome-untangle-hygiene.vitest.ts,
  the #555 prerequisite for the Phase 5 client extraction).

- chat/+page.svelte imports probeChatBackend from the $lib/api/chat.js domain
  client instead of the $lib/api.js barrel, which re-exports every admin
  domain client (routes/chat/page-imports.vitest.ts).

- routes/admin/+page.svelte honors the ?tab=diagnostics landing deep-link
  (installed_broken → Systems tab); routes/connections/+page.svelte opens the
  add form when landed via /connections/new (?new=1).

Behavior deltas that follow from the untangle: the admin surface no longer
renders voice controls or the advanced-mode switch (they are chat chrome),
and its back-to-chat button targets the plain chat route rather than a
session-restoring path (session paths require chat-store imports).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
)

Carried from Phase 0's outstanding items into Phase 3, where the route
structure it documents actually changed (plan ui-runtime-modes-plan.md
Phase 3 step 5): the §6.5 landing table as implemented, the guard
inventory (auth, admin gate, requireAdmin, requireCapability, setup
localhost, host/origin), every page route with surface + guard, the API
namespaces, and the #555 chrome composition map. Notes that Playwright
suites need a real browser + Docker and cannot run in the sandboxed CI
container.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
TDD red suite for the Host/Assistant control-plane split (plan
ui-runtime-modes-plan.md Phase 4, §6.4, §8.5; issue #555):

- hooks.server.admin-404.vitest.ts: /admin/* is a dead namespace — no
  alias, no gate redirect; authenticated /admin doc nav falls through to
  the router 404 (resolve stub emulates the router from route files on
  disk); /admin/endpoints stops aliasing to /connections; non-host modes
  stop redirecting /admin/* to /chat; routes/admin/ deleted and
  routes/host/+page.svelte exists; HOST_ADMIN_LANDING flips to '/host',
  installed_offline lands on /host, runtime context routes.host is
  '/host'; hooks.server.ts drops the computeFeatureFlags/features.admin
  route gate (Phase 4 step 4 — whether the derived alias in features.ts /
  +layout.server.ts can also be deleted is left to the implementation's
  grep; today +layout.server.ts still returns it). RED: all 10 tests
  (200-instead-of-404, 302 redirects to /connections and /chat, '/admin'
  landing/pointer values, gate still present).

- routes/api/host/health/server.vitest.ts: representative privileged
  /api/host/* endpoint (mechanical move of /admin/health). 403 in
  assistant-container EVEN WITH a valid admin session (capability-based,
  not session-based; body.error === 'capability_not_available'); 403 in
  pwa-static; 200 in host-ui and electron-host; 401 without a session
  (requireAdmin stays in addition to the capability guard). RED: all 6
  ("Cannot find module './+server.js'" — computed-specifier dynamic
  import keeps svelte-check green while red).

- routes/api/host/stack/server.vitest.ts: host-scoped half of the old
  /admin/assistant endpoint (AssistantTab split). GET/PUT projectName +
  lanExposureEnabled at /api/host/stack; payload carries NO
  personaContent; PUT no longer requires it; assistant-container gets
  403 with a valid session and stack.env stays untouched (acceptance:
  cannot edit project name or bind address); 401 unauthenticated. RED:
  all 7 (module missing).

- routes/api/assistant/persona/server.vitest.ts: assistant-owned half —
  GET/PUT /api/assistant/persona over config/assistant/persona.md;
  assistant-container CAN read/write (acceptance); host-ui keeps
  assistant-settings:write; pwa-static 403 with a valid session
  (capability_not_available); 401 unauthenticated. RED: all 7 (module
  missing).

- routes/api/assistant/akm/server.vitest.ts: assistant-scoped AKM config
  (AkmTab split) — GET/PATCH /api/assistant/akm over
  config/akm/config.json; assistant-container CAN edit (acceptance);
  pwa-static 403; 401 unauthenticated. RED: all 5 (module missing).

- routes/api/host/guard-hygiene.vitest.ts: every
  routes/api/host/**/+server.ts contains a requireCapability( call
  (plan §8.5 — server-side enforcement; the walker requires >= 20 moved
  endpoints so the per-file check can never pass vacuously); host-level
  AKM sharing lives at /api/host/akm/host-sharing and not under
  /api/assistant. RED: 3 tests (routes/api/host does not exist).
  CHARACTERIZATION (green today, must stay green): 2 constraint tests —
  session-lifecycle endpoints are NOT under /api/host (a capability
  guard on login would lock assistant-container out before it could
  authenticate) and connections stay at /api/connections (the Phase 2
  EXCEPT clause).

- lib/api/admin-paths-hygiene.vitest.ts: no quoted '/admin' path
  survives in lib/api domain clients or in .svelte components outside
  the deleted routes/admin tree (dead calls/links once /admin 404s), and
  the login page posts to an existing auth endpoint outside /admin
  (acceptance: login/session flow unchanged — only the address moves;
  the exact new path, e.g. /api/auth/login, is the implementation's
  choice, pinned only as "exists and not /admin"). RED: 3 tests
  (offenders: 9 lib/api clients — addons, akm, automations, backups,
  containers, providers, secrets, versions, voice; 7 .svelte files —
  EndpointList, ModeSwitch, SettingsDrawer, AkmHealthReportSection,
  attention/+page, chat/+page, login/+page).
  CHARACTERIZATION (green): 1 walker-sanity test.

NOTE for the implementer: resolve-landing.vitest.ts,
hooks.server.landing.vitest.ts and ui-route-map.md still pin the Phase 3
'/admin' values via their documented TODO(phase-4) constants — flip them
with the implementation; this suite pins the '/host' side.

Verified red for the right reasons: server project 41 failed / 1041
passed across 118 files; the only failing files are these seven and
every failure is a missing-module or missing-feature assertion (no
syntax errors). ui:check: 0 errors 0 warnings (red-state safe). biome:
baseline unchanged, no findings in the new files; eslint: clean on the
new files. CLI suite 87 pass / 1 known uid-0 env failure; lib suite 845
pass / 10 skip / 17 known uid-0 failures — parity.

No production code changed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
Session auth (login/logout/session) leaves the dying /admin namespace but
does NOT move under /api/host — a capability guard on login would lock
assistant-container out before it could ever authenticate for
/api/assistant/* (pinned by routes/api/host/guard-hygiene.vitest.ts).
The flow itself is unchanged: only the address moves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
… + /api/assistant (#555)

Plan ui-runtime-modes-plan.md Phase 4 (§6.4, §8.5). /admin/* is now a dead
namespace: the route tree is deleted, hooks neither gate nor alias it, and
requests fall through to the router 404 (no alias — ratified §2).

- Pages: routes/admin/+page.svelte → routes/host/+page.svelte.
  HOST_ADMIN_LANDING flips to '/host' (installed_offline/broken land there);
  runtime context routes.host is '/host'; nav/components (EndpointList,
  SettingsDrawer, ModeSwitch, attention, chat) point at /host.
- APIs: the ~50 privileged /admin/* JSON endpoints move to /api/host/* and
  EVERY one now carries a server-side requireCapability('host:…') guard in
  addition to requireAdmin — a valid admin session in a mode without host
  capabilities (assistant-container, pwa-static) gets 403
  capability_not_available (§8.5: capabilities are enforced server-side;
  hasCapability() is UX only). Hygiene test guard-hygiene.vitest.ts
  enumerates the tree and asserts the call in each file.
- New /api/assistant/* namespace (assistant-settings:read/write guards):
  persona (from the old /admin/assistant), akm config (from /admin/akm),
  model (from /admin/opencode/model — OpenCode's own setting, plan step 2
  'persona, model'). assistant-container CAN edit these; pwa-static cannot.
- AssistantTab split: host stack settings (projectName,
  lanExposureEnabled → GET/PUT /api/host/stack, host:stack:write) vs
  assistant persona (GET/PUT /api/assistant/persona) with separate save
  actions and hasCapability-driven visibility. AkmTab split: config megaform
  is assistant-scoped; index maintenance/diagnostics (reindex, stats,
  health-report) are host-gated; host key sharing stays host-only at
  /api/host/akm/host-sharing.
- hooks.server.ts: the features.admin route gate is replaced by a capability
  check on /host/*; the Phase 2 /admin/endpoints alias is removed; the
  X-Frame-Options exception follows the health-report move.
- features.admin derived alias DELETED (plan step 4): grep found no readers
  left — hooks and +layout.server.ts were the last. computeFeatureFlags/
  FeatureFlags are gone; the Phase 1 characterization tests that protected
  the alias through Phases 1–3 (features.vitest.ts, layout.server.vitest.ts)
  are retired with it, as those tests' own Phase-4 escape hatch and the plan
  authorize; the env → hostMode mapping they pinned stays covered by the
  serverRuntimeContext tests.
- lib/api domain clients target /api/host/* + /api/assistant/*; the old
  /admin/assistant client splits into fetchHostStackSettings/
  saveHostStackSettings + fetchAssistantPersona/saveAssistantPersona.
- Moved colocated route tests run as OP_UI_HOST_MODE=host-ui (the endpoints
  are capability-guarded now); the old /admin/assistant suite's rename/LAN
  behavior survives as api/host/stack/rename.vitest.ts minus persona.
  computeServerRuntimeContext tolerates url-less test event stubs
  (publicBaseUrl only). Phase 3 TODO(phase-4) constants in
  resolve-landing.vitest.ts / hooks.server.landing.vitest.ts flip to '/host'
  as their comments instructed. e2e suites follow the new paths.
- Phase 2 EXCEPT clause honored: connections stay at /api/connections;
  session lifecycle stays outside the capability namespaces at /api/auth.

Server suite: 1073/1073 pass. ui:check 0 errors 0 warnings. eslint clean on
changed files; biome baseline unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
…#555)

ui-route-map.md reflects the post-Phase-4 truth (/host page, /api/host/*
with per-endpoint requireCapability guards, /api/assistant/*, /api/auth/*,
/admin/* → 404). api-spec.md and managing-openpalm.md endpoint paths follow
the namespace move, with a Conventions note on the capability guards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
…555)

Review-gate fixes for Phase 4: two browser-project (*.svelte.vitest.ts)
tests still referenced the dead /admin namespace and would fail on CI
where Playwright browsers are installed:

- EndpointList.svelte.vitest.ts: the 'Manage this assistant…' link now
  resolves to /host, not /admin — assert the new href.
- ProvidersPanel.svelte.vitest.ts: fetchSecretFile() now requests
  /api/host/secrets/op_api_key — match the mock (and its comment) to the
  new path so the reveal-API-key test exercises the secret branch again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
#486, #555)

Flip the plan status line, the section 7 phase headers, and the section 10
milestone summary to DONE with one-line as-built notes where the landed code
deviated from the design: RuntimeContext v2 lives in lib/server/features.ts
(filename kept), the Phase 2 /admin/endpoints alias was removed again in
Phase 4, /splash became /attention, no new Playwright smoke tests (existing
e2e stack suites updated instead), session auth moved to /api/auth/*, and
the assistant namespace split into persona/model/akm.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
Test-first coverage for the P5a ui-kit extraction (plan
ui-runtime-modes-plan.md §6.11, Phase 5 step 1):

- packages/ui/src/lib/ui-kit-extraction.vitest.ts — RED (4 tests, fail
  for the right reason): components/{common,icons} must leave
  packages/ui/src/lib, no ui source may keep importing the old
  $lib/components/{common,icons} paths (233 offenders today), and ui
  must consume @openpalm/ui-kit.
- packages/ui-kit/tests/no-app-coupling.test.ts — HYGIENE,
  already GREEN (labeled as such): no ui-kit source file may import
  $lib/api, $lib/server, @openpalm/lib, chat state, or the
  endpoints/connections store; includes a post-move guard against a
  forever-vacuous scan.
- packages/ui/src/lib/ui-kit-components-characterization.vitest.ts —
  CHARACTERIZATION, already GREEN (labeled as such): SSR-renders
  IconButton + IconClose resolved from ui-kit first with fallback to
  the current location, pinning "zero behavior change" across the move
  (verified green against a simulated post-move layout too).

Scaffolding only (inert, no production code): packages/ui-kit
package.json (private, bun test script, no exports) + root workspace
entry + lockfile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
… @openpalm/ui-kit (#555)

P5a of the client split (plan ui-runtime-modes-plan.md §6.11, Phase 5
step 1): packages/ui-kit is now a real raw-source workspace package —
private, no build step; its exports point at .svelte/.ts/.css source and
the consuming app's Vite/Svelte 5 pipeline compiles it as part of the
app build.

Moved out of packages/ui/src/lib:
- components/common/ (all 21 components + their co-located
  *.svelte.vitest.ts browser tests and harnesses)
- components/icons/ (all 64 icons + index barrel)
- the Stillness custom-property token blocks and the wiz-* design
  vocabulary from app.css -> ui-kit/src/lib/theme/tokens.css, @imported
  by ui's app.css (ui-specific layout CSS stays in the app; the @import
  sits above all rules because CSS ignores late imports)

Wiring:
- ui devDependency @openpalm/ui-kit workspace:*; every ui import
  rewritten $lib/components/{common,icons}/... ->
  @openpalm/ui-kit/components/{common,icons}/... (63 files)
- kit-internal imports rewritten to relative paths (the old $lib paths
  no longer exist)
- vite.config: ssr.noExternal += @openpalm/ui-kit in dev (raw .svelte
  source cannot be require()d by node), optimizeDeps.exclude, and the
  vitest browser project also collects ../ui-kit/src/**/*.svelte.vitest
  so the moved tests keep running through ui's browser setup (ui-kit
  deliberately has no vitest of its own; bun's isolated linker means
  ui-kit declares svelte/vitest/vitest-browser-svelte devDeps so the
  test files' bare imports resolve)

Presentational-only rule (§6.11/§8): SecretSelect was the one component
importing $lib/api; its fetchSecretFiles/saveSecretFile are now injected
as required props and the single call site (AddonsTab) passes the api
functions — zero behavior change. Toast/ThemeToggle/Drawer/FriendlyError
keep their $lib service imports (notifications, theme-state, focus-trap,
error-messages type), which the hygiene test deliberately permits; they
resolve through the consuming app's alias.

Verified: ui:check 0/0; ui vitest node project 1082 passed 0 failed
(4 P5a red tests now green) with all 30 browser files still collected
(known chromium-download env failure unchanged); bun test ui-kit 2/2;
cli/lib/electron suites at baseline parity; production build inlines
tokens.css; dev-server curl checks green in host-ui and pwa-static modes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
…ate (#555)

Review-gate fixes for P5a:

- Toast.svelte no longer imports $lib/voice/voice-state — that store pulls
  transcribeAudio/fetchVoiceConfig from $lib/api one transitive hop out,
  breaking ui-kit's presentational-only rule (plan §6.11) and blocking P5b
  from compiling Toast without recreating the ui app's voice subsystem.
  The ui app's root layout now mirrors voiceState.errorMessage into the
  notifications queue itself (same replace-in-place semantics), so the
  voice error surface is unchanged while ui-kit stays app-decoupled.
- Extend the no-app-coupling hygiene test to forbid voice-state and any
  voice/ module-dir import so the coupling cannot be reintroduced.
- Add a svelte-check based `check` script + standalone tsconfig to ui-kit:
  the raw-source package has no build step, so this is the ONLY TS
  coverage its ~86 source files get (consuming apps' vite builds strip
  types without checking; packages/ui's check only validates the public
  prop surface at usage sites). Probe-verified: an injected type error
  fails the script. $lib/* resolves types-only against packages/ui's
  implementations of the small app-provided contract (notifications /
  theme-state / focus-trap / error-messages — all import-free modules).
- Wire the new gate into the workspace flow: root `check` runs it after
  ui:check, root `test` now includes packages/ui-kit (hygiene test runs in
  CI), ci.yml gets a "Type check UI kit" step, release.yml runs
  ui-kit:check alongside ui:check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
…med by workflow)

Inert packages/client scaffold + partial transport red tests from the
interrupted P5b test author; the resumed run completes them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
Completes the P5b red suite started in the interrupted WIP commit
(transport request shaping / SSE parsing / health probe were already
authored there; this adds the rest):

- connections-store.test.ts: ConnectionEntry CRUD + active selection
  against the in-memory storage backend (plan §6.6) — persistence lives
  in the backend, locked entries immutable, remove clears a dangling
  active id. RED: missing src/lib/connections/index.ts (15 tests).
- connections-seed.test.ts: runtime-config.json loading (own-origin
  '/runtime-config.json'; 404/offline/malformed -> null) + locked/default
  seeding semantics (idempotent upsert by id, config wins for locked,
  default never steals an explicit selection) + the offline read path.
  RED: missing src/lib/connections/index.ts (13 tests).
- landing.test.ts: client landing choice — 0 connections ->
  /connections/new, else /chat (plan §6.5 pwa-static branch). RED:
  missing src/lib/resolve-landing.ts (3 tests).
- purity.test.ts: dist-grep hygiene — the built bundle must contain no
  '@openpalm/lib' and no '/api/host' marker strings (§8.5/§8.10); fails
  loudly (never skips) while build output is absent. RED: build missing
  until `bun run client:build` lands (3 tests). The 3 source-hygiene
  tests in the same file (no @openpalm/lib dep in package.json, no
  src/lib/server/, no lib import in src) are CHARACTERIZATION tests that
  already pass on the inert scaffold and must stay green.

Suite state: 64 tests across 7 files — 61 red for the right reason
(missing feature modules / missing build), 3 green characterization.
No production code. biome lint clean for packages/client.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
P5b items 1-3 core modules for @openpalm/client (plan §6.6/§6.11):

- src/lib/transport/index.ts — the ONE transport: direct fetch against a
  connection's OpenCode/guardian base URL with Basic (username defaults to
  'openpalm', mirroring the host app's probeEndpoint)/Bearer/none auth,
  credentials 'omit' on every request (no cookies, ever), path-prefix-safe
  URL joining, session list/create + message send (OpenCode parts
  envelope), streaming-decode SSE frame parser, and a never-throwing
  health probe on the host app's RemoteStatus vocabulary.
- src/lib/connections/index.ts — ConnectionEntry store behind a storage
  abstraction (in-memory + raw-IDB IndexedDB backends, same semantics);
  CRUD with locked (config-owned) entries immutable, persisted active
  selection, idempotent runtime-config seeding that never steals an
  explicit user selection, and loadRuntimeConfig() from the app's own
  origin (absent/offline/malformed -> null; offline boot never crashes).
- src/lib/resolve-landing.ts — §6.5 client branch: 0 connections ->
  /connections/new, else /chat.

Turns the P5b red suite green except the 3 dist-grep purity tests, which
need the static build (next commit).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
…ring (#555)

P5b items 3-4 for @openpalm/client (plan §6.9 Slice A/§6.10/§6.11):

- SvelteKit 2 + Svelte 5 app on adapter-static with SPA fallback
  (ssr=false, prerender=false) — no server runtime in the artifact, no
  src/lib/server/, no @openpalm/lib anywhere (§8.10; enforced by the
  purity dist-grep tests, now green against the built bundle).
- Views adapted from packages/ui onto the transport + IndexedDB store
  (packages/ui keeps its own chat until parity): /chat (session list/
  create + message send against the active connection; history + live SSE
  wiring follow with chat parity), /connections (client-side manager with
  per-connection direct health probes, locked/managed entries read-only),
  /connections/new -> /connections?new=1 alias. Root route redirects
  through resolveLanding(). Theme boot + Stillness tokens consumed from
  @openpalm/ui-kit (icons only — no ui-kit component that requires the
  $lib app contract, so the client supplies none yet).
- Per-connection credential material lives in the secret store
  (IndexedDB meta area) under auth.secretRef — entries never carry
  credentials inline, and nothing host-scoped exists to hold (§8.10).
- bin/serve.mjs: zero-dependency static file server — loopback bind by
  default, port via --port/PORT, SPA fallback to index.html, serves
  runtime-config.json from inside or beside the build, traversal-safe.
- scripts/stamp-version.mjs writes .openpalm-client-version into the
  build (same delivery-stamp pattern as packages/ui).
- Workspace wiring: root client:dev/build/check/test/serve scripts;
  client:check joins the root check chain. Runtime deps stay empty —
  the bundle is self-contained and the serve script is stdlib-only.
- tsconfig gates the app source at 0 errors/0 warnings via svelte-check
  --fail-on-warnings; the bun:test suite is excluded from that program
  (bun module rules: bun:test builtin, .ts-extension imports) and is
  executed by bun test instead.

All 64 package tests green (transport/store/landing/purity); checks
0/0 across ui, ui-kit, client; cli/lib/ui-vitest/electron at baseline
parity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FetR7BttT1xBrA8fBwhui9
@itlackey

itlackey commented Jul 7, 2026

Copy link
Copy Markdown
Owner

@codex do a thorough code review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f9f0158de8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread containers/assistant/entrypoint.sh
Comment thread packages/electron/src/main.ts
Comment thread containers/assistant/entrypoint.sh
@itlackey itlackey merged commit c1eadbd into main Jul 8, 2026
5 checks passed
@itlackey itlackey deleted the claude/ui-runtime-modes-phases-1-4 branch July 8, 2026 20:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants