bliss/CLAUDE.md

87 lines
6.6 KiB
Markdown
Raw Normal View History

2026-08-02 22:06:42 -04:00
# 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` bound in its own vm context — see `bootstrapContext` 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 `<head>`, and attaches `data-bliss-route` / `data-bliss-clone` / `data-bliss-copy` attributes to `<body>` (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`): `<div hx-get="{url}" hx-trigger="load"></div>`. 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/`.