Architecture reference
The repo is four layers with strictly one-way dependencies. Each layer only knows about the one before it; scaffolded apps never reach back into the template or the tools.
- packages/shell — @geenius-mvp/shell, the reusable engine: auth wiring, feature registry + router, chrome theming, UI primitives, entity views. Consumed via workspace link:, so shell changes reach every app instantly, no publish.
- _template/app, _template/supabase-app, _template/static-site — the sources the scaffolder stamps. They compile with ALL features enabled and carry @feature:/@site: marker blocks plus __TOKEN__ placeholders.
- tools/ — the CLI: scaffold.mjs, deploy.mjs, doctor.mjs, selftest.mjs, smoke.mjs, with shared modules in tools/lib/ (cli.mjs, markers.mjs, catalog.mjs, features.mjs, wizard.mjs, static-blog.mjs). Reads the template and the shell's chrome.ts catalog; writes only into apps/.
- apps/* — scaffolded output. Owned by you after generation; the scaffolder refuses to overwrite an existing apps/<slug>.
Shell entrypoints
The exports map in packages/shell/package.json defines exactly six subpaths:
- . — src/shell/index.ts: the full Convex entrypoint (createLabApp, auth, registry, all UI).
- ./styles.css — src/styles/shell.css: the complete stylesheet, all 100 designs included.
- ./chrome — src/shell/ui/chrome.ts: design/accent/font/radius/density/animation catalogs + the design-state API. The scaffolder parses this file for its catalogs.
- ./design-swatches — src/shell/ui/design-swatches.ts: per-design preview swatch data.
- ./ui — src/shell/ui-only.ts: UI primitives and entity views with no backend imports (static and marketing surfaces).
- ./supabase — src/shell/supabase-entry.ts: the Supabase-variant app factory.
Marker system internals
Two marker axes exist, both with the same start/end grammar: @feature:<id> gates optional features, @site:<id> gates site surfaces (webapp, website, blog, docs). Markers are recognized inside four comment styles: //, {/* */}, <!-- -->, and SQL -- comments — the regex in tools/lib/markers.mjs matches the marker text on the line regardless of comment wrapper.
// tools/lib/markers.mjs
/@(?:feature|site):([a-z-]+) -- start/
/@(?:feature|site):([a-z-]+) -- end/
stripMarkers(text, excluded) walks a file line by line with a stack: a start line pushes its id and is dropped (marker lines never survive, even for kept features); an end line pops and must match, otherwise it throws unbalanced feature markers; content lines are dropped while any excluded id is on the stack. An unclosed marker at EOF also throws.
Stripping only removes marker blocks. Files owned exclusively by one feature are deleted outright: tools/lib/features.mjs maps each feature id to per-db file lists (featureFiles), and scaffold.mjs fs.rmSync's those paths for excluded features. Site types work the same way via SITE_TYPES[type].del. Only files with extensions in TEXT_EXT (.ts .tsx .json .html .css .md .js .txt .sql) get marker stripping and token replacement.
The leak guard
After stamping, scaffold.mjs re-reads every text file in the new app and tests it against LEAK_RE:
export const LEAK_RE = /__APP_[A-Z_]*__|__PORT__|__DESIGN__|__ACCENT__|__MODE__|__LAYOUT__|__SINGLE__|@feature:|@site:/;
If any token or marker survived, the entire apps/<slug> directory is deleted (never leave a broken app behind) and the command dies listing the leaking files. Adding a new feature therefore only requires marker blocks in the template plus one FEATURES entry — the leak guard catches any typo'd marker.