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
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ target/
data/
wal/
.git/
loomem-dashboard/node_modules/
loomem-dashboard/dist/
loomem-dashboard/coverage/
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,24 @@ jobs:
- name: cargo fmt --check
run: cargo fmt --all -- --check

dashboard:
name: Dashboard (lint + test + build)
runs-on: ubuntu-latest
defaults:
run:
working-directory: loomem-dashboard
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
# Node 20+ is preinstalled on ubuntu-latest runners.
- name: npm ci
run: npm ci
- name: eslint
run: npm run lint
- name: vitest
run: npm test
- name: vite build
run: npm run build

audit:
name: Audit
runs-on: ubuntu-latest
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ jobs:
sudo apt-get update
sudo apt-get install -y libclang-dev

# The dashboard SPA is embedded into loomem-server at compile time
# (rust-embed over loomem-dashboard/dist), so the front end must be
# built BEFORE cargo. Node 20+ is preinstalled on GitHub runners.
- name: Build dashboard SPA
working-directory: loomem-dashboard
run: |
npm ci
npm run build

- name: Build release binaries
run: cargo build --release --target ${{ matrix.target }} -p loomem-server -p loomem-cli -p loomem-migrate

Expand Down
125 changes: 116 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ bincode = "1.3"
axum = "0.8"
tower = "0.5"
tower-http = { version = "0.6", features = ["trace"] }
# Embedded dashboard SPA (loomem-dashboard/dist compiled into the binary —
# ships one self-contained file to every instance). Chosen over ServeDir
# (would require deploying a directory next to the binary) and include_str!
# (doesn't scale to Vite's hashed asset names). cargo-audit clean, actively
# maintained, no default extra features.
rust-embed = "8"

# Security primitives (audit 2026-07-01): constant-time token comparison
# (item 4) + key-material zeroization on drop (item 5). Both were already in
Expand Down
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# Stage 0: Build dashboard SPA (embedded into loomem-server via rust-embed,
# so it must exist before cargo runs)
FROM node:22-trixie-slim AS dashboard
WORKDIR /app/loomem-dashboard
COPY loomem-dashboard/package.json loomem-dashboard/package-lock.json ./
RUN npm ci
COPY loomem-dashboard/ ./
RUN npm run build

# Stage 1: Build Rust server
# Pinned builder (trivy DS-0001): 1.93 tracks `rust-version` in Cargo.toml;
# -trixie matches the runtime base below, so the binaries link against the
Expand All @@ -6,6 +15,7 @@ FROM rust:1.93-trixie AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends clang libclang-dev pkg-config && rm -rf /var/lib/apt/lists/*
COPY . .
COPY --from=dashboard /app/loomem-dashboard/dist ./loomem-dashboard/dist
RUN cargo build --release -p loomem-server -p loomem-migrate

# Stage 2: Runtime
Expand Down
15 changes: 15 additions & 0 deletions loomem-dashboard/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module.exports = {
root: true,
env: { browser: true, es2022: true, node: true },
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module', ecmaFeatures: { jsx: true } },
settings: { react: { version: 'detect' } },
rules: {
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
},
};
8 changes: 8 additions & 0 deletions loomem-dashboard/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules/
coverage/
.vite/
# Built SPA — embedded into loomem-server at compile time. The folder itself
# stays in git (.gitkeep) so `cargo build` works before the front end is
# built; the contents are a build artifact.
dist/*
!dist/.gitkeep
41 changes: 41 additions & 0 deletions loomem-dashboard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# loomem-dashboard

The hosted dashboard for a Loomem instance — a React SPA (Vite + Tailwind)
embedded into the `loomem-server` binary at compile time via `rust-embed`.
Every instance ships it the same way: `https://<instance>/` serves the
dashboard, `/mcp` and `/v1/*` stay the engine's API surface.

Screens (v1): **Connect** (MCP endpoint + token + per-client recipes + live
status), **Memory** (browse / search / edit / delete / version history /
entities), **Settings**.

## Development

```bash
npm install
npm run dev # http://localhost:3031, proxies API calls to :3030
npm test # vitest
npm run lint # eslint
```

Run `loomem-server` locally on port 3030 for live data.

## Production build

```bash
npm run build # writes dist/
cargo build -p loomem-server # embeds dist/ into the binary
```

Build order matters: the front end first, then cargo. CI and `release.yml`
do this automatically. With an empty `dist/` (fresh clone) the server still
compiles; `/` then answers an honest 404 until the SPA is built.

## Conventions

- Fonts are self-hosted (`@fontsource-variable`) — no Google Fonts request,
deterministic rendering offline and in headless environments.
- Design tokens in `src/index.css` follow Loomem Design System v2 (ramp-C
gradients only, two-tone wordmark, no black buttons, warm ink surfaces).
- No mock data in the production path: a failed endpoint renders an error
state, never fabricated zeros.
Empty file added loomem-dashboard/dist/.gitkeep
Empty file.
Loading
Loading