18 October 2026
LessonLab is a working local resource studio, version 0.1.0, with one complete generator: Maths → Number Lines → Subtraction. This handover gives the next developer the setup, implementation boundaries, compatibility rules and next development steps. Read AGENTS.md, README.md and ARCHITECTURE.md before changing those boundaries.
Worksheet definitions now pair a typed generator with its controls, inheritance policy, presets, preparation and dependent-field editing rules. Shared educational, paper, font and presentation schemas are composable. Each type keeps its own content settings; only explicitly allowlisted common settings are inherited. The workspace stores generator identity with its configuration and seed. History, draft restoration, previews and API exports resolve the registered definition and versions. Subtraction's custom settings UI and editing rules are isolated in its plugin and React editor. A multiplication test fixture verifies different fields and question data without adding an unfinished option to the teacher interface.
This supersedes the earlier note that the store and workspace require a typed subtraction adapter. See docs/ADDING_WORKSHEETS.md and tests/worksheet-plugins.test.ts for the continuation guide and executable contract tests.
The active studio now uses schema/layout/renderer version 6 and layout style beside. New worksheets, restored drafts and opened history entries use the question beside the number line. Questions and seeds are preserved. The active layout never falls back to an above-line question; crowded settings show validation guidance. Opening an older resource updates its active configuration, and Save writes a revision. This supersedes the historical layout reopening notes below, following the owner’s instruction that legacy support is unnecessary.
Plain numbers now use a closing parenthesis (1)) in both layouts and every export. Schema/layout/renderer version 5 stores optional presentation.layout.questionNumberSuffix as ). New defaults and explicit plain-number edits use this format. Historical records without the suffix keep dotted numbering and their original output; measurements account for the chosen suffix. API and history support schemas 1–5.
The Worksheet layout selector has been removed from Simple and Advanced settings. New worksheets place questions beside the line. Coloured rows remains available and preserves ink, questions and seed. Opening a saved worksheet applies the fixed beside-line arrangement. Answer boxes and distinct number badges remain available.
The optional presentation.appearance.colouredRows field introduces schema/layout/renderer version 4. New classic geometry reserves panel padding even when colours are off, so changing the switch does not move content or change pagination. Existing records without this field preserve schemas 1–3 and their original SVG. Explicit layout edits materialise the draft's existing visible row-colour choice; saved history is unchanged.
Simple settings now expose Answer spaces, Answer space (Writing line / Answer box), Question numbers and Question number style (Plain numbers / Number badges). New worksheets use outlined badges, visually separated from the calculation, in both above-line and beside-line layouts. Presentation changes preserve the questions and seed. Existing saved worksheets retain their plain numbering until a teacher selects badges and saves a new revision.
The optional presentation.layout.questionNumberStyle field introduces configuration schema 3. Badge numbering uses layout/renderer version 3; explicit plain numbering uses the original layout/renderer versions. Missing fields preserve schema-1 and schema-2 output. API and history readers accept schemas 1–5. tests/fixtures/reference-config.ts freezes schema-2 defaults alongside the existing schema-1 fixture. No algorithm change or storage migration is involved.
Teachers can discover a generator, configure a worksheet, preview multiple pages, export it and reopen saved generations. The latest change separates the above-line/beside-line layout choice from the Coloured rows switch. Both layouts support answer boxes and numbered badges; the beside-line layout retains taller ticks at multiples of five. Older saved worksheets retain their original layout.
The repository is initialized, but all project files are currently untracked. There are no commits and no configured Git remote. No deployment has been completed. Review the files and create an initial commit before further feature work.
Repository location
/Users/adam/Library/CloudStorage/Dropbox/dev/ios-ng/projects/webapps/LessonLab
Use Node.js 22.12 or newer and npm. Run these commands from the repository root. No credentials or external service setup are needed.
npm ci
npm run dev
Open http://127.0.0.1:6173. The development frontend uses port 6173 with a strict port setting; the API uses port 3001. The frontend proxies /api to the API. The earlier default of 5173 has been replaced in the development configuration, browser tests and QA scripts.
npm run build
npm start
Open http://127.0.0.1:3001. The compiled API serves the built frontend, including bookmarked application routes. PORT=3002 npm start selects another port. The API binds to loopback and has no production authentication; public multiuser hosting remains future work.
The npm workspace monorepo separates the teacher interface, local API, deterministic generation, rendering, exports and persistence. Educational context plus content settings and a seed produce questions. Presentation settings then produce physical page geometry. The shared SVG renderer supplies both the browser preview and export pipeline.
| Location | Responsibility |
|---|---|
| apps/web | React, Router, Zustand, shared controls, discovery, workspace, library and preferences. |
| apps/api | Express request validation, generation and export endpoints, production static serving. |
| packages/core | Generator contract, semantic page elements, physical paper sizes and seeded utilities. |
| packages/generators | Registry, subtraction schema, presets, content generation, classic and reference layouts. |
| packages/rendering | Framework independent SVG rendering and font measurement. |
| packages/export | Vector PDF, raster images and ZIP downloads from the shared SVG pages. |
| packages/persistence | Repository interfaces, local adapters, versioned history, drafts, preferences and template contract. |
| packages/shared | Vendored print fonts, character support data and font advance metrics. |
-
Keep content generation separate from presentation. Font, spacing, orientation and layout changes must preserve the seed and questions. Regenerate deliberately creates a fresh seed.
-
Use seeded randomness only. Generation enumerates feasible subtraction triples and samples without replacement unless repeats are explicitly allowed. Report impossible constraints before generation.
-
Use millimetres for page geometry and points for font settings. Keep rows whole during pagination. Derive tick positions from their actual numeric values.
-
Use one SVG renderer for preview and every export. Validate configurations at UI, API and persistence boundaries. Never accept arbitrary browser supplied SVG for export.
Workspace actions live in apps/web/src/state/store.ts; controls are in components/ConfigurationPanel.tsx. Application design tokens live in styles/tokens.css and are separate from worksheet styling. Generator defaults and control metadata are in packages/generators/src/config.ts. The classic and reference layouts are separate files; versions.ts derives compatibility versions.
The registry drives discovery, routes, shared workspace state, persistence parsing and API dispatch. Each worksheet definition supplies its own typed editing rules; an optional custom React editor handles richer controls. See docs/ADDING_WORKSHEETS.md before adding another worksheet type.
New worksheets default to eight questions on A4 landscape, UK England Year 2, standard difficulty and fully labelled number lines from 0 to 50. The print defaults use Lato, 17 pt questions, 8.5 pt labels, 10 mm margins, pastel reference rows and answer boxes. Name and date fields are enabled; the footer is disabled.
packages/generators/src/layout-reference.ts arranges the calculation and aligned answer space to the left of each number line. Rounded panels cycle through eight pastel colours. Endpoints and multiples of five receive taller ticks, while tick values and positions remain mathematically unchanged. The monochrome theme uses white panels and black ink.
When a horizontal line cannot fit its labels legibly, the layout moves a full width line below the calculation and reports the adjustment. Label fitting may reduce the label font to 7 pt with a warning. If that still fails, generation is rejected with guidance. Requested labels and intervals are never silently removed or changed.
The optional fields presentation.layout.style and presentation.appearance.rowTheme are deliberately absent in legacy configurations. Do not add defaults while parsing those records. Their absence preserves the original stacked layout and exact SVG output. Explicit classic style can also use schema 2 while retaining layout and renderer version 1.
| Configuration | Schema | Layout | Renderer |
|---|---|---|---|
| Legacy with both new fields absent | 1 | 1 | 1 |
| Explicit classic style | 2 | 1 | 1 |
| Reference style without numbering style | 2 | 2 | 2 |
| Badge numbering, either layout | 3 | 3 | 3 |
| Explicit plain numbering, classic/reference | 3 | 1/2 | 1/2 |
| Independent row colours without suffix, either layout | 4 | 4 | 4 |
| Closing-parenthesis suffix, either layout | 5 | 5 | 5 |
The subtraction generator algorithm remains version 1. Record versions come from documentVersions(config), rather than from current defaults. History and API validation accept supported schema 1, schema 2, schema 3, schema 4 and schema 5 records and reject incompatible version combinations.
The Worksheet layout selector is no longer offered. Coloured rows remains an independent switch for the current or historical layout. New worksheets default to the beside-line layout with colours enabled. Other saved settings remain in place, including an existing line answer style. New worksheets default to boxes and number badges.
tests/fixtures/legacy-config.ts freezes the original defaults. With seed print-proof, the legacy document ID is 7b6bff07 and its first page SVG hash is 9ff6badf. Do not update these expectations merely to make a changed renderer pass.
output/pdf/subtraction-number-lines-0-30-lessonlab.pdf
scripts/layout-proof.ts
The generated PDF is the repository sample for the supplied 0 to 30 design reference. The script reproduces it and writes working previews under .qa/layout-reference. The original reference PDF remains a user supplied Mail attachment outside the repository and may not be available on another machine.
The app uses browser localStorage behind repository interfaces, with an explicitly local development user. This is local persistence, not authenticated cloud storage. Initial generation, regeneration and export save snapshots. Manual setting changes persist as a draft; Save creates a new history record rather than overwriting an earlier generation.
A saved record contains the full configuration, seed, generator and schema versions, layout and renderer versions, timestamps and favourite state. Rendered SVG and thumbnails are reconstructed, not stored. Reopen uses the saved configuration and seed; duplicate uses a fresh seed. Rename deliberately updates that record’s title and printed heading. Favourite and delete are explicit library operations.
The storage namespaces include lessonlab.history.v1 and lessonlab.draft.v1. Preferences use lessonlab.preferences.v1 followed by the user ID. A storage namespace suffix of v1 is independent of a record’s configuration schema version; schema 2 records remain in the existing history namespace.
Browser storage belongs to a specific origin. Switching between localhost and 127.0.0.1, or between ports 5173 and 6173, opens a separate local library. Existing data on the earlier port is not automatically migrated. Clearing browser data removes local history and drafts. Downloaded exports remain independent files.
PDFKit and SVG to PDFKit produce vector PDFs with embedded print fonts. Resvg rasterises the same SVG using the bundled fonts with system font loading disabled; Sharp encodes PNG, JPEG and WebP. Image resolutions are 96, 150 or 300 DPI. Multiple image pages are packaged as a ZIP. Export controls include page selection, filename, answer sheets and applicable image quality.
PNG and JPEG carry print density metadata. WebP resolution is represented by pixel dimensions. These outputs come from physical vector geometry, so they do not depend on preview zoom or browser screenshots. Matching answer sheets preserve the question order and pagination.
Lato and Comic Neue regular and bold fonts are vendored under packages/shared/fonts with SIL Open Font License files. Browser copies live under apps/web/public/fonts. Font metrics and supported characters are generated from the same binaries; unsupported print characters are rejected.
npx tsx scripts/font-metrics.ts
Run this after changing font assets to sync browser copies and metrics. Review layout and renderer version changes whenever historical output can change. Keep packages/shared/fonts with the packaged app. LESSONLAB_ROOT can identify the repository root when launching outside the normal workspace commands.
The local endpoints are GET /api/health, GET /api/generators, POST /api/generate and POST /api/export. Requests are validated again on the server. Unknown API routes return a JSON 404 response.
The plain-number suffix update on 18 October 2026 passed 70 unit/integration tests, all 22 browser checks, and the production build. Checks cover closing parentheses through 100) and frozen schema-4 dotted output. Raster exports were visually inspected with colours enabled and disabled; compatibility checks freeze output for schemas 1–3. The earlier production smoke check preceded this update.
npm run typecheck
npm test
npm run test:e2e
npm run build
npm run test:production
The production smoke check requires a build and temporarily uses port 3003. Browser tests use installed Chrome. Alternatively, run npx playwright install chromium, then PLAYWRIGHT_CHANNEL=chromium npm run test:e2e.
Tests cover arithmetic, feasible pools, ticks, label fitting, pagination, presets, legacy output, storage, export dimensions and embedded fonts. Browser checks cover the teacher journey, downloads, history, preferences, responsive layouts, keyboard focus, print styles and legacy reopening. Automated accessibility scans complement manual assistive technology and classroom testing.
Only subtraction is implemented. The app has a light theme and two print font families. Guardrails allow 1 to 100 questions and nonnegative integer number lines up to 500. Invalid or illegible combinations are rejected. Browser export requires the local API.
Template persistence has a contract but no management interface. Firebase authentication, Firestore, sharing, AI interpretation, additional generators, offline export, background export jobs, tagged accessible PDFs and a general historical execution system remain deferred.
-
Review the untracked files and create an initial commit. Configure a remote and backup according to the owner’s preference.
-
Add a second generator to exercise the registry and typed editor boundary. Reuse shared rendering, exports and repositories.
-
Add template management. Templates preserve settings and normally create fresh content; history reproduces a saved generation.
-
Before public multiuser deployment, add authentication, authenticated server operations and per-user Firestore rules. Replace the local adapters behind their interfaces.
Future AI should select a versioned generator and return validated configuration. Mathematical content and diagram geometry must continue to come from deterministic code.
Keep development port 6173. Confirm presentation changes preserve questions, legacy snapshots retain their original output, preview and export agree, and saved revisions reopen exactly. Run the checks relevant to the changed boundaries and inspect representative exported pages.