Skip to main content
Glama

Stillcurrent

Stillcurrent keeps the architecture docs and diagrams of your software up to date: LLM agents write and update them over MCP and a REST API, and engineers and architects read them. Documents are Markdown with first-class Mermaid diagrams. Users sign in, keep their documents in PostgreSQL, group them into projects, and read them with IBM Plex typography, a contents sidebar, callouts, highlighted code and themed, zoomable diagrams (with special routing for C4). Scripts use a REST API; LLM clients connect over MCP.

  • Stack: Next.js 16 (App Router), React 19, TypeScript, PostgreSQL 17 with Drizzle ORM, Better Auth, Zod, marked + DOMPurify + highlight.js, Mermaid 11.16, CodeMirror 6, Vitest and Playwright.

  • Contracts: docs/GUIDE.md is the authoring guide served at /guide (and raw at /guide.md); everything it promises renders. PLAN.md is the build plan; CLAUDE.md holds the coding rules.

Getting started

Requirements: Node 22 LTS, pnpm 10 (corepack enable), Docker.

pnpm install
cp .env.example .env.local          # then set BETTER_AUTH_SECRET (openssl rand -base64 32)
pnpm db:up                          # Postgres 17 in Docker, with the stillcurrent and stillcurrent_test databases
pnpm db:migrate
pnpm db:seed                        # dev@example.com / dev-password-123 and the welcome document
pnpm dev                            # http://localhost:3000

