Architecture

Three views of one room

Conceptual, logical, and physical. The written design follows the drawings, and it is the same text kept in the repository.

Conceptual

The visitor, the lessons, the playground, and the records, with no account between them.

VisitorReads, then writes SQLLessonsSimple selectthrough recursionPlaygroundEditor, Run, ResetResult gridLiving pagesVisionArchitecture, releasesIn-browser databaseOne private copyper visitSeed recordsScholars and worksCitations as a graph

Logical

Lesson text and the seed feed the editor. The editor asks the in-browser engine. The living documents feed the three reading pages.

content/lessons.tsChapter, SQL, noticeMySQL notecontent/seed.sqlTables and rowsLoaded at opendocs markdownVision, architectureChangelogLesson pagesTry it loads the SQLSQL editorAny statementReset restores seedRendered pages/vision/architecture, /releasessql.jsexecute()list of tablesResult gridColumns and valuesNULL shown as NULL

Physical

A static host stores files. The browser downloads them once and runs every query in memory.

Static hostCloudflare Pages or Vercel HobbyHTML, CSS, JavaScriptNext.js static exportsql-wasm.wasmSQLite, MIT, in the fileSeed inside the pagecontent/seed.sql at buildBrowser tabWhere the query runsWebAssembly engineOpens an empty databaseMemory tablesGone when the tab closesQuery resultsStay on this machineonce

Architecture and design

Product: Scriptorium

Last updated: 2026-10-15

Shape

Next.js 15 App Router, TypeScript, Tailwind 4, static export. SQL runs in the browser through sql.js, which is SQLite compiled to WebAssembly. The seed is read from content/seed.sql when the play page is built and handed to the editor. There is no server process and no account.

Visitor → /                 lessons, Try it
        → /play             editor, schema, results, Reset
        → /vision           this folder's vision document
        → /architecture     three drawings, then this document
        → /releases         CHANGELOG.md

Key files

AreaPath
Lessonscontent/lessons.ts
Seedcontent/seed.sql
Engine callssrc/lib/execute.ts, src/lib/database.ts
Playgroundsrc/components/Playground.tsx
Drawingssrc/components/architecture/Diagrams.tsx
Documentsdocs/VISION_AND_REQUIREMENTS.md, docs/ARCHITECTURE_AND_DESIGN.md, CHANGELOG.md
Tokenssrc/app/globals.css
Chromesrc/components/SiteHeader.tsx, SiteFooter.tsx, Logo.tsx
Docs rule.cursor/rules/living-docs.mdc
Wasm copyscripts/copy-wasm.mjs places sql-wasm.wasm in public/

Engine

Each visit opens an in-memory database and runs the seed. Queries stay in that tab. The result grid shows up to 500 rows and labels the rest as truncated. NULL is printed as NULL. The table list scrolls inside its panel.

Reset closes that database and opens a new one from the same seed. A browser refresh does it too, because the database is not written to disk. A failed open does not stick: the next open or Reset tries again. If the seed itself fails, that database is closed before the error is shown.

The curriculum is SQL. Where MySQL differs, the lesson carries a MySQL note: SUBSTRING and CHAR_LENGTH, DATE_FORMAT and TIMESTAMPDIFF for real dates, AUTO_INCREMENT, GROUP_CONCAT … SEPARATOR, START TRANSACTION, EXPLAIN, CREATE INDEX without IF NOT EXISTS, window frames (ROWS BETWEEN, EXCLUDE), and pivot and unpivot written as CASE and UNION ALL. Stored procedures and @ user variables are reading lessons. MATCH_RECOGNIZE and the PIVOT and UNPIVOT operators are Oracle illustrations: the editor is read-only, because MySQL does not support them, and Run fills the grid from an equivalent query, labeled Oracle. A walkthrough under the grid explains how the statement produced those columns. Those words inside a comment or a quoted string are ordinary SQL and run on this engine. An edited operator statement does not. MySQL does not implement those operators, and neither does this engine. Tests run every other lesson against a fresh seed and check that the Principia walk reaches Elements, that a rolled-back update leaves Hypatia's 60 hours in place, that Newton's one-row moving totals are 40, 65, and 37, and that England pivots to two papers and one treatise.

citations is a directed graph. The recursive lesson starts at Principia and follows cited works, with a depth guard.

Three views

The architecture page draws these before the prose.

  • Conceptual — a visitor, lessons, a playground, the living pages, the in-browser database, and the seed records.
  • Logical — content/lessons.ts feeds the lesson pages and the editor. content/seed.sql feeds the engine. execute returns grids. The markdown files feed /vision, /architecture, and /releases.
  • Physical — a static host holds HTML, CSS, JavaScript, sql-wasm.wasm, and the seed baked into the play page. The browser downloads those files and runs the engine in memory.

Visual system

Paper #F6EAD8, ink #152028, muted #5A6B76, accent #0F4C5C, line #D8C4AA. Display type is Source Serif 4. Body is Source Sans 3. SQL is IBM Plex Mono. Panels are frosted paper. The header and footer use the same paper at 0.9 opacity. On a narrow screen the name sits above the navigation.

The backdrop is public/hero-bg.jpg: scholars at a table of manuscripts and loose papers. A paper wash, opaque in the middle and thin at the edges, keeps the column readable. Below 640px the wash is vertical and the image scrolls.

The mark is an open folio on a tan circle, not the AetherForge horseshoe.

Hosting

output: 'export' writes out/. Cloudflare Pages or Vercel Hobby can serve that directory. No environment variables. npm install copies the wasm file into public/ so the export includes it. The copy is gitignored. The source repository is https://github.com/jmaret/scriptorium. The three typefaces are files in the repository, so the production build does not download fonts.

Safety of a local engine

The database has no file access and no network. A visitor can drop a table in their own tab. Reset and refresh rebuild it. There is no other visitor's data to damage.

Changelog

  • 2026-10-15 — The typefaces ship with the site, so a production build does not download them.
  • 2026-10-15 — A failed open can be retried. Operator names inside a comment or a quote run as ordinary SQL.
  • 2026-10-15 — Source repository: https://github.com/jmaret/scriptorium.
  • 2026-10-15 — MATCH_RECOGNIZE, PIVOT, and UNPIVOT are read-only, with a walkthrough under the Oracle grid.
  • 2026-10-15 — MATCH_RECOGNIZE, PIVOT, and UNPIVOT return an Oracle illustration of the lesson statement.
  • 2026-10-15 — Pivot and unpivot run in the page. The PIVOT and UNPIVOT operators are shown and not executed.
  • 2026-10-15 — Moving frames and EXCLUDE run in the page. MATCH_RECOGNIZE is shown and not executed.
  • 2026-10-15 — Static Next.js room, sql.js in the browser, three architecture drawings, living documents rendered as pages.