Plan: Pixel Lab owns the art: a .pixellab document, and a build that publishes (from intent.md 2026-10-19)#
Files that change#
- Engine home (new):
cli/lib/engine/{engine,iso,motion,serialize,edit,starter,svg}.mjs(copied from the site'ssrc/lib/pixel-art/at the commit named in the copy's message),cli/lib/engine/compile.mjs(fromscripts/optimize-pixel-art.mjs),cli/lib/engine/preview.mjs(fromscripts/pixel-art-preview.mjs),cli/lib/engine/starter-palette.mjs. - Document:
cli/lib/document.mjs(new; replacescli/lib/site.mjs, deleted),cli/lib/context.mjs,cli/lib/session.mjs,cli/lib/render.mjs,cli/lib/cli.mjs,cli/lib/serve.mjs,cli/lib/argv.mjs,cli/lib/help.mjs,cli/lib/server-stdio.mjs,cli/lib/server-mcp.mjs,cli/lib/commands/document.mjs(new; replacescli/lib/commands/site.mjs, deleted),cli/lib/commands/art.mjs,cli/lib/commands/proposal.mjs,cli/lib/registry.mjs. - Tests:
cli/test/fixtures/sample.pixellab/(new),cli/test/engine.test.mjs,cli/test/compile.test.mjs,cli/test/preview.test.mjs,cli/test/art-commands.test.mjs,cli/test/document.test.mjs(new),cli/test/helpers.mjs,cli/test/site.test.mjs(→ folded intodocument.test.mjs), every suite that namesmakeSiteor--site. - Package:
package.json(name, exports, files),docs/cli.md(generated). - App:
app/Sources/PixelLab/Server/Locator.swift,Server/ServerConnection.swift,Server/Protocol.swift,Model/AppModel.swift,Model/Settings.swift,Views/SetupView.swift,Views/PublishSheet.swift(renamed fromBuildArtSheet.swift),Views/MainWindow.swift,PixelLabApp.swift,Support/ScriptRunner.swift,app/Tests/PixelLabTests/*for the new model code,scripts/build-app.sh(document type in Info.plist). - Scripts:
scripts/acceptance.sh,scripts/acceptance/scene-object.txt,scripts/acceptance/document.txt(new),scripts/acceptance/mcp-agent.mjs,scripts/snapshot.sh. - Docs:
README.md,CLAUDE.md,docs/architecture.md,docs/testing.md,cli/README.md,app/README.md,scripts/README.md,docs/intents/2026-10-mac-app/plan.md(a pointer at the top only),docs/screenshots/document-*.png(new). - This change:
docs/intents/2026-10-pixellab-document/{intent,spec,plan}.md(new).
Order of work#
One slice, on feat/pixellab-document, straight to a PR into main: one runner (the main session) does it in one sitting and the owner asked for one PR per repo to review. One commit per step:
- The change record: intent, spec and plan.
- The engine's home (R6, R9, R13): copy the seven modules, port the compiler into
compile.mjs(exports:parse,optimizeSvg,verifyLossless,isRectGroup,rectPixels,attr,BUDGET,compileScene(sources, name, budgets),compileAll,sceneSizes,headSources,countItems) and the previews intopreview.mjs(previewColors(manifest.preview)from the manifest or defaults); add the sample fixture document andstarter-palette.mjs; port the engine, optimizer and preview tests with their names kept. (As built: the preview tests stay insideengine.test.mjsas they were in the site; the site-art sections of the engine test, library, outfit and palette brand checks, go to the site's own tests.--docclashed with thedocparam of thedoc.*commands, so the global flag is--document.) - The document (R1–R5):
document.mjs(resolution--document→PIXELLAB_DOC→ config →--site/PIXELLAB_SITE→ default site; manifest parsing and errors; legacysource/folders;openDocument()),ctx.documentwithctx.sitekept as an alias,render.mjstaking the theme and preview colors from the document,document info | new | copy,palette getmoved,session.infowithdocument;helpers.mjs'smakeDocument()(andmakeSite()kept, laying the site's art out as a legacy checkout, so the art suites change little);document.test.mjs; every suite repointed. - The art commands (R7, R8):
art buildin-process with--dir,art preview(one PNG at--scale, written by the global--out),art sizes;LEFT_OUTfor MCP;art-commands.test.mjsfor preview and sizes, the publish tests inedit-commands.test.mjs;npm run docs:cli. (Steps 2–4 landed as one commit: the document module, the engine's home and the art commands depend on each other, and the suite is green only with all three.) - The package (R15):
package.jsonnamepixellab,exports,files; a test that every export resolves. - The app (R10–R12):
Locator.documentProblem,documentPathand--document/--sitelaunch, the setup screen and Settings rows, File menu (New Document…, Open Document… ⌘O, Save As… ⇧⌘S, Publish ⌘B),PublishSheet, the app delegate'sapplication(_:open:), the window title, the document type inbuild-app.sh; ScriptRunner'spublish,saveas,expect outputs|document|publishdir; Swift tests for the model's path resolution and publish state. - Acceptance and snapshots (R12):
acceptance.shpublishes into the temp site and checks the six SVGs;document.txtsaves as and reopens; snapshots of the setup screen, Settings and the Publish sheet in dark and light. - Docs (R14): README,
CLAUDE.md(the rules flip),docs/architecture.md,docs/testing.md, the three code READMEs, the pointer in the architecture record;npm run docs:siteclean. - Verification run (Proof) and the PR.
Workflow and reviews#
The main session runs every step in order on feat/pixellab-document in its own worktree, commits each, pushes with git push -u origin feat/pixellab-document and opens the PR into main citing this plan. Owner review: the PR, where the owner reads the record, the diff and the snapshots, and tries the installed build if they wish; the owner merges. Independent review: none planned; the ported tests (byte-identity of the site's art, the engine's own suite), the Swift tests, the acceptance pass and the snapshots are the verification. The owner can ask for a verify-change run on the PR.
Risks#
- The art changes by a byte. The compiler is ported, not rewritten, and
compile.test.mjscompares every committed SVG in the site checkout with what the ported compiler produces from its sources (R7: the site's art compiles byte-identical). If it fails, fix the port. - The two repos drift during the transition. The lab's
--sitekeeps resolving a legacysource/folder, so the site's agents keep working until its PR lands; the site pins the lab by commit. Tested on both layouts (the sample fixture, and the site checkout as it is). - The app's document type and open-from-Finder can't be checked by the script hook. The plist is checked by
scripts/build-app.sh(plutil lint) and the delegate path is unit-tested at the model level; opening from Finder is tried by hand once and noted in the PR. - Riskiest step: 3, where every command's ctx and every test's helper change at once. It lands as one commit with
npm testgreen on both layouts before step 4 starts. CLAUDE.md's rules are read by every future session; the flip is written plainly ("the engine lives here") so no session re-imports the engine from the site.
Proof#
npm testshows# fail 0, with the ported engine, compiler, preview and CLI suites and the new document tests, run twice: with the site checkout present and withPIXELLAB_TEST_SITEpointing nowhere (the core suites run on the fixture, the art suites skip).npm run docs:cliandnpm run docs:siteleavegit statusclean and build with no broken links.swift build --package-path appwith no errors;scripts/test-app.shpasses.scripts/build-app.shbuilds;plutil -lintpasses on its Info.plist;scripts/acceptance.shexits 0, with the publish step writing six byte-identical SVGs into the temp site.- Script-hook runs with
expect outputs 6,expect document <name>aftersaveas, and snapshots of the setup screen, Settings and the Publish sheet in dark and light, looked at before reporting.