To run the whole stack in Docker instead (Postgres, migrations and the app on http://localhost:8080), put a BETTER_AUTH_SECRET in a .env file next to docker-compose.yml (there is no default, so no two installations share one), then:

docker compose up -d --build

The local Compose file has a development database password and open sign-up; never expose it. See Deployment for production.

Related MCP server: Docs MCP Server

Scripts

Command

What it does

pnpm dev

Dev server (Turbopack)

pnpm build / pnpm start

Production build and server

pnpm check

Lint, type check and unit tests; run before every commit

pnpm lint / pnpm typecheck / pnpm test

The three parts of check

pnpm test:e2e

Playwright. Locally it reuses a running dev server; CI=1 PORT=3400 pnpm test:e2e runs against pnpm build output

pnpm db:up / pnpm db:down

Start or stop the Postgres container

pnpm db:generate

Create a migration after a schema change (review the SQL, never edit an applied migration)

pnpm db:migrate

Apply migrations

pnpm db:studio

Drizzle Studio

pnpm db:seed

Development user and welcome document

Unit tests are co-located (*.test.ts(x)). Repository and service tests run against the stillcurrent_test database (DATABASE_URL_TEST), one file at a time.

Architecture

src/app/         routes: pages, layouts, server actions, route handlers (/api/v1, /api/mcp, /api/health)
src/components/  React components by feature (shell, viewer, diagrams, workspace, library, projects, settings)
src/lib/         pure, isomorphic logic: Markdown pipeline, Mermaid helpers and C4 router, preferences, formatting
src/server/      server-only: db (Drizzle schema + migrations), repositories and services per domain,
                 auth, http (problem+json, tokens, rate limits), mcp, logging
src/proxy.ts     request id, security headers with a per-request CSP nonce, sign-in gate
  • Layers: app → components → lib and app → server. Only repositories touch the database; services enforce authorisation (every query is scoped to the signed-in user, a share link, or an API token and its project restriction) and throw typed domain errors.

  • Same services everywhere: the UI (server actions), the REST API (/api/v1, OpenAPI at /api/v1/openapi.json) and the MCP server (/api/mcp) call the same service functions.

  • Rendering: lib/markdown/render.ts turns Markdown into sanitized HTML on the server (and in the editor's live preview). Documents are stored as Markdown, never as HTML. Mermaid runs only in the browser (securityLevel: 'strict') and loads only when a document has diagrams; the server's check_markdown parses diagrams in a worker thread with its own JSDOM.

  • Concurrency: every update carries expectedVersion; a stale write gets a conflict instead of overwriting.

  • Security: nonce-based CSP with strict-dynamic, no eval in production, frame-ancestors 'none', HSTS on HTTPS, hashed share links and API tokens (shown once), rate limits (60 requests per minute per token; sign-in 10 attempts per account and 30 per address per minute, sign-up 10 per address per 10 minutes, password checks 10 per user per 10 minutes). The address comes from the last X-Forwarded-For entry (or X-Real-IP) that the reverse proxy sets; without a proxy (a loopback address), only the per-account and per-user limits apply.

  • Logs: one JSON line per event on stdout/stderr with the request id. Logs never contain document text, Markdown, tokens or passwords; MCP tool calls log only the tool name, outcome and duration.

Environment variables

Validated at startup by src/env.ts; the list with comments is in .env.example.

Variable

Required

Notes

DATABASE_URL

yes

postgres://user:password@host:5432/db

DATABASE_URL_TEST

tests

Database the unit tests may wipe

BETTER_AUTH_SECRET

yes

At least 32 characters: openssl rand -base64 32

BETTER_AUTH_URL

yes

The app's public URL (same as APP_URL)

APP_URL

yes

Public base URL used in share links, API and MCP responses. https:// turns on HSTS and upgrade-insecure-requests

SIGNUP_ENABLED

no

Defaults to true in development and false in production

LOG_LEVEL

no

info (default), warn or error

MERMAID_WORKER_MODULE_DIR

no

Folder whose node_modules holds mermaid and jsdom for check_markdown (set in the Docker image)

SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM

no

Reserved for password-reset email; leave SMTP_HOST empty to disable

Deployment

The Dockerfile builds a standalone Next.js server that runs as the unprivileged node user with a healthcheck on /api/health, plus a migrate target that applies migrations and exits. docker-compose.prod.yml wires Postgres, the migration step and the app; every secret is required.

  1. Create a server (or managed Postgres) in an EU region; the data is personal data under the GDPR.

  2. Put a .env next to docker-compose.prod.yml:

    POSTGRES_USER=stillcurrent
    POSTGRES_PASSWORD=<long random string>
    BETTER_AUTH_SECRET=<openssl rand -base64 32>
    APP_URL=https://docs.example.com
    SIGNUP_ENABLED=true        # create the first accounts, then set false and restart
  3. docker compose -f docker-compose.prod.yml up -d --build. The migrate service runs before every app start, so deploying a new version is the same command.

  4. Terminate TLS in front of the app (Caddy, nginx or a load balancer) and forward to 127.0.0.1:3000. The proxy must pass the client address in X-Forwarded-For (Caddy and nginx's $proxy_add_x_forwarded_for do; the app reads the last entry) so the per-address sign-in limits work, and forward Host unchanged so Next's Server Action origin check and Better Auth's cookies see the public origin. Point your uptime monitor at https://docs.example.com/api/health (200 when the app and the database are up, 503 otherwise).

With sign-up disabled, new accounts are created by turning SIGNUP_ENABLED on briefly; a command-line tool for accounts and password resets is still open in PLAN.md.

Backups

All state lives in PostgreSQL (documents, revisions, projects, share links, tokens, preferences, accounts).

# nightly dump (keep several days, store a copy outside the server, in the EU)
docker compose -f docker-compose.prod.yml exec -T postgres \
  pg_dump -U "$POSTGRES_USER" -Fc stillcurrent > "stillcurrent-$(date +%F).dump"

# restore into an empty database
docker compose -f docker-compose.prod.yml exec -T postgres \
  pg_restore -U "$POSTGRES_USER" -d stillcurrent --clean --if-exists < stillcurrent-2026-10-04.dump

Test a restore now and then. Revisions keep the last 100 versions of each document, which covers mistakes but is not a backup.

Maintenance guides

Adding a highlight language

  1. Import the language from highlight.js/lib/languages/<name> in src/lib/markdown/highlight.ts and add it to LANGUAGES; add aliases to ALIASES and a display label to LABELS.

  2. List it (with aliases) in docs/GUIDE.md, section 6. LLMs only use what the guide names.

  3. Run pnpm check; highlight.test.ts fails when the guide's list and the registered languages differ.

highlight.js runs on the server for read views; the language adds nothing to the read view's JavaScript (tests/e2e/performance.spec.ts checks this).

Upgrading Mermaid safely

Mermaid is pinned to 11.16.x because the C4 post-processing (src/lib/mermaid/c4/postprocess.ts) depends on Mermaid's C4 SVG structure (g.person-man, marker-end lines, #444444 strokes, aria-roledescription="c4").

  1. Bump mermaid in package.json on a branch.

  2. Run pnpm check: the C4 router tests, the palette contrast tests and the parse test that feeds every Mermaid block in docs/GUIDE.md and the welcome document through the new version.

  3. Run CI=1 PORT=3400 pnpm test:e2e (diagram rendering, C4 routing without crossings, CSP).

  4. Open the welcome document (/dev/welcome in development) in light and dark and look at every diagram, especially the C4 ones. Update the guide if the supported syntax changed.

Performance budgets

tests/e2e/performance.spec.ts enforces them on the production build: public read view ≤ 155 KB of gzipped JavaScript, workspace read view ≤ 170 KB (React and Next alone are about 130 KB), Mermaid only on documents with diagrams, and LCP ≤ 2 s for a 2,000-word document over throttled 4G. Keep rarely used UI (dialogs, editors, diagram code) behind dynamic imports, and keep Zod out of browser code (lib/prefs/fields.ts explains why).

Connecting MCP clients

LLM apps that speak the Model Context Protocol can read, check, create and update documents, manage projects, see history and share. Create a token in Settings → API tokens (read-only, read and write, optionally limited to some projects); Settings → Connect an MCP client shows copy-ready setups with your URL.

# Claude Code
claude mcp add --transport http stillcurrent https://docs.example.com/api/mcp \
  --header "Authorization: Bearer scur_…"
// Cursor (.cursor/mcp.json)
{ "mcpServers": { "stillcurrent": { "url": "https://docs.example.com/api/mcp",
  "headers": { "Authorization": "Bearer scur_…" } } } }

Claude Desktop and VS Code setups are on the settings page. The server is stateless Streamable HTTP (POST /api/mcp), shares the REST rate limit, and tells the model to read the authoring guide, run check_markdown before saving, and pass expectedVersion on updates.

REST API

/api/v1 covers documents (list, search, create, read, raw Markdown, update, move between projects, trash) and projects (list, create, read, update, delete, zip export). Authenticate with Authorization: Bearer scur_…. The OpenAPI 3.1 description is at /api/v1/openapi.json; errors are RFC 9457 application/problem+json.

License

Stillcurrent is licensed under the PolyForm Noncommercial License 1.0.0. You may use, change and share it for any noncommercial purpose. Anyone you give a copy to, changed or not, must also get the license terms and the Required Notice: line from LICENSE.md. Commercial use needs a separate license from the copyright holder.

Related MCP Connectors

Related MCP Servers