Skip to content

feat: embedded dashboard v1 (Connect + Memory) - #60

Merged
vvooki-sys merged 2 commits into
mainfrom
feat/dashboard-v1
Jul 10, 2026
Merged

feat: embedded dashboard v1 (Connect + Memory)#60
vvooki-sys merged 2 commits into
mainfrom
feat/dashboard-v1

Conversation

@vvooki-sys

@vvooki-sys vvooki-sys commented Jul 10, 2026

Copy link
Copy Markdown
Owner

What

The hosted dashboard, shipped inside the engine: a React SPA (loomem-dashboard/, Vite + Tailwind, Loomem DS v2) embedded into the loomem-server binary via rust-embed and served at / on every instance identically — hosted fleet, loomem.ai, localhost. One binary, no separate frontend process.

Screens (v1):

  • Connect — the onboarding surface: real MCP endpoint computed from the page origin, unlock-token reveal/copy, per-client recipes (Claude Desktop / Claude Code / ChatGPT / Cursor) parameterized with the real endpoint + token, and a live status card that actually probes /v1/status (no mock anywhere).
  • Memory — master-detail browser: server-paginated list, substring + semantic search, layer/agent filters, inline edit/delete over the existing /api/memories/:id, version history (/v1/memory-chain/:id), related entities in-panel.
  • Settings — stream / role / server version, logout.
  • Login — token-unlock over the existing auth_middleware + GET /v1/whoami. No new auth surface; in local passthrough mode (no token configured) the gate opens straight through.

Server:

  • handlers/dashboard.rs (new): GET /api/dashboard/memory (list; reuses chunk_to_memory_item so rows are byte-identical with GET /api/memories/:id) and GET /v1/memory-chain/:id (thin wrapper over the existing loomem_core::contradiction::get_memory_chain).
  • spa.rs (new): rust-embed fallback with client-side-routing support; API-shaped paths stay honest 404s; hashed assets are cache-immutable; an unbuilt dist/ compiles fine and / answers an honest 404.
  • scope.rs: source_for returns to production use (its dashboard consumer is back).
  • main.rs: additive only — two routes in the declarative table (CC=1 preserved) + the SPA fallback registered outside the auth layer so the login screen loads without a token.

Build/infra: rust-embed added to workspace deps (rationale in Cargo.toml), CI gains a Dashboard job (npm ci/lint/test/build), release.yml and Dockerfile build the SPA before cargo. Fonts are self-hosted via @fontsource-variable (Fraunces full variant carries the opsz axis) — deterministic rendering offline and in headless environments, no Google Fonts request.

Verification

  • cargo fmt --check + cargo clippy --workspace -- -D warnings — green (pre-commit hook).
  • cargo test --workspace — 303 passed; the only 2 failures are the pre-existing ac6_3_* MCP tool-count tests already failing on main (they count 14/15 tools; untouched by this PR — CI runs --lib and stays green).
  • Front: vitest 52/52, eslint clean, vite build clean.
  • Live smoke (16/16) against a real debug binary with the SPA embedded, a real RocksDB and LOOMEM_AUTH_TOKEN set: bad token refused → good token unlocks; Connect shows the real endpoint/token, recipes parameterized, live probe goes green; the copied recipe's credentials complete a real MCP initialize handshake against /mcp (and 401 without them); Memory lists seeded data, server-side search filters, history loads, edit persists via PUT, delete removes the row; a mid-session 401 drops back to the login screen.
  • Screenshots (1440×900, fonts loaded) on the assets/dashboard-v1-screens branch:
Screen
Login login
Connect connect
Memory memory
Memory detail (after edit) edited
Settings settings

Deviations from the brief

  • Unused donor components (Avatar, Badge, FilterChip, SegmentedControl, InlineEditField, KeyRevealModal, useKeyboardShortcuts, useStoredSort, format.js, utils.js, RealityBenchTrend) were not ported — no consumer in v1, zero dead code in a public repo; easy to port when a consumer appears.
  • IdentityBanner collapsed to stream + total — single-user sessions have no user identity, so the donor's service-token warning would always fire.
  • Stats tiles skipped — /v1/stats counts searches/stores, not memories/entities; an honest tile needs a small aggregate endpoint (v1.1 candidate alongside Graph).

