Bench-testing, Embedded Networks, & Circuit Hacks: Centralized Open-source Research Engine
Markdown authoring. PostgreSQL publishing. An engineering-log interface.
BenchCore is a self-hosted research publishing platform with a SvelteKit frontend and a
standalone TypeScript API. Write Markdown with TOML frontmatter, connect notes
with Obsidian-style [[wikilinks]], and publish through a session-protected
admin editor. PostgreSQL is the runtime source of truth; the frontend never
reads the database or the authoring directory.
Stable release of the v0.7.0 milestone: knowledge navigation and media refinement.
- Link previews: hover or focus a wikilink or graph node for a lightweight session-aware preview. Unpublished records stay masked and reader-only bodies stay redacted, with no persistent client preview cache.
- Fuzzy suggestions: accent-insensitive editor completion across slugs, titles and tags, without new dependencies.
- Reader navigation: collapsible heading index from sanitized Markdown headings with stable anchors, fitted graph views and focused neighborhoods.
- Archive filters: combine up to 20 tags with AND/OR, sort by publication, last update or existing likes, and preserve filters across pages. Protected engagement counts and rank stay hidden from guests.
- Media lifecycle: dry-run-first orphan cleanup CLI with bounded batches and saved-reference rechecks, strict 5 MiB direct-upload limits with exact signed length/type and owner-bound completion, immediate local previews with measured progress and reordering, and audience-safe cache controls.
- Delivery gates: stable workspace metadata with exact-tag validation and separate stable/beta approval environments. Packaging and publication remain separate operator actions.
For release checks and the remaining operator gates, see
0.7.0 release preparation. pnpm release:check
validates workspace versions; pnpm release:package builds the versioned Node
bundle after pnpm build. Neither command tags, publishes or deploys.
Media management. The orphan cleanup CLI defaults to dry-run with bounded batches and saved-use rechecks; apply mode needs stopped writers and an explicit maintenance acknowledgement. Direct uploads keep the allowlisted MIME set and the 5 MiB cap with exact signed length/type and owner-bound completion.
Graph navigation. Published posts connect through shared tags with linear-size hub links for large topic groups, plus topic shortcuts, readable graph labels, related-post lists and direct reading links.
Reader UX. Shared typography, navigation, topic cards, touch targets, keyboard focus and recovery messages were refined; reader and admin pages hold at the tested narrow viewport with no horizontal overflow.
Admin safety. Title/slug/status filters, explicit delete confirmation, image link copy controls with clipboard feedback and a selectable manual fallback, and server-side pagination with status counts.
Vercel and private R2. Full app and API on Node 24 Vercel Functions with no listeners or startup migrations, browser-direct R2 uploads via signed length/type with owner-bound tickets and immutable conditional publication, and scheduling temporarily disabled on that target.
Online beta. One public Node service for frontend and /api/* on 5180
with a private loopback SSR bridge, reader-only registration, admin user
management with last-active-admin protection, own-password changes with global
revocation, and tag-matched environment-gated prerelease delivery.
The full checklist lives in TODO.md.
- Markdown + TOML —
+++frontmatter, validated metadata, sanitized HTML, tables, code blocks, images, excerpts, and reading-time estimates. - Wikilinks & backlinks —
[[slug]]and[[slug|label]]link notes; backlinks show only visible source posts. Hover or focus a wikilink or graph node for a session-aware preview. The reader includes a heading index. - Public publishing — home, paginated posts, post detail, tags, and tag archives. Draft, archived, and not-yet-public content stays out of public data.
- Search —
GET /api/posts?search=...; PostgreSQL generatedtsvectorwith thesimpleconfiguration and a GIN index. Combine tags with AND/OR and sort by publication, updates or existing likes. Editor suggestions match fuzzy titles, slugs and tags. - Admin workbench — session-protected deck, create/edit/delete posts, write/preview modes, comma-separated tags, and image uploads.
- Publishing lifecycle — draft, published, archived; publication timestamps
and a separate scheduled
publishAtvalue in the API/admin. The 0.5.0 due publication job runs approximately once per minute. - Media storage seam —
StorageProviderisolates callers from the backend;MEDIA_STORAGE=local|database|r2selects filesystem images, database blobs or private R2. R2 batches have immediate previews, measured upload progress and attachment reordering.pnpm media:cleanupreports orphan counts without deleting; apply mode requires an explicit maintenance acknowledgement. - Authentication — scrypt password hashes, random session tokens, hashed tokens at rest, HttpOnly cookies, SameSite=Lax, and Secure production cookies.
- Database accounts — username/email login; provision an administrator through
private stdin with
pnpm user:create, without storing account credentials in.env. - Security hardening — exact Origin allowlists, bounded in-memory rate limits, response security headers, and frontend Content Security Policy in the 0.5.0 milestone.
- SEO & feeds — canonical URLs, metadata, sitemap, RSS, and robots routes.
- Three themes — light, dark, and OLED; CSS tokens, persisted preference, and a pre-paint script to reduce theme flashes.
- Self-hosting — one Node service for frontend and API, with PostgreSQL; explicit migrations, admin bootstrap, and repeatable content import.
- Quality gates — Vitest suites, frontend/API typechecks, frontend lint, and production builds; CI runs the same checks.
- No application telemetry — no built-in analytics, tracking SDKs, or third-party trackers. Hosts and reverse proxies can still log requests.
Scope: 0.7.0 is prepared locally, not a published package or a promise of a hosted service. Revision storage is groundwork only: no automatic revision capture, history browser, diff, or restore workflow is claimed. Basic moderated comments and anonymous likes are implemented. Advanced anti-spam, MFA, revision workflows and other deferred features are not claimed. See TODO.md for milestone status.
The production runtime combines frontend and backend into one Node service.
Pages and /api/* share the same domain and port. Run pnpm build, configure
the database and trusted origin, then run pnpm start on port 5180.
/register creates readers only; /account changes passwords; /admin/users
manages accounts and protects the final active administrator.
Node deployment covers the unified Node artifact, GitHub CI/CD, HTTPS hosting, explicit migrations, backups and release limits. No GHCR/container images are required. GitHub Pages cannot run the backend.
Vercel deployment runs independently built frontend and API services in one project/domain, with a private runtime binding and R2 browser-direct uploads. Scheduled publication is temporarily disabled on that target; the persistent Node runtime remains available.
| Layer | Responsibility |
|---|---|
api/src/markdown/ |
Split +++ fences, parse TOML, validate with Zod, render and sanitize Markdown |
api/src/posts/ |
Publishing rules, timestamps, slug conflicts, imports, public DTOs |
api/src/db/ |
Repository contracts, memory test doubles, Drizzle implementations and schema |
api/src/auth/ |
Password verification, token generation, session hashing and cookies |
api/src/media/ |
Swappable image storage behind StorageProvider |
api/src/server.ts |
HTTP boundary and routing; services own business rules |
frontend/src/lib/ |
Server-only API clients, SEO URL helpers, components, theme state |
frontend/src/routes/ |
Public pages, authentication, guarded admin, feeds and metadata routes |
The database and uploaded media contain runtime state. Import is a deliberate upsert by slug, not a live filesystem watcher or two-way editor synchronization. Re-importing a file can overwrite later admin edits.
| Version | Focus | Status |
|---|---|---|
| 0.2.0 | Content pipeline and auth core | Shipped |
| 0.3.0 | Database, API and media | Shipped |
| 0.4.0 | Public site and admin | Shipped |
| 0.5.0 | Hardening and release | Prepared locally |
| 0.6.0-beta.1 | Online beta preparation | Shipped |
| 0.6.0 | Media management and storage refinement | Shipped |
| 0.7.0 | Social graph, links and knowledge navigation | Shipped |
| 0.8.0 | Engagement, moderation and auth hardening | Pending |
| 0.9.0 | Stabilization, performance and deployment parity | Pending |
| 1.0.0 | Production readiness and developer experience | Pending |
160 API + 135 frontend tests, workspace/runtime typechecks, lint and production builds pass. Disposable browser tests cover admin creation, editing, preview, deletion confirmation/cancellation and cleanup. Selected WCAG A/AA checks report no violations. Live PostgreSQL, hosted R2 and deployment checks were not run.
Prerequisites: Node.js 24, pnpm 10.15.0, and PostgreSQL 17.
git clone https://github.com/camiu01/BenchCore.git
cd BenchCore
pnpm install --frozen-lockfileIf pnpm is not installed, use npx --yes pnpm@10.15.0 in place of pnpm
throughout the commands below. Do not substitute an unpinned pnpm@latest.
To see the public interface without PostgreSQL, run pnpm demo:api and
pnpm dev:frontend in separate terminals. The preview uses synthetic public
notes and keeps authored drafts private. Set ADMIN_USERNAME and
ADMIN_PASSWORD in the preview process environment to create an optional
in-memory administrator. See Development.
Copy .env.example to .env, set development-only database/admin values, and
load the required variables into the processes you launch. A root .env is a
reference file, not a guarantee that every CLI loads it automatically.
See Development for PowerShell and POSIX setup.
pnpm db:migrate
pnpm seed
pnpm dev:apiThe API development command runs
tsx watch --env-file-if-exists=../.env src/index.ts from api/.
The watch subcommand must precede Node flags; otherwise Node treats
watch as a filename instead of starting the file watcher.
In another terminal, with the same relevant environment:
pnpm dev:frontendOpen http://localhost:5173; the API listens on http://localhost:5181.
If your database already has an operator account, skip pnpm seed and sign in.
To create an account without importing posts, use pnpm user:create as described
in Operations. Sign in at /login, then open /admin. Set SITE_URL and
PUBLIC_SITE_URL to http://localhost:5173 for local development.
pnpm build
pnpm beta:migrate
pnpm startConfigure .env with the intended PostgreSQL connection and trusted SITE_URL
before applying migrations. Pages and /api/* share port 5180.
Admin creation is an explicit operation, not part of startup.
Use Beta deployment for artifact installation,
production settings and account provisioning.
pnpm --filter benchcore-api exec tsc --noEmit
pnpm check
pnpm test
pnpm lint
pnpm buildflowchart TB
Browser["Browser: readers and administrators"]
Frontend["SvelteKit frontend"]
API["TypeScript HTTP API"]
Services["Authentication, publishing and Markdown services"]
Repositories["Repository contracts + Drizzle"]
Database[("PostgreSQL: posts, tags, users and sessions")]
Files["content/posts/*.md: Markdown + TOML"]
Import["Explicit content import"]
Storage["StorageProvider"]
Local["Local filesystem"]
Blobs["Database media repository"]
R2["Private R2 bucket"]
Browser <-->|Pages and admin forms| Frontend
Frontend <-->|API data and authenticated commands| API
Browser <-->|Same-origin media and upload endpoints| API
API --> Services
Services <--> Repositories
Repositories <--> Database
Files --> Import
Import --> Services
API --> Storage
Storage <--> Local
Storage <--> Blobs
Blobs <--> Database
Storage <--> R2
Browser -.->|R2 only: signed direct upload| R2
- A reader opens a page. SvelteKit asks the API for visible posts, and the API queries PostgreSQL through repositories. The frontend receives API data, never a database connection. Drafts and archived posts remain private; reader-only bodies require a reader or administrator session.
- An administrator signs in and edits a post. The API checks the session and trusted Origin, validates metadata, sanitizes rendered Markdown and saves through the repositories. Preview renders HTML without saving the post.
- Markdown files enter the database only through an explicit import. Import parses TOML, validates fields and applies the same publishing pipeline. Editing a file does not update the running site until it is imported.
- Images use the configured storage provider. Local and database uploads pass through the API. For R2, the API authorizes an upload, the browser sends the image directly to R2, and the API validates completion before accepting its managed URL.
The diagram shows logical boundaries. Development uses frontend port 5173
and API port 5181; the production Node runtime serves pages and /api/*
through one origin on port 5180.
content/posts/ # Markdown + TOML authoring source
api/
src/auth/ # scrypt passwords and token sessions
src/db/ # contracts, schema, Drizzle, memory repos
src/markdown/ # frontmatter validation and safe rendering
src/posts/ # publishing, CRUD, import
src/media/ # StorageProvider and backend implementations
src/server.ts # standalone node:http API
src/index.ts # dependency wiring and boot
scripts/ # migration, seed, content import
drizzle/ # committed SQL migrations
tests/ # Vitest suites
frontend/
src/lib/components/ # document shell, SEO, theme picker, editor
src/lib/server/ # authenticated server-only API client
src/routes/ # public pages, login/logout, admin, feeds
src/app.css # theme tokens and spec-sheet design
src/app.html # page shell and pre-paint theme script
tests/ # Vitest suites
docs/ # developer, author, API, operations guides
.github/ # CI and contribution templates
runtime/ # unified production Node listener
scripts/ # allowlisted packaging and isolated SQL smoke
TODO.md # milestone roadmap
Conventions: tabs, single quotes, semicolons, TSDoc on every function, feature files <400 lines, functions <50 lines.
An engineering-log spec sheet: monospace typography, blueprint grid, bordered
sheet with a hard shadow, record cards, stamp badges, and inventory tables.
Themes use data-theme and CSS variables rather than independent page palettes.
Preference lives in localStorage under site-theme; it is not an analytics
identifier. See Admin & themes.
- Separate frontend and API so deployments and storage boundaries stay explicit.
- Plain
node:httprather than a web framework for a compact API. - TOML
+++fences avoid confusion with Markdown horizontal rules. - PostgreSQL owns published runtime data; Markdown remains a portable source.
- Services own publishing visibility; unpublished slugs always mask as 404.
- HTML is sanitized before it reaches a page or editor preview.
- Repository interfaces keep unit tests independent of a live database.
- Media callers use an interface, never filesystem paths or blob SQL.
- SvelteKit 3 configuration lives in
vite.config.ts; relative imports and server-onlyprocess.envkeep tooling and secret boundaries predictable. - Dependencies are pinned; pnpm's lockfile is part of the reproducible build.
| Guide | Contents |
|---|---|
| Documentation index | Suggested reading paths and scope |
| Architecture | Data flow, module boundaries, database, trust boundaries |
| Development | Prerequisites, shell setup, environment, commands |
| Authoring | TOML fields, Markdown, links, import, scheduling |
| API | Routes, payloads, cookies, Origin, search, media, errors |
| Admin & themes | Editorial workflow, image handling, design tokens |
| Operations | Deployment, migrations, backup/restore, incident checks |
| Testing | Automated checks, regression coverage, manual smoke tests |
| Troubleshooting | Common setup and runtime failures |
| Contributing | Workflow, conventions, PR checklist |
| Security policy | Private reporting, privacy, supported scope |
| Threat model | Assets, attackers, controls, operational limitations |
Maintained by Camiu (@camiu01). GNU Affero General Public License v3.0 (AGPL-3.0-or-later) — see LICENSE.