A self-hostable navigation/bookmark dashboard with a Rust backend (Axum + SQLite), a SvelteKit SPA frontend, and an inline editor for the admin. Each nav item can expose multiple URLs grouped under user-defined "sites" (think work / home, or shanghai / beijing) — switch the active site from the header and every card re-points to its matching URL without changing the layout.
The home view is a macOS Launchpad-style paginated grid: cards reflow into pages
sized from the live container rect, the page turner runs as a hand-rolled
translate3d track (1:1 finger tracking on trackpad, snap-on-release on mouse
wheel, dot indicator + ←/→ keyboard), and jiggle mode enables drag-to-reorder,
drag-into-folder, and item-on-item auto-folder gestures.
The Rust binary serves both the JSON API (/api/*) and the SvelteKit SPA static
assets in a single process — no separate web server or reverse proxy required.
Designed for intranet self-hosting; expose via your network's existing TLS
terminator if needed.
docker run -d \
--name lens \
-p 8080:8080 \
-v ./data:/app/data \
-e BOOTSTRAP_ADMIN_PASSWORD=changeme \
lens:latestVisit http://localhost:8080. Click Log in in the top-right, enter your password, then click Edit to add/remove nav items inline.
See Deployment for compose, custom port, and data persistence options.
| Path | Purpose |
|---|---|
web/ |
SvelteKit 2 + Svelte 5 SPA. pnpm dev for local dev (proxies /api to :8080). |
server/ |
Rust binary (lens). cargo run for local dev (default port 8080). |
tests/ |
Playwright e2e specs covering read + login + create. |
scripts/ |
docker-smoke.sh, e2e.sh, dump-bootstrap.mjs. |
docs/superpowers/ |
Design specs and implementation plans. |
Dockerfile |
Multi-stage build → distroless single image. |
docker-compose.yml |
Sample single-service compose for intranet self-host. |
Backend (terminal A):
cd server
cargo run # listens on :8080On first boot, an admin password is generated and printed; it's also written to
server/dev-data/INITIAL_PASSWORD.txt (auto-deleted after the first password
change via the UI).
Frontend (terminal B):
cd web
pnpm install # uses pinned pnpm 10 via packageManager
pnpm dev # listens on :5173, proxies /api to :8080Run unit tests:
cd web && pnpm test:unit
cd server && SQLX_OFFLINE=true cargo testRun e2e (requires Docker):
bash scripts/e2e.shcd web
pnpm install
pnpm build # outputs to web/build (static assets)cd server
SQLX_OFFLINE=true cargo build --release --bin lens
# binary at server/target/release/lensRun it with the SPA build directly (no Docker):
cd server
STATIC_DIR=../web/build DATA_DIR=./prod-data \
./target/release/lensdocker build -t lens:latest .The multi-stage Dockerfile:
- builds the SPA with
node:20-alpine+ pnpm, - builds the Rust binary with
rust:1.88-slim(usesSQLX_OFFLINE=trueagainst the committedserver/.sqlx/cache), - assembles a distroless
gcr.io/distroless/cc-debian12runtime (~70 MB) with the binary at/usr/local/bin/lensand SPA assets at/app/static.
Smoke-test the freshly built image:
bash scripts/docker-smoke.shSingle-process, single-port. Mount one volume for the SQLite DB + uploaded icons.
PORT=8080 BOOTSTRAP_ADMIN_PASSWORD=changeme docker compose up -ddocker-compose.yml publishes ${PORT}:${PORT} and bind-mounts ./data to
/app/data. To switch ports later, change PORT and up -d again — both the
host mapping and the container's listening port follow the same variable.
docker run -d --name lens \
-e PORT=9090 -p 9090:9090 \
-v ./data:/app/data \
-e BOOTSTRAP_ADMIN_PASSWORD=changeme \
lens:latestIf you keep the default PORT=8080 baked into the image, just publish
-p <host>:8080.
Everything stateful lives under DATA_DIR (defaults to /app/data in Docker):
data.db— SQLite (WAL); contains nav items, sites, groups, admin password hashicons/— proxied/uploaded favicon cache (7-day TTL refresh)INITIAL_PASSWORD.txt— generated on first boot ifBOOTSTRAP_ADMIN_PASSWORDis unset; auto-deleted after the first password change via the UI
Back up the volume to back up the whole instance.
For HTTPS or path prefixing, front lens with any reverse proxy (Caddy,
nginx, Traefik, Cloudflare Tunnel, Tailscale Funnel...). When TLS is terminated
upstream, set SECURE_COOKIES=true so session cookies are marked Secure.
| Var | Default | Notes |
|---|---|---|
PORT |
8080 |
TCP port |
DATA_DIR |
/app/data (Docker) / ./dev-data (cargo) |
SQLite + uploads + INITIAL_PASSWORD |
STATIC_DIR |
/app/static (Docker) / ../web/build (cargo) |
SvelteKit build output |
BOOTSTRAP_ADMIN_PASSWORD |
(unset) | First boot only; otherwise random + file |
SECURE_COOKIES |
false |
Set true only when serving over HTTPS |
RUST_LOG |
info,sqlx=warn,tower_http=info |
tracing-subscriber filter |
docker exec -it lens lens reset-password --password=<new>
# or interactively (the binary prompts)This invalidates all sessions and removes any leftover INITIAL_PASSWORD.txt.
See docs/superpowers/specs/2026-05-19-rust-navigation-platform-design.md for the
full design. Briefly:
- Frontend: Svelte 5 (runes) + SvelteKit 2, no UI library — design tokens and
10 hand-built primitives under
web/src/lib/components/ui/.apiClientvalidates every response against zod schemas mirroring the backend types. Self-implemented i18n (~80 lines), no library. The Launchpad pager (web/src/routes/+page.svelte) owns horizontal scroll itself —translate3dtrack + rAF settle curve — instead of nativeoverflow-x: auto + scroll-snap, so the same animation loop covers in-gesture pan, post-release settle, drag-to-edge auto page turn, and trackpad inertial multi-page commit. Drag-and-drop isdragGridaction inweb/src/lib/util/dragGrid.ts, with merge / reorder / spring-load resolved against a layout cache rebuilt on spring-load and edge-pan. - Backend: Axum 0.7 + SQLx (SQLite WAL). 3NF schema. Single-admin auth
(bcrypt + signed cookie session via
tower-sessions). CRUD endpoints behind aRequireAuthextractor. Favicon proxy with 7-day disk cache. CLI subcommand for password reset. - Deploy: Multi-stage Dockerfile produces a distroless image (~70 MB). The
Rust binary serves both the SPA (
tower-http::ServeDirwith SPA fallback) and/api/*.
Shipped since the original Plans 1–5:
- ✅ Launchpad-style paginated home with horizontal page turn (trackpad / wheel / pointer / ←→ keyboard) and dot indicator
- ✅ Jiggle-mode drag-and-drop: reorder cards, drag-into-folder, item-on-item auto-folder, edge-pan to adjacent page during a drag, spring-loaded folders
- ✅ Inline folder expand panel (backdrop blur + click-outside dismiss)
- ✅ Dedicated admin dashboard layout (separate from the public Launchpad chrome)
Still ahead:
- Full management UI for groups, sites, tags (currently editable via API only)
- Site settings dialog (site name, avatar upload, ICP filings, default theme)
- Multi-user / OAuth / audit log
- Real-time multi-device sync (SSE)
- PWA / offline
MIT.