Out of scope (v1.1+)

Graph 3D (spec in the mockup), stats tiles, httpOnly-cookie token storage.

Greptile Summary

This PR embeds a React dashboard into loomem-server and wires it into the existing authenticated API surface. The main changes are:

  • New Vite/Tailwind dashboard for Connect, Memory, Settings, and Login screens.
  • Embedded SPA serving through rust-embed with client-route fallback and API-shaped 404 handling.
  • New dashboard memory list endpoint and memory-chain endpoint for browser/history views.
  • Dashboard build steps added to CI, release workflow, and Docker image build.

Confidence Score: 5/5

This PR appears safe to merge based on the reviewed paths.

The changed server routes remain behind existing auth except for static SPA serving. The SPA fallback preserves API 404 behavior. The reviewed dashboard API and UI flows are internally consistent. No new blocking functional or security issues were identified.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Validated the embedded dashboard login flow by confirming the real AuthGate login screen renders after intercepting unauthenticated /v1/whoami responses.
  • Verified the unlock flow into /connect, including the token UI and a live status card backed by intercepted /v1/status.
  • Validated the /memory page rendering the real master/detail UI with intercepted memory items and a selected detail panel.
  • Validated the /settings page rendering the stream id, role, and mocked server version.
  • Reviewed the blocker check for the embedded binary as documented in the libclang log artifact.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
loomem-server/src/handlers/dashboard.rs Adds authenticated dashboard memory listing and memory-chain endpoints; pagination overflow issue from earlier review is addressed with saturating math.
loomem-server/src/spa.rs Adds embedded SPA fallback serving assets with API-prefix 404 handling and cache headers.
loomem-server/src/main.rs Registers protected dashboard API routes and unauthenticated static SPA fallback outside auth.
loomem-dashboard/src/lib/api.js Adds same-origin dashboard API helpers with bearer-token injection and unauthorized notification.
loomem-dashboard/src/components/AuthGate.jsx Adds token unlock gate backed by /v1/whoami and global 401 handling.
loomem-dashboard/src/pages/ConnectPage.jsx Adds real endpoint/token recipes and live status probing for client onboarding.
loomem-dashboard/src/pages/MemoryPage.jsx Adds master-detail memory browser orchestration with edit/delete mutations and filter state.
loomem-dashboard/src/pages/memory/useMemoryList.js Adds server-backed substring listing and semantic search mapping with stale-response protection.
Dockerfile Adds a dashboard build stage and copies the built dist before Rust compilation.
.github/workflows/release.yml Builds the dashboard before release cargo builds so embedded assets are present.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Browser as Dashboard SPA
participant Fallback as spa_fallback
participant Auth as Auth Middleware
participant API as Dashboard/API Handlers
participant Store as RocksDB/Indexes

Browser->>Fallback: GET /, /memory, assets
Fallback-->>Browser: index.html or embedded asset
Browser->>Auth: GET /v1/whoami with Bearer token
Auth->>API: authenticated request
API-->>Browser: user context
Browser->>Auth: "GET /api/dashboard/memory?page&q&filters"
Auth->>API: dashboard_memory_handler
API->>Store: resolve scope, scan chunks/entities
Store-->>API: scoped memory rows
API-->>Browser: paginated memory list
Browser->>Auth: PUT/DELETE /api/memories/:id or GET /v1/memory-chain/:id
Auth->>API: existing admin/dashboard handlers
API->>Store: update/delete/read version chain
API-->>Browser: mutation result or history
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Browser as Dashboard SPA
participant Fallback as spa_fallback
participant Auth as Auth Middleware
participant API as Dashboard/API Handlers
participant Store as RocksDB/Indexes

