Plug-and-play universal digital e-book & documentation engine.
Point it at any folder of Markdown files and it becomes a searchable, paginated, themeable documentation reader — no config, no frontmatter requirements, no rebuild step for content changes.
- What it does
- Quick start
- Configuration
- How it works
- Project layout
- Deployment
- Documentation
- Contributing
- License
Most documentation tools ask you to restructure your content: add frontmatter,
maintain a sidebar config, register routes. edoc-pnp asks for nothing. It
recursively scans a directory, infers the table of contents from the folder
structure and headings, computes continuous e-book page numbers from word
density, and builds a full-text index — all at server startup.
| Capability | Detail |
|---|---|
| Zero configuration | Drop in .md files; the TOC, badges and categories are derived automatically |
| Continuous pagination | Documents get real e-book page ranges (~300 words/page) across the whole corpus |
| Full-text search | FlexSearch index over titles, headings and body text; Ctrl+K palette with keyboard navigation |
| Mermaid diagrams | Fenced ```mermaid blocks render as live diagrams |
| Syntax highlighting | Prism, with bash / json / yaml / python / javascript / typescript / rust bundled |
| 7 themes | Obsidian, Midnight OLED, Cyberpunk, Deep Space, Warm Sepia, Aurora Teal, Light Glass |
| Reader controls | 4 font families, 5 reader widths, adjustable font size and line height — persisted to localStorage |
| Reading progress | Live progress bar, per-document outline, prev/next pagination |
| Frontmatter aware | Optional YAML frontmatter (title, phase, category, badge) overrides inferred values |
Requirements: Node.js 18 or newer (developed on Node 24) and npm.
git clone <your-repo-url> edoc-pnp
cd edoc-pnp
npm install
cp .env.example .envPut your Markdown somewhere and point DOCS_DIR at it in .env:
DOCS_DIR=./docs
PORT=3000Then build the frontend and start the server:
npm run build
npm startOpen http://localhost:3000.
Two processes, because the API and the Vite dev server run separately:
npm run dev:server # terminal 1 — Express API on $PORT
npm run dev:vite # terminal 2 — Vite with HMR on :5173Visit http://localhost:5173. Vite proxies /api/* to the Express server using
the PORT value from your .env, so .env must exist before starting Vite.
| Script | Purpose |
|---|---|
npm run dev:server |
Start the Express API only (no static serving) |
npm run dev:vite |
Start the Vite dev server with HMR and API proxy |
npm run dev |
Alias for dev:server |
npm run build |
Build the production frontend bundle into ./dist |
npm start |
Serve API and the built ./dist bundle in production mode |
All configuration is environment variables — see .env.example.
| Variable | Default | Description |
|---|---|---|
DOCS_DIR |
./docs |
Folder to scan for Markdown. Relative paths resolve from the process working directory. |
PORT |
3000 |
Port the Express server binds to. Also the proxy target for the Vite dev server. |
NODE_ENV |
— | When production, the server also serves the static ./dist build. npm start sets this. |
Place an edoc.config.json file inside your docs directory to override the
title and subtitle that would otherwise be inferred from the docs' root
README.md:
{
"title": "My Product Handbook",
"subtitle": "Everything you need to operate the platform"
}Any document may declare frontmatter to override inferred metadata:
---
title: Ticket Model
phase: Phase 1 — Master Specification
badge: PART IV
---graph LR
A[DOCS_DIR] --> B[Recursive scanner]
B --> C[gray-matter frontmatter parse]
C --> D[Heading extraction + slugify]
C --> E[Page calculator ~300 w/page]
C --> F[FlexSearch index]
D --> G[JSON REST API]
E --> G
F --> G
G --> H[React reader UI]
At startup initializeSpecEngine() walks DOCS_DIR, skipping dotfolders,
node_modules and dist. Each .md file is parsed for frontmatter and
headings, assigned a title (frontmatter → first H1 → filename), a category
(frontmatter → formatted folder name), a badge, a word count, a read-time
estimate and a continuous page range. Everything is held in memory and exposed
over three read-only endpoints.
| Endpoint | Description |
|---|---|
GET /api/docs/structure |
Project metadata, total page count, phase groupings, full document index |
GET /api/docs/content?path=<rel> |
Parsed document with headings, page range, and prev/next links |
GET /api/search?q=<query> |
Full-text search results with highlighted excerpts (min. 2 characters) |
Full request/response schemas: _docs/03-reference/01-api.md.
Note: the index is built once at startup. Restart the server after adding or editing documents.
edoc-pnp/
├── server.js # Express API + spec engine (scan, index, paginate)
├── vite.config.js # Vite config with /api dev proxy
├── index.html # SPA entry point
├── src/
│ ├── main.jsx # React root
│ ├── App.jsx # Layout shell and data fetching
│ ├── index.css # Theme variables and global styles
│ ├── context/
│ │ └── SettingsContext.jsx # Theme/typography state + keyboard shortcuts
│ └── components/
│ ├── Header.jsx # Top bar, search trigger, progress bar
│ ├── LeftSidebar.jsx # Index and per-document outline tabs
│ ├── IndexPage.jsx # Landing page with phase-grouped catalogue
│ ├── ReadView.jsx # Markdown renderer and reading surface
│ ├── CodeBlock.jsx # Prism highlighting with copy button
│ ├── DiagramBlock.jsx # Mermaid renderer
│ ├── SearchModal.jsx # Ctrl+K command palette
│ └── SettingsModal.jsx # Theme, font, width, spacing controls
├── _docs/ # Documentation for edoc-pnp itself (underscore
│ # keeps it out of the default ./docs scan path)
└── tessera-docs/ # Bundled example corpus — illustrative only,
# not a dependency, safe to delete
Deployment recipes for Docker, systemd, Nginx, PM2 and PaaS providers live in
_docs/04-deployment/. The short version:
npm ci --omit=dev && npm install --include=dev # build deps needed for vite
npm run build
NODE_ENV=production PORT=3000 DOCS_DIR=/srv/docs node server.jsSee the deployment guide for hardening, process supervision and reverse-proxy configuration.
To put a free public demo online, the
Render guide deploys the bundled
Dockerfile with no code changes.
This project's own documentation lives in _docs/. Because it is
written in the same zero-config Markdown the engine consumes, you can read it
inside the reader itself:
DOCS_DIR=./_docs npm start| Section | Contents |
|---|---|
| Getting started | Installation, quickstart, configuration |
| Architecture | Pipeline overview, spec engine, frontend design |
| Reference | REST API, environment variables, keyboard shortcuts, theming |
| Deployment | Production, Docker, reverse proxy, Render |
| Contributing | Development workflow, code style |
Note
The folder is deliberately named _docs/ and not docs/. Since ./docs is
the application's default DOCS_DIR, a folder by that name would be picked up
and rendered automatically — the app would publish its own documentation
instead of yours. The leading underscore keeps it out of that default path.
The repository ships a tessera-docs/ directory containing a
29-document specification. It exists purely as example content — a corpus
large enough to demonstrate multi-level categories, continuous pagination,
grouping and full-text search against something realistic.
To be explicit:
-
Tessera is not part of this project. It is not a dependency, it is not imported by any source file, and no code in
edoc-pnpreferences it. -
Nothing breaks if you remove it. Delete the folder and point
DOCS_DIRat your own content:rm -rf tessera-docs
Remember to update
DOCS_DIRin your.envif it still points there, and the volume mount indocker-compose.ymlif you use Compose.
The sample content is sourced from the Tessera project — a filesystem-native resource automation framework — and is included for illustration only: https://github.com/kazkriska/tessera
Contributions are welcome. Please read CONTRIBUTING.md and the Code of Conduct before opening a pull request.
Released under the MIT License.