(
) )\ )
( ( ( ( /( ( (()/(
)\ )\ )\ )\()) ))\ /(_)) ( ( (
((_)((_|(_|(_)\ /((_|_))_ )\ )\ )\
\ \ / / (_) |(_|_)) | \ ((_)((_|(_)
\ V / | | '_ Y -_) | |) / _ Y _|(_-<
\_/ |_|_.__|___| |___/\___|__|/__/
Self-hosted markdown documentation browser. Live mode for editing, static-build mode for publishing. Built in collaboration with Claude Code.
npm install -g github:danielcbright/vibedocs
vibedocs --root ./demoOpen http://localhost:8080. That's it.
The repo ships with a demo/ workspace (three fictional Cirrus Weather projects) so you can try the app without your own markdown:
git clone https://github.com/danielcbright/vibedocs.git
cd vibedocs
VIBEDOCS_ROOT=$(pwd)/demo npm start-
Auto-discovery — point at a directory of projects and VibeDocs finds the markdown
-
Live reload — edits to
.mdfiles appear instantly via WebSocket -
Syntax highlighting — Shiki with dual light/dark themes
-
Mermaid diagrams — fenced
```mermaidblocks render inline -
Full-text search — Ctrl+K command palette with instant results
-
Mobile-first layout — hamburger drawer + bottom-sheet TOC
-
Table of contents — auto-generated from headings with scroll-spy
-
Dark / light / system theme — toggle in one click
-
File upload (opt-in) — bearer-token-gated upload endpoint, hidden when disabled
-
GFM — tables, task lists, strikethrough, autolinks
VibeDocs has two ways to serve docs:
| Mode | Command | Use for |
|---|---|---|
| Live | npm start (or vibedocs) |
The interactive app — search, live reload, theme toggle. Run it against a directory you're actively editing. |
| Static build | npx vibedocs build --serve |
Publish to a static host. Outputs plain HTML with rendered Shiki + tables. Add --hydration minimal to ship ~500 KB less JS per page (no search dialog, system theme only). |
Both modes share the same renderer (one source of truth for HTML output).
A thin Hono backend watches files, owns the AppState (single in-memory source of project trees + render results), and serves rendered HTML. A React 19 frontend reads from it. Inter-doc links are normalized in a single pass by the URL Rewriter — same code path for both render modes. See CONTEXT.md for the domain language and ADR-0001 for the AppState shape rationale.
The demo/ directory ships with three fictional projects so you can try VibeDocs without setting up your own:
cirrus-api/— REST API reference with mermaid diagrams, error tables, and per-endpoint pagescirrus-sdk/— TypeScript / Python / Go SDK docs with typed code samplescirrus-dashboard/— UI component library docs with prop tables and design tokens
VIBEDOCS_ROOT=$(pwd)/demo npm startThe demo content is entirely fictional (stratus-key-DEMO-12345, https://api.cirrus.example.com). Nothing in it references a real product.
| Variable | Default | Description |
|---|---|---|
VIBEDOCS_ROOT |
current working directory | Root directory to scan for projects |
VIBEDOCS_PORT or PORT |
8080 |
Port to listen on |
VIBEDOCS_WS_ALLOWED_ORIGINS |
(unset) | Comma-separated extra Origin allowlist for the WebSocket handshake. Defaults cover localhost. |
VIBEDOCS_WS_ALLOW_NO_ORIGIN |
false |
Accept WS handshakes with no Origin header (non-browser clients) |
VIBEDOCS_UPLOAD_TOKEN |
(unset) | Bearer token gating POST /api/upload/*. When unset, upload endpoint 404s. |
VIBEDOCS_READ_ONLY |
false |
Force read-only mode — upload endpoint 404s even with a valid token |
VIBEDOCS_UPLOAD_MAX_BYTES |
10485760 (10 MB) |
Per-file upload size cap |
VIBEDOCS_ROOT should contain project directories, each with markdown files:
$VIBEDOCS_ROOT/
├── project-a/
│ ├── README.md
│ ├── CLAUDE.md
│ └── docs/
│ ├── getting-started.md
│ └── api-reference.md
├── project-b/
│ ├── README.md
│ └── docs/
│ └── architecture.md
└── ...
Each subdirectory becomes a "project" in the sidebar. Root-level .md files and everything under docs/ are displayed.
npm run dev # Backend (8080) + Vite dev server (5173) with HMRThe Vite dev server proxies /api/* to the backend, giving you hot module reload for frontend changes and auto-restart for backend changes.
npm run dev:server # Backend only
npm run dev:frontend # Frontend only
npm test # Run tests (vitest)
npm run build # Build the frontend bundle- Backend: Hono + TypeScript + Node.js 20+
- Frontend: React 19 + Vite + shadcn/ui + Tailwind CSS v4
- Markdown: unified / remark / rehype pipeline
- Syntax highlighting: Shiki (dual-theme via CSS variables)
- Diagrams: Mermaid.js (lazy-loaded, zero cost on diagram-free pages)
- Live reload: chokidar + WebSocket
VibeDocs is designed to run as a persistent service. A systemd unit file is included in systemd/vibedocs.service — edit the paths and run scripts/setup-service.sh to install it.
See scripts/promote.sh for a build-validate-restart workflow.
When exposing beyond localhost (tailnet, LAN, public), set VIBEDOCS_WS_ALLOWED_ORIGINS to every URL the browser will load the app from — otherwise live reload silently fails (this is intentional: it blocks cross-site WebSocket hijacking).
CONTEXT.md— domain language and architecture overview (AppState seam, ports/adapters split, Render Modes, URL Rewriter)docs/adr/— architectural decision recordsCLAUDE.md— project memory and conventions for Claude Code sessions
VibeDocs was built in collaboration with Claude Code — Anthropic's agentic coding tool. Every line of the Hono backend, the unified markdown pipeline, the React frontend with shadcn/ui components, and the test suite came out of conversational sessions with Claude.
Deep adversarial reviews — stress-testing architectural plans, surfacing failure modes, grilling design assumptions before they shipped — came from Grok 4.x. Several of the decisions recorded in docs/adr/ and the audit work in v0.2.0 were shaped by that two-model loop: Claude proposes, Grok pushes back, the design that lands is whatever survives both rounds. See docs/arch-viz-adversarial-review.md and docs/arch-viz-grounded-response.md for one example of that pattern in action.
It started as a one-shot "show me my markdown" viewer and grew iteratively: discovery, rendering, search, live reload, theming, table of contents, then two render modes, the AppState consolidation, and the URL Rewriter — each one a focused session. The CLAUDE.md file in this repo is the project memory that ties those sessions together.