Browser->>Fallback: GET /, /memory, assets
Fallback-->>Browser: index.html or embedded asset
Browser->>Auth: GET /v1/whoami with Bearer token
Auth->>API: authenticated request
API-->>Browser: user context
Browser->>Auth: "GET /api/dashboard/memory?page&q&filters"
Auth->>API: dashboard_memory_handler
API->>Store: resolve scope, scan chunks/entities
Store-->>API: scoped memory rows
API-->>Browser: paginated memory list
Browser->>Auth: PUT/DELETE /api/memories/:id or GET /v1/memory-chain/:id
Auth->>API: existing admin/dashboard handlers
API->>Store: update/delete/read version chain
API-->>Browser: mutation result or history
Loading

Reviews (4): Last reviewed commit: "fix(dashboard): saturate pagination offs..." | Re-trigger Greptile

@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
loomem-site Ready Ready Preview, Comment Jul 10, 2026 8:30pm

Request Review

Ship the hosted dashboard with the engine itself: a React SPA
(loomem-dashboard/, Vite + Tailwind, Loomem DS v2 tokens, self-hosted
fonts) compiled into the loomem-server binary via rust-embed and served
at / on every instance identically — hosted fleet, loomem.ai, localhost.

Why in the binary: single-user Loomem's deployment story is one
self-contained file; a separate frontend process or directory would
break that on the fleet and for self-hosters.

Screens (v1): Connect (real MCP endpoint from the page origin, unlock
token reveal/copy, per-client recipes for Claude Desktop / Claude Code /
ChatGPT / Cursor parameterized with the real endpoint+token, live status
probing /v1/status), Memory (server-paginated list, substring + semantic
search, inline edit/delete over the existing /api/memories/:id, version
history, related entities), Settings. Login is token-unlock over the
existing auth_middleware + /v1/whoami — no new auth surface.

Server side: new handlers/dashboard.rs (GET /api/dashboard/memory list,
GET /v1/memory-chain/:id via loomem_core::contradiction) reusing
chunk_to_memory_item and resolve_scope; scope.rs source_for returns to
production use (its dashboard consumer is back). spa.rs serves the
embedded dist/ with an SPA fallback that keeps API-shaped paths as
honest 404s. CI gains a dashboard job; release.yml and Dockerfile build
the SPA before cargo. With an unbuilt dist/ the server still compiles
and / answers an honest 404.

Deviations from the brief (one-liners): unused donor components
(Avatar, FilterChip, SegmentedControl, InlineEditField, KeyRevealModal,
useKeyboardShortcuts, useStoredSort, RealityBenchTrend) were not ported
— no consumer in v1, zero dead code in a public repo; IdentityBanner
collapsed to stream+total (no user identity in single-user); stats
tiles skipped (/v1/stats counts searches/stores, not memories).

Critical file rationale: main.rs changes are additive — two route
registrations in the declarative table (CC=1 preserved) plus a fallback
registered outside the auth layer; no existing route, middleware, or
control flow changed. Verified by router_builds_without_panic, 6 new
dashboard handler tests, 4 spa tests, and cargo test --workspace.

Signed-off-by: Łukasz Gumowski <lukasz.gumowski@gmail.com>
Comment thread loomem-server/src/handlers/dashboard.rs Outdated
`page` is parsed straight from the query string, so a request like
`?page=18446744073709551615&per_page=200` reached the offset multiplication
in dashboard_memory_handler — an overflow panic in debug builds, a wrapped
(and thus wrong, earlier) page in release. Use saturating_mul so an oversized
page clamps to usize::MAX, which .skip() turns into a harmless empty page;
normal pagination is unchanged (page-1 can't underflow, page is .max(1)).

Addresses Greptile P1 review comment on #60.

Signed-off-by: Claude <noreply@anthropic.com>
@vvooki-sys
vvooki-sys merged commit 39a5cd8 into main Jul 10, 2026
8 checks passed
@vvooki-sys
vvooki-sys deleted the feat/dashboard-v1 branch August 5, 2026 16:54
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.

2 participants