Tutorial: System Map — Overview

System Map — Overview

A deeper walk-through of how the machine fits together. For the quick orientation see ../context/architecture.md; for module-wiring rules see ../reference/esm-patterns.md.

This is the macro picture. Per-layer deep-dives live alongside it (see README.md): core-engine.md and gui.md describe the current app; cli.md and server.md document the removed pre-revival CLI + classic server for historical reference.

The pre-revival 2022–2023 system (a yargs CLI + an Express/Pug server) was removed from the tree. A sibling CLI is planned but not built yet; today the only front end is the targets/web/ SPA.

The surfaces

There is one isomorphic engine (engine/core/) driven by one front end — the React/Vite SPA (targets/web/), which builds into two editions:

  1. The SPA (browser) — composes a DPL prompt, runs the engine in the browser (via the Vite loader), and calls provider adapters directly with the user's BYOK key.
  2. The local /api backend (targets/web/backend/) — present only in the local/desktop edition. Serves the built SPA plus an /api/* surface (image save + feed, the Manager's on-disk file ops, ImageMagick convert). The online edition ships no server.
  3. The engine under Node — the same engine/core/ engine runs headless under Node for the test suite and the /api runtime, which also proves the browser and Node produce the same output.

One engine, two loaders

engine/core/ has no fs / framework dependency; content is supplied by an injected loader:

  • NodenodeLoader.js: fs + createRequire(import.meta.url) for synchronous .js generator loads (Node 24 can require() an ES module), resolving the content root module-relative (new URL("../../", import.meta.url) = the repo root, cwd-independent).
  • BrowserbrowserLoader.js: a Vite import.meta.glob("../../engine/data/blocks/**/*.js") build-time macro; the lists ship code-split (browserCatalogData.js).

Both call each generator's default(...) and read its full / suggestion_exclude flags. See ../reference/esm-patterns.md.

The prompt pipeline

The engine runs settings.promptModules in order over each prompt string — the stages are in engine/core/stages/:

prompt → block → prompt-salt → list → emphasis → cleanup
  • block ({#name}) — call the generator from engine/data/blocks/<category>/; nesting re-expands up to ~10 passes; honors the per-token intensity / focus dials; NSFW-gated by name token. (The one flat catalog replaced the old v1/v2 generations, and the legacy <expansion> stage was removed.)
  • prompt-salt ({salt}) — inject a random / incrementing number (a subseed alternative).
  • list ({name}) — pull a random line from engine/data/lists/<name>, with emphasis / editing / alternating randomization (engine/helpers/randomEmphasis|Editing|Alternating.js) and once-only depletion.
  • emphasis — render typed () / [] emphasis into the active provider dialect (SD/MJ weights, NAI braces, or plain words).
  • cleanup — collapse stray spaces / commas.

The DPL parser + renderer (engine/core/dpl/) compile and roll each template; a seeded Rng (engine/core/rng.js) makes runs reproducible.

Dynamic-prompt classification

engine/promptFilesAndSuggestions.js scans engine/data/blocks/ and splits generators into full vs partial by reading each module's full export, excluding suggestion_exclude ones. These drive the random promptSuggestion()s and the SPA's block pickers. Tag metadata comes from engine/blockManifest.js; NSFW gating from engine/gatedLists.js (isGatedBlock).

Image generation

Images are generated by the provider adapters in targets/shared/<id>/ — roughly 40 of them behind one interface, over a shared transport in providers/_shared/. Calls go straight from the browser to the chosen provider with the user's key (no server relay), so a provider that can't be called from a browser is disabled in the online build. Stable Diffusion is still supported — it's where the project started — but it is now one option among many.

In the local edition, POST /api/image (targets/web/backend/apiHandler.js) writes each PNG plus a .json metadata sidecar (the prompt layers, the deterministic engine roll, the AI rewrite, the provider, and a key-stripped settings snapshot) into output/; GET /api/feed reads them back for the Gallery. The in-app Manager edits the real data/lists + data/blocks files through /api/manage/* (targets/web/backend/manageFs.js) and hot-applies them via the runtime loader (targets/web/frontend/lib/runtimeLoader.js).

Settings as the spine

Everything reads from the merged settings — engine/settings.js defaults ⊕ the user's overrides (user-settings.json on disk / localStorage in the browser). It carries the pipeline order, the content paths, the active provider + dialect, gating (includeAdult), the intensity default, and the per-provider BYOK keys (kept only on the device and stripped from any saved image sidecar). The online build boots the default-settings shell and settles stored settings in via a guarded two-pass store, so the SSR prerender and the client-first render match.