# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Commands ```bash node index.js # Start the server (port 3000) npx prettier --write . # Format code ``` No build step. No test suite. Restart the server to pick up changes to `index.js` or `db.js`; changes to Eta templates in `views/` are picked up live (cache is disabled). Requires a `.env` file with `VAPID_PUBLIC_KEY` and `VAPID_PRIVATE_KEY` for web push. ## What This Is Bliss is a browser-based low-code platform where users build mini web apps ("Structures") entirely through a `/workshop` UI. Each Structure has routes, templates, and SQLite databases — all stored in the main app database and executed at runtime via Node's `vm` module. ## Architecture ### Two layers of routes The Express app has two kinds of routes: 1. **Workshop routes** (hardcoded in `index.js`) — The `/workshop/*` editor UI for managing structures, routes, templates, databases, and files. 2. **User routes** (dynamic, loaded from DB) — Caught by the `app.all("*")` handler at the bottom of `index.js`. At request time it matches the path against the `routes` in-memory array (rebuilt by `buildRoutes()`), finds the matching route record, and executes the user's `handler` JS string inside a sandboxed VM context. `buildRoutes()` must be called after any route is created/updated to refresh the in-memory matcher array. ### Sandboxed execution (`bootstrapContext`) User route handlers run in `vm.createContext()` with a restricted API: - `require('eta')` — Eta template instance scoped to the structure - `require('db')` — getter for named database instances. `require('db')(alias)` returns `{ library, sql }`: `sql` is the raw `better-sqlite3` instance for that db, and `library` is `module.exports` from that db's own "library" script (arbitrary JS, edited via `/workshop/:structure_id/db/:db_id/library`, run once per request with `sql`, `console`, and `fetch` bound in its own vm context — see `runLibrary` in `index.js`). Handlers typically call helper functions off `library` rather than writing raw SQL inline. - `require('push')` — web-push library - `require('files')` — `{ saveFile }` helper - `console.log` — writes to the app's `logs` table (not stdout) - `fetch`, `setTimeout`, `clearTimeout` - `route(url)` — prefixes a URL with the structure's `route_prefix` Handlers call `res.render(templateName, context)` or `ws.render(templateName, context)` to render Eta templates. These are injected onto `req`/`res` after the context is created (not available inside `bootstrapContext` itself). WebSocket routes are bootstrapped once by `bootstrapWebsocketHandler` and stored in `wsRoutes`; subsequent saves only update the handler function without re-registering the ws path. ### Template rendering (`bootstrapTemplateWithHTMXetc`) Every HTML response passes through this function, which uses Cheerio to inject HTMX, Tailwind, Hyperscript, and `bliss_inspector.js` into the ``, and attaches `data-bliss-route` / `data-bliss-clone` / `data-bliss-copy` attributes to `` (or to fragment children for HTMX partial swaps). These attributes drive the in-page editor overlay. - `data-bliss-route` — link to the workshop editor page for whatever produced this element. - `data-bliss-clone` — `hx-get` target for the clone-structure modal. - `data-bliss-copy` — only set for GET routes, via `embedHTML(url)` (`index.js`): `
`. This is the actual page-nesting primitive — the inspector's 📋 button copies this snippet to the clipboard, and pasting it into any other template lazy-loads that route as a live child page via HTMX. This is how Structures compose: pages nesting pages nesting pages, each one an independently addressable, independently editable route. ### The inspector (`bliss_inspector.js`) and where it's headed Currently the 🔍 overlay (injected into every rendered page, see above) only highlights `[data-bliss-route]` elements and offers links out to the editor, a copy-embed button, and a clone-structure modal — it does not let you edit anything in place yet. The long-term goal is for the inspector to become a real-time, in-page structure editor: instead of jumping out to `/workshop/...`, you'd edit a nested page's route/template directly where it's embedded and see it update live. Two things that design needs and don't exist yet: - Each embedded view needs to carry its **source** (which structure/route/template produced it) — `data-bliss-route` currently only gives a link out, not enough info to edit inline. - Each embedded view needs to carry its **args** — the context/params it was rendered with (e.g. `res.render(template, context)`'s `context`, URL/query params) — so the in-place editor can show and modify what was actually passed in, not just the output. Cross-fragment interaction (nested pages talking to each other) isn't a bespoke Bliss feature — it rides on standard HTMX/Hyperscript behavior since every nested page lives in the same DOM: `hx-swap-oob` for out-of-band updates (already used for head injection), and bubbling custom events (Hyperscript `send`/`trigger`, `hx-trigger="eventName from:body"`) for one nested page to notify siblings/ancestors. ### Database layer (`db.js`) - Main app DB: `./dbs/0.sqlite` — stores structures, routes, templates, dbs metadata, files, logs. - User databases: `./dbs/{id}.sqlite` — one SQLite file per user-created DB, opened on demand and cached via LRU (max 25 connections). - Eta template instances are also LRU-cached per structure (max 100). The cache must be invalidated if template content changes — currently the cache is not explicitly invalidated on update, relying on the LRU eviction policy. - Migrations in `migrations/` are applied sequentially on startup (tracked in a `migrations` table). ### Structure cloning `cloneStructure` (in `db.js`) deep-copies a structure's routes, templates, scaffold pages, and optionally its databases (full copy vs. aliased reference). Aliased DBs share the same SQLite file across structures. ### Views vs. user templates - `views/` — Eta templates for the workshop UI itself (not user content). - User-created templates are stored as rows in the `templates` table and resolved at render time via `getTemplater()` / `readFile`. ### Scaffold pages Non-GET routes (POST, PUT, DELETE, WS) get auto-generated scaffold pages — test forms or WebSocket test UIs — stored in `scaffold_pages`. These are served at `/workshop/:structure_id/route/:route_id/preview`. ### Frontend stack (workshop UI) HTMX + Hyperscript + Tailwind (CDN, preflight disabled) + `bliss_inspector.js` (the in-page editor overlay). All served from `public/js/`.