Current state only. For the chronological history of what changed each session and why, see
sessions/. For the commit-by-commit changelog see version.md.
Health (2026-07-12, 2.60.0)
Green, and — new as of 2.58.0 — actually gated in CI, which it wasn't before: the mobile app, its parity/gating/no-caps checks and the metro engine-parity check had never run in CI at all.
| Gate | State |
|---|---|
npm test (docs · registry · lint · format · smoke · mobile parity · unit · web · mobile) |
green |
| Node unit / integration / regression | 369 |
| Web (jsdom) | 430 |
| Mobile (jest-expo) + coverage gate | 122 |
| Mobile a11y (axe, 5 sizes × 2 colour schemes) | 60 — zero serious/critical WCAG 2 A/AA |
| Mobile on-device (Detox — release APK on a real Android runtime) | release-gated (PR into main / dispatch) — engine 13.0 s per 1000 prompts, rows committed in 34 ms |
| Mobile visual baselines | 50 (win32) + Linux, regenerated by the visual-baselines.yml workflow |
| Web build / online build + SSR prerender | green |
check:committed · check:tidy |
clean |
The local gate is now the CI gate. It wasn't: npm test ran lint but not format:check (CI runs
both), so dev sat red in CI on files a "green" session had written. A local gate that is a subset
of the CI gate is a lie about what green means — format:check now runs inside npm test.
Open / known:
- Desktop (Tauri) path wiring was rewritten for the new layout but never verified with a real Rust build — do that before shipping desktop.
- Live image/text generation against real provider keys / a running SD WebUI is still manual (not in CI).
The 1000-prompt promise HOLDS on a real device — measured, not asserted.
From the on-device gate (release APK, Android emulator, KVM):
| Roll | engine | render | memory | jank |
|---|---|---|---|---|
| 20 | 1497 ms | 383 ms | 112 MB | 95.7% |
| 200 | 5447 ms | 196 ms | 122 MB | 92.7% |
| 1000 | 23366 ms | 253 ms | 110 MB | 89.8% |
1000 prompts render in 253 ms with flat memory — the list virtualizes exactly as promised, and the cost per prompt falls as N grows (94 → 28 → 24 ms). The jank is the emulator's software GPU: identical in all three rolls, which is why the gate judges each roll against the same device's own baseline instead of an absolute frame budget. (Engine time is Hermes on an emulator; Node does the same 1000 in 158 ms.)
The "defect" this gate first reported was my own test. For a few hours this file said the app missed its headline promise. It didn't — and the app's own instrumentation said so all along:
[rap-perf] roll 1000 prompts: 13044ms (engine only)
[rap-perf] committed 1220 result rows ← 34 ms later
Results accumulate across rolls (each batch is prepended, by design), so after a 20-prompt baseline
the label reads "220 generated", not "200 generated" — and the test sat through a ten-minute timeout
waiting for a string that could never appear. The suite now scrolls to the top, clears the list, and
derives the expected label from observed state.
Two things worth keeping from that hole:
- The test's verdict was louder than the app's evidence, and I believed the test. The
[rap-perf]lines were in the log from the first run that had them, saying plainly that the rows were committed. Read the instrument you built before you distrust the thing it measures. - It also retires the 2026-07-11 conclusion that FlashList's web renderer was the problem. The
react-native-web export renders 1000 prompts in 165 ms with 16 rows mounted
(
scripts/probe-mobile-list.mjs). Nothing was wrong there either.
Delivered in 2.60.0 — the harness that found it, and the last de-dup:
- On-device gate.
targets/mobile/e2e/+.detoxrc.jsdrive the release APK with Detox (CIandroid-devicejob; the local Windows AVD can't run it — see below). It measures a three-point curve on one device in one session (20 → 200 → 1000) and asserts the shape — cost per prompt, memory and jank must not climb with N — using the platform's own accounting (dumpsys gfxinfo/meminfo), never a stopwatch. Seeplans/testing.md. → promoted totargets/mobile/lib/dplInserts.js(262 lines) is still a hand-port — the last oneengine/dplInsertCatalog.js(the grammar is shared; each target keeps its own label layer).checkDplInsertsdeleted. Seeplans/de-duplication.md.→ promoted totargets/mobile/lib/dplInserts.js(262 lines) is still a hand-port — the last oneengine/dplInsertCatalog.js(the grammar is shared; each target keeps its own label layer).checkDplInsertsdeleted. Seeplans/de-duplication.md.
Repository structure — engine/ + targets/ (restructured 2026-07-06, 2.47.0; the "dynamic prompt"
concept was renamed to "block" everywhere in 2.48.0, and the Tauri target targets/desktop was renamed
targets/web-shell): the project lives at the
repo root as an engine + build targets. The isomorphic prompt engine is under engine/
(engine/core/ = the DPL engine + pipeline stages + both loaders; plus engine/helpers/ and the
manifest/settings/content-safety modules), and the engine owns its content under engine/data/
(lists, presets, sources, and the {#name} block generators). Build targets live under
targets/: targets/web/ is the React/Vite web target (ONE npm package, split into frontend/ +
backend/), and targets/web-shell/ is the Tauri shell (its own package; wraps the built local
web target). targets/shared/ is the cross-target app layer — the ~40 provider adapters + the
injectable transport + the accent themes, imported by the web, the CLI and the mobile app (2.52.0–
2.54.0; it used to live inside the web target as targets/web/shared/). Two further targets have since
shipped: targets/cli/ (the prompt command-line tool, 2.50.0) and targets/comfyui/ (the ComfyUI
custom-node target, 2.51.0), plus targets/mobile/ (Expo/React Native, Android).
The universal override overlay stays at the repo-root user/ (user/lists, user/blocks). Build/meta
tooling is in scripts/, the Node engine test suite in tests/ (the web target has its own under
targets/web/tests/); all commands run from the repo root. The dev server (npm run web) is
development-only — end users run the built local desktop target or the hosted web build. The pre-revival
2022–2023 CommonJS system (the old CLI + classic Express/Pug server) was removed from the tree — it
lives in git history and as a read-only reference clone under assets/references/. (Historical entries
below predate this restructure and may still say src/…, gui/…, or engine-v3/…; those paths are now
engine/… / targets/web/… at the repo root.) Desktop target caveat: its path wiring was rewritten
to the new layout but not yet verified with a real Tauri/Rust build — do that before shipping desktop.
CLI target — the prompt command-line tool (2.50.0 — branch feature/cli-target): a new build
target under targets/cli/ (own npm package, @random-ai-prompt/cli, bin prompt). Traditional args +
flags, --help, colored output, and bash/zsh/fish/PowerShell completion — full parity with the engine
and the GUI by default: it reuses the shared engine, the targets/shared/ provider adapters, and
the user/settings/ store (shared BYOK keys), exposing every engine/settings.js field as a flag.
Image/upscale/rewrite run the real backend (apiHandler.js) in-process on an ephemeral port with a
fetch shim, so provider adapters run unmodified and images save to the shared output/ folder with the
gallery's sidecar; an ESM hook (jsonLoader.mjs) injects type: json for the providers' bare JSON
imports. Commands: generate (default), list, config, keys, rewrite, upscale, completion.
Root postinstall installs it; npm run cli runs it; the root ESLint gate lints it and
tests/cli/cli.test.js (14 tests) is in the root Vitest gate. Verified: --help, list providers
(40), text generation, midjourney copy formatting, the missing-key guard, and the full headless gate
(lint / smoke / 293 Vitest tests / prettier). Not yet verified: live image generation against a
running SD server or paid provider APIs (manual, not in CI). See systems/cli.md.
User content overlay — a repo-root user/ folder beside data/ (2.46.0 — branch
feature/user-overlay, on that branch pending review): a first-class user overlay so people
add/tweak prompt content without touching the app's built-in files. A repo-root user/
(user/lists, user/blocks, user/settings) sits beside data/ and the app watches both, with
user-wins precedence on a name clash (like a settings override). Both engine loaders scan two
roots per pool (engine/core/nodeLoader.js on disk; the browser keeps names at first paint from lazy
globs and loads user content from a separate code-split engine/core/browserUserCatalog.js overlaid
last). The overlay is local/desktop only — gated off the online build (VITE_ONLINE), so the
hosted bundle carries no user content. The Manage tab groups "your content" (badged yours) on
top via new user-lists/user-blocks roots; buildManageSnapshot merges them into the runtime pool
so live generation honors the overlay, while edits write into user/. User content has no upstream,
so ghost/"restore default" are suppressed and restoreFromRepo refuses user roots. Settings moved
from targets/web/user-settings/ to the unified user/settings/ (boot migration folds the old location). The
desktop shell seeds user/ once and preserves it across upgrades. npm test green; visual
baselines need a refresh (the Manage tree gained the user sections) before the main release. See
version/2026-07.md.
Gallery composer + live placeholders, multi-select, a11y + SEO (2.45.0 — branch
feature/gallery-composer-a11y-seo, on dev pending review): the composer prompt box was extracted
into a reusable targets/web/frontend/components/PromptComposer.jsx (Home markup unchanged); the Gallery now
carries a narrow copy of it in a .g-composer slot at the top, and generating from it streams live
placeholder cells into the grid that resolve into the finished images as each batch lands (isolated
orchestrator targets/web/frontend/lib/gallery/generateIntoGallery.js; the perf-critical useImageBatches is
untouched). The gallery gained multi-select + mass delete (checkboxes, select-all, one-confirm disk
delete via App.deleteManyItems). An accessibility pass added a skip link, a landmark <h1>,
role="tabpanel" view panes, role="alert"/aria-live regions, a prefers-reduced-motion guard, and
.sr-only/.skip-link utilities (axe A/AA clean). An SEO pass added sitemap.xml, a FAQPage +
enriched WebApplication JSON-LD, and a keywords meta. New tests:
generateIntoGallery + GalleryMultiSelect. See version/2026-07.md.
Auto-updating — check-and-notify shipped; desktop auto-install implemented (2.44.0): local/desktop
editions check for a newer GitHub release on launch and show a dismissible corner card,
edition-aware (download the new installer/portable/release, or a copyable git pull for a checkout)
— the online build is exempt (always the latest deploy). Core in targets/web/frontend/lib/updateCheck.js (+
useUpdateCheck.js + components/UpdateBanner.jsx); backend GET /api/update fetches the latest
release server-side (1 h cache) + detects the edition (Tauri stamps RAP_EDITION; else .git ⇒ git;
else source); dismissal + throttle persist through the new update storage namespace. Phase 2 (full
in-app auto-installer) is LIVE behind the updater Cargo feature: signing public key committed, a Rust
check-on-launch → native-prompt → install/relaunch trigger (compile-verified; default build pulls in
neither the updater nor dialog crate), and a CI updater-manifest job that assembles latest.json. The
CI signing secret is set and the first signed release (v2.44.0) shipped — all installers + .sig +
a valid latest.json attached — so installed builds prompt-and-self-update from the next release on.
Privacy page updated (desktop update-check disclosure). See
reference/desktop-updater.md +
plans/updates-upgrades.md.
Pre-built distribution + desktop edition (2.43.0 — branch feature/prebuilt-distribution): every
edition now ships pre-built so nobody has to build from source, and the hosted site is reframed as
just one deployment of the online edition. New desktop edition (targets/web-shell/): a thin Tauri
(Rust) shell that runs the unmodified local SPA + Node /api backend as a bundled sidecar (bundles
the platform node, stages to a writable working copy that preserves user data across upgrades, launches
serve.js on a free port, points the WebView at it). release.yml now attaches a self-hostable
…-online.zip and a per-OS matrix builds Windows .msi/NSIS/portable-.zip, macOS .dmg, and Linux
.AppImage/.deb (signed, on their own runners). README leads with download-or-build (21 badges
restored); the in-app links menu gained "Get the desktop app" / "Run it yourself". Updates/auto-upgrade
are deferred to a pre-3.0 design (plans/updates-upgrades.md). Verified
locally on Windows (installers build; portable runs end-to-end with the bundled node sidecar serving);
cross-OS installers are CI-only. See systems/desktop.md.
Large-scale performance (2.42.0 — on dev): the app is built to stay seamless at its officially
supported maximum simultaneous load — a 100k-image gallery + 1000 prompts / ~10k images + a
100k-line Manage file, all at once. The gallery is virtualized (windowed uniform grid over the pure
targets/web/frontend/lib/virtual/windowRange.js — bounded DOM at any count; replaced the old wide/tall masonry with
uniform cells so row-windowing is exact); the 1000-prompt results list uses content-visibility +
a memoized PromptResult (all rows present, offscreen ones skip layout/paint/decode); and auto-image
generation is placeholder-first + chunked — useImageBatches shows every prompt's busy placeholder
instantly and runs the real generate behind a per-provider concurrency limiter (rewrites through a
separate text-provider limiter), so a huge run never stampedes an API. The concurrency lives in a new
shared-settings system (targets/shared/_shared/settings/, auto-discovered + injected into every
provider's schema): a per-provider "Batch chunk size" with metadata defaults (local 6 / hosted 3).
Guarded by a Playwright perf suite (tests/perf/, real release server via playwright.perf.config.js
— npm run test:perf:scenarios, in test:all + a CI job) and a profiler (npm run profile). See
version/2026-07.md.
Version: 2.51.1 (single source of truth: repo-root VERSION; kept in sync with package.json
and the desktop targets/web-shell/tauri.conf.json;
see reference/versioning.md). Everything through 2.51.1 is merged and
released to main. As of the 2026-07-09 maintenance sweep the repo carries only two branches —
main and dev — on both the local checkout and GitHub, with no open pull requests; all prior
feature/* and dependabot branches have been merged and deleted.
main is branch-protected (2026-07-02): releases now run through a pull request (gh pr merge --merge), not a local git push origin main. PR-required with 0 approvals (solo self-merge), strict
status checks, enforce-admins, force-push/deletion blocked, linear history off. The OpenSSF Scorecard
was hardened from 4.2 the same day. See reference/git-workflow.md and
reference/deployment.md.
SPA internationalization (2.15.0 — branch feature/i18n-react-intl): the whole React SPA is
internationalized with react-intl + the full FormatJS pipeline. Every user-facing string across
all ~28 components (text, title/placeholder/aria-label, confirm/prompt, ICU plurals/numbers) is a
defineMessages / intl.formatMessage / <FormattedMessage> call — ~407 messages, English rendering
byte-identically (visual baselines untouched). New targets/web/frontend/i18n/ (config.js locale registry +
resolveLocale, loadMessages.js import.meta.glob catalog loader, I18nProvider.jsx); App.jsx split
into a thin root + AppShell; a Display language selector in Settings persisted to settings.locale
("auto" follows the browser). Tooling: babel-plugin-formatjs (auto IDs) in the Vite react plugin;
@formatjs/cli scripts (i18n:extract → src/i18n/messages/en.json, i18n:pseudo → an en-XA
pseudo-locale); targets/web/eslint.config.js + npm run lint:i18n (eslint-plugin-formatjs). Only English is
shipped as a real locale (the app's DPL/prompt jargon makes machine translation low-quality; adding a real
language is now a one-file job). Coverage is complete (~480 messages) — including the DPL-technical lib
modules: validateDpl.js (editor lint diagnostics) takes an optional intl with a createIntl English
fallback so its message-asserting tests stay green, and dplInserts.js (the DPL syntax catalog) is a
getDplInserts(intl) builder. See version/2026-06.md.
Manage tab (2.12.0 — on dev): a 4th SPA tab, the in-app content manager (local mode only — gated
on a file-backend capability probe, locked online). It edits the real data/lists + data/blocks
files on disk and hot-applies them live via a runtime (disk-snapshot) loader (runtimeLoader.js) the
engine reads through — no reload, except an edited .js generator module body (which can't run from
fetched text without eval, so it reloads). Left pane: the real nested folder tree (categories vs subfolders
color-coded, force-prefix/group folders badged, _-markers abstracted, NSFW-gated, search). Editors:
blocks (DPL + JS-sidecar tabs/boilerplate), folders (rename, sidecar priority/description/forceList, marker
toggles), and lists (virtualized entry mode + raw CodeMirror — seamless at 27k lines). Plus add/delete,
drag-to-move, restore-default (from main), ghost pills for files deleted locally but still upstream
(diffed against a published data/manifest.json, disk-cached a day), and external-edit auto-refresh (SSE
fs.watch). Backend: targets/web/backend/manageFs.js + /api/manage/* (Vite dev middleware today). Plan +
details: plans/manage-tab.md. Contract-tested in tests/integration/manageFs.test.js.
DPL intensity dial + block content refactor (2.10.0 — shipped): a {#name} reference can carry
an intensity percent ({#great-bridge 25%}, 1–100; 0→1; unspecified → 50%, top-level and nested)
that flows into the generator. Lines take intensity conditions in the weight slot ([<10%] - grass; ops
< <= > >= = !=; stackable [100|<10%] or [100 <10%]); probability gates and repeat/one of/N of
counts auto-scale by intensity; and text can interpolate it via {intensity}
(tiny/small/normal/large/huge/massive), {intensity%}, {intensity-num}, each accepting a relative ±NN%
modifier (also on nested {#name ±NN%} refs). Engine in engine/core/dpl/dpl.js +
engine/core/stages/block.js; design: reference/intensity-design.md.
The content refactor is complete across all five categories (scene · fragment · subject · style ·
prompt): de-scattered (knight no longer pulls {#landscape}/[[castle]]; beach↛city; etc.), render-farm
filler stripped, typos fixed (interrior, accesories, mesmorizing, sceptor), anime-irl→anime-realism,
list-backed + intensity-aware. The engine auto-append now re-resolves nested tokens (the {#rays}-leak root
cause). Open: confirm the 50% default at the top level (one constant, DEFAULT_INTENSITY, to retune).
Provider header redesign (2.9.0): the top bar is now a single Providers dropdown
(targets/web/frontend/components/ProvidersMenu.jsx) + a provider-settings gear + the NSFW switch. The dropdown holds
two rich pickers (ProviderPicker.jsx): Image (grouped Local — incl. Plain text — / Online) and Text
(Off + the rewrite AIs OpenAI / Gemini / Grok), each with its BYOK key field (ApiKeyField.jsx) on the
right in an aligned two-column grid; the key is keyed by provider id, so the same provider chosen for both rows
shares one key (shown once). The provider's own knobs (ProviderBox.jsx, now bare — no card/collapse)
moved into the gear popover (ProviderGear.jsx), out of the prompt area; ProviderSelect.jsx was removed and
the rewrite select/key left Settings.jsx. The negative prompt left provider settings: the composer's
editor flips between Prompt and Negative via a switch on the insert bar (only when the provider
supports negatives), storing the per-provider negative the engine already reads. Next: a per-provider
preset manager + migrating the old v1–v2 flat presets (deferred). Windows visual baselines refreshed; the
Linux set needs the visual-baselines.yml workflow before the main release.
Keyword tooling + DPL insert toolbar (2.8.0): the single view's keyword cloud is now backed by a real
parser (targets/web/frontend/lib/keywords.js — strips SD/NovelAI weighting syntax, keeps accents for display but folds
them for dedupe/search) with a "Rebuild with AI" button that keyword-translates the prompt, alphabetizes,
and saves over the image's sidecar (POST /api/image/meta); the composer gained an autoKeyword toggle
beside the auto-fix wand (independent + chainable), backed by a new KEYWORD_SYSTEM rewrite mode; and the
single view's details block is a real <table>. Also lands the DPL insert toolbar above the prompt box
(DplInsertBar.jsx + dplInserts.js, snippet insertion via DplEditor.insertSnippet). The stale E2E
selectors that still targeted <textarea> (red since the 2.7.26 CodeMirror switch) were fixed to drive
.prompt-input .cm-content, and the Linux visual baselines were refreshed.
DPL editors (2.7.26): the prompt, negative, and wrapper Start/End boxes are CodeMirror 6 editors
(targets/web/frontend/components/DplEditor.jsx + targets/web/frontend/lib/dpl/dplLanguage.js) with DPL syntax highlighting
(theme-aware --dpl-* colors in styles.css) and a brace-aware {…}/{#…} token autocomplete. Part of a
four-branch GUI/DPL UX pass on dev; the Playwright visual baselines still need a refresh for the
prompt-box change.
Dynamic-prompt sidecars (2.7.27): category .json sidecars carry a priority (orders the
category/folder pills inside the Blocks tab — lower = higher, default 1000; the curated order is
Any · Prompt · Scene · Subject · Style · Fragment · User · Special), and generator sidecars can carry
nsfw: true to be hard-hidden when the NSFW switch is off (gone from the picker and from the engine,
not just emptied).
Result/gallery polish (2.7.28): the prompt box's live-preview moved to the box's upper-right corner as an icon (off the bottom action bar); generated-prompt rows dropped the copy button for click-to-copy text (full text on hover; the DPL line's hover tooltip also shows an example that re-rolls every second); and gallery thumbnails now carry the same hover actions as the generate thumbnails (open in default app, reveal in explorer, delete).
Online build is stripped (2.7.29): when built with VITE_ONLINE=true, the SPA is Generate-only —
the header tabbar (Gallery/Single) is gone, the NSFW toggle is removed and adult content forced off, and
no image feed/storage is touched (generated images stay in-memory; nothing is saved to the browser). The
local build is unchanged.
Photo gallery (2.7.25): the old v1-2 image feed is back as a first-class v3 view. The top-bar now
carries a Generate · Gallery · Single switch (targets/web/frontend/App.jsx) over three top-level views that all stay
mounted for the session — each keeps its state + scroll position when you switch tabs (shared feed /
search / current-image state lives in App). The gallery (targets/web/frontend/components/Gallery.jsx) browses
everything saved to output/; the single view (targets/web/frontend/components/SingleView.jsx) is the full
per-image page. Generated images and gallery thumbnails open into the single view (Back returns where you
came from); the Single tab shows the last image, or a random one the first time. Every
generated image now gets a .json metadata sidecar next to it (prompt sent, the deterministic engine
roll, the AI translation, the source DPL, negative, provider, and a settings snapshot with API keys
stripped), written by POST /api/image and read back via a new GET /api/feed (targets/web/vite-plugin-api.js).
The gallery is a masonry grid with keyword search; clicking opens a dedicated single-image page (not a
modal) with the prompt and negative each in their DPL / engine-roll / AI-translation / sent-final layers, a
curated details table over the full settings snapshot + raw JSON, a clickable keyword cloud, prev/next nav,
and actions (open / reveal / download PNG / Convert & download via ImageMagick / delete). The sidecar is
nested (prompt:{dpl,roll,ai,final}, negative:{…}), and the negative prompt is AI-translated too when
auto-fix is on. The dev server detects ImageMagick (/api/magick) and converts on demand
(/api/image/convert); the convert menu hides when magick isn't installed. Local-only by nature (the feed +
conversion need the dev server's filesystem); a static/online build shows an empty gallery with a note. See
version/2026-06.md.
Layout reorg (2.7.1): completes the v3-only move. Blocks are now flat under
engine/data/blocks/<category>/ — the v3/ wrapper and the leftover {#v1/}/{#v2/}/{#any-ver}
version routing are gone (engine + both loaders + the SPA browser). The loose raw build inputs moved to
engine/data/sources/ (artists.csv, danbooru.csv, nai-tag-expirement.json), and the SPA folder was renamed
web-app/ → targets/web/ (the name anticipates a planned CLI; the core engine is already headless). A fuller
notes sweep of the remaining v1/v2/web-app references in the deeper reference/ docs is still pending.
Content rating (2.6.1): the SPA now defaults to SFW (settings.includeAdult: false) and carries a
right-aligned NSFW toggle in the top-bar (targets/web/frontend/components/NsfwToggle.jsx) — a stopgap until the
options screen lands. Turning it ON requires a confirmation dialog; turning it OFF is immediate; the choice
is remembered in the browser (it's part of settings → localStorage). The engine already gated on
includeAdult (core/listStore.js, core/stages/*, gatedLists.js); this just exposes the switch. Still
pending: the SFW/adult word-list split + re-adding the Style control (see
plans/removed-pending-readd.md).
fairyfox mesh: this repo is a node in the fairyfox system. Project-side onboarding is done — the
CLAUDE.md mesh-awareness block (reference/cross-project-sync.md),
the notes/version/branch model, and a fairyfox-themed docs site (reference/documentation.md)
are in place. The themed docs go live on the next main release + Pages deploy. Branch model (adopted
2026-06-25): the project now follows the system's full git-flow standard, and master was renamed
to main (see reference/git-workflow.md). Hub updates adopted
2026-06-26: the process-reports loop (fairyfox-reports/ +
reference/process-reports.md — every fairyfox run, including a
check-only one, writes a report) and the standards compliance audit
(reference/compliance.md), plus ## Verify sections on the git-workflow
and versioning notes. Notes-only adoption — no VERSION bump, no release. Open (hub-side, for the
owner, in the 1fairyfox.github.io repo): the registry's notes: link still points at tree/master/…
(update to tree/main/… after the default branch flips), and the adopts_hub/docs-site flag is overstated.
The registry's branch: dev is correct — that field tracks the work branch, not the default branch.
Hub span 0.20.2 → 1.6.1 adopted 2026-07-25: seeded the project's first
reference/adoption-manifest.md (0.21.0 linchpin) + four new-standard
notes (checklists-are-contracts, mandate-ledger, docker, readme), folded ship-contract / phase-
by-default / manifest-gate / base-coin-mandatory into the existing notes, and made the README
readme-standard compliant. Notes/docs only, no VERSION bump. Open gaps (in the manifest): the
first full compliance pass (flips copied-only→implemented), the browser-gated docs-site chrome bump
2.2.1→2.3.0 + 1.4.0 visual requirements, and a committed Docker path for the Linux-only test loop. See
fairyfox-reports/2026-07-25-adopting-updates.md.
First full compliance audit run 2026-08-02: 25/30 applicable standards implemented; headline
finding — the ship-contract's OpenSSF Scorecard is 6.7, below the 7.0 floor (live-API verified; 27
known vulns, bulk RUSTSEC in the Tauri/Rust desktop deps, plus solo-repo-structural checks). Also
partial: supply-chain (no release provenance), docs-site (browser-gated), docker (no Dockerfile),
mandate-ledger. Fixed in-pass: pruned 5 stale Dependabot branches (remote = main/dev only). The
Scorecard remediation needs a dedicated pass (risky Rust CVE updates on an unverified desktop build) —
owner's go-ahead. See
fairyfox-reports/2026-08-02-compliance-audit.md.
Scorecard REMEDIATED 2026-08-02 (2.60.3): 6.7 → projected ~7.4 ≥ 7.0. The dominant lever
(Vulnerabilities, was 0) is cleared and verified with osv-scanner v2.4.0 = "No issues found" — the
tool Scorecard runs. Fixable advisories fixed (root/web npm audit fix; mobile postcss/brace-expansion
overrides); the 17 unmaintained Tauri-v2-Linux Rust crates (no upstream fix, per cargo audit) + 1
build-time Expo uuid documented + filtered via osv-scanner.toml next to each manifest. npm test
green. Manifest ship-contract row → implemented; now 26/30 applicable standards implemented. The
public api.securityscorecards.dev refreshes on OSSF's weekly rescan. See
fairyfox-reports/2026-08-02-scorecard-remediation.md.
Blocks (2.5.0): added pick-one groups — a category folder with 2+ generators is an implied
group ({#scene} runs one random scene generator; .group files + markers too), and the same for
expansions (<lighting> splices one random expansion). Added the {#any} / {#any-sfw} / {#any-nsfw}
wildcard (one random generator from the whole catalog, {keyword}-style mode variants). Renamed the
engine/ category to prompt/ (force-prefixed → {#prompt/…}): danbooru→d, random→random-words,
and *-prompt→* (so {#prompt/random} is the composite; the default settings.prompt is now
{#random-words}). Reworked the SPA navbar into a single "Prompts" heading with one v1/v2 superset
switch (v2 default) over full / partial sub-tabs; folder-group and {#any} pills are clickable.
The "pick one" always resolves to ONE concrete generator/snippet, never a line union.
Blocks (2.3.0 + 2.4.0): engine/data/blocks/ was brought to full parity with the
list/expansion systems. 2.3.0: the 79 v2 generators (+ the user-submitted one) were reorganized into
category folders under a new v2/ root (scene/subject/fragment/style/prompt/user), v1/ left
frozen; resolution by path suffix, <name>.json description sidecars, _-internal / _force-prefix /
compareNames. 2.4.0: the sigil became {#name} (brace-delimited like {list}/<expansion>,
/-path capable; bare #name retired — 204 internal refs migrated, v1 untouched); automatic NSFW gating
by name token (isGatedBlock); tag metadata (engine/blockManifest.js); and a uniform SPA — one
Dynamic-prompts block with category-folder pills
(plain labels — folders are organization, not groups: a generator is a script, not a word pool) and a
v1/v2 toggle. Only the new engine (core loaders/stage, classifier, SPA) was touched — the classic
server + prompt-modules/ are read-only legacy reference. See
reference/blocks-architecture.md.
Expansions (2.2.0): data/expansions/ was brought to parity with the list system — the 9 snippets nest
into category folders (detail, style, lighting, subject, scene) with shared path-suffix resolution
(existing <name> references unchanged), each has a <name>.json description sidecar (folders too), and the
SPA token cloud groups them by folder with tooltips. Random-union groups / clickable folder pills / SFW-NSFW
splitting were intentionally left out (they don't fit deterministic copy/paste snippets). See
reference/expansions-architecture.md.
Keyword lists (2.1.0, branch cleanup/list-reorg): the engine/data/lists/ corpus was purged of slurs /
minor-sexualizing / extreme-shock content via a new engine/contentSafety.js filter (wired into the CSV
build scripts), the 48k-line keyword.txt dictionary was sorted by part of speech into dict-* lists
(keyword.txt is now proper nouns), and duplicated composites were collapsed into virtual lists
(engine/listManifest.js: danbooru, d-keyword, d-character, artist, artist-digipa, plus new
danbooru-sfw and *-all). See reference/list-architecture.md.
Current state (read this first)
Everything below happened during the 2026-06-18 revival (the project had been dormant since 2023-04-07). Four strands, in order:
1. Modernized to ES modules on Node 24.
- Runtime: Node 24 LTS (was implicitly an old Node).
.nvmrc=24,engines.node >= 24. - Module system: the entire codebase is now ES modules (
"type": "module"). ~130 files moved from CommonJS (require/module.exports) toimport/export. There is no remainingrequireexcept the deliberatecreateRequireused for config-driven synchronous plugin loading (dynamic prompts / prompt modules) and the optional legacyuser-settings.jsmigration. - Dependencies: all taken to current majors — Express 5, yargs 18, open 11,
cli-progress3,crc4,compromise14, lodash 4, pug 3.node-fetchwas removed in favor of the built-in globalfetch. - Tooling added: ESLint 9 (flat config) + Prettier 3, plus
.editorconfig,.nvmrc,.prettierrc.json/.prettierignore.npmscripts:start,server/webui,lint,lint:fix,format,format:check.
2. Reorganized the tree (2.0.1). All code lives under src/, all prompt content (lists,
expansions, presets, the CSV sources) under data/; runtime/user data (output/,
user-settings.json, results.json) stays at the repo root. src/chdir.js pins the cwd to the repo
root (its parent) so every cwd-relative path keeps working.
3. Started the web migration — a React + Vite SPA (targets/web/, usable online BYOK or locally). The
real prompt engine was ported to a browser-safe core/ driven by an injected loader (Node: fs +
createRequire; browser: Vite import.meta.glob), so there is one engine, no duplicated prompt logic.
As of 2.0.2 the SPA front-end is a single redesigned home page (Home.jsx) styled after the
pre-revival generate screen — dark charcoal + mint brand, Rokkitt/Maven Pro, a hero, and one composer
that unifies prompt building (blocks cloud, share links, custom expansions/presets,
Normal/Anime toggle, live preview) and generation (provider line, generate prompts/images,
in-session gallery). The full settings form lives in a right-side slide-over drawer
(SettingsDrawer.jsx). Build tooling is Vite 8 / @vitejs/plugin-react 6. The classic Express + Pug
server and the CLI are untouched and still work.
4. Documented the whole repo in one JSDoc doc-site. npm run docs (scripts/build-docs.mjs) builds
a single JSDoc + docdash site that unifies the per-function code API (every authored .js,
including the React SPA via a babel-transpile-then-JSDoc step) with the entire notes/ tree as
tutorials (cross-links rewritten). Doxygen was retired. Coverage is complete: @file on every
authored file, per-function JSDoc across all server-side code, all 113 blocks, the frontend
scripts, and the whole targets/web/ SPA — only anonymous callbacks are left (no generator extracts them).
The full AI/notes system (CLAUDE.md + notes/) backs all of this and is kept living.
First ship (2026-06-22): the deployment hold is lifted. The stable branch (then master, renamed to
main on 2026-06-25) was first advanced off the pre-revival 241a148 up to the CI-green dev HEAD.
As of the 2026-06-25 git-flow adoption it now advances by --no-ff merge + tag (PATCH straight from
green dev, MINOR/MAJOR via a release/* branch) rather than fast-forward. Getting there required
unbreaking CI first — both npm ci jobs were red on a
lockfile drift (root + gui) and format:check was red on ~40 un-Prettier'd files; both fixed
(build/style only, no version bump). Active work continues on dev. CI now runs the full gate — lint,
format:check, smoke, the Node + jsdom Vitest suites, the gui build, and the Playwright E2E +
accessibility + visual-regression specs. Visual works cross-OS: baselines are committed for both Windows
(*-chromium-win32.png, system Chrome) and Linux (*-chromium-linux.png, bundled chromium); regenerate the
Linux set via the "Update visual baselines (Linux)" workflow (visual-baselines.yml). See
reference/deployment.md.
Verification done: node --check on all 152 server-side JS files (0 syntax errors); npm run lint
(0 errors, 163 pre-existing style warnings); a Prettier pass over the codebase; and an import smoke
test that loads the whole ES-module graph, loads all 113 blocks via require(ESM), runs
promptSuggestion(), and expands #random — all green.
Not yet runtime-verified end to end: actually generating an image requires a running Stable
Diffusion WebUI (--api) on imageSettings.url; that wasn't exercised here. The CLI (index.js) and
server (server.js) entry points pass syntax + import-graph validation and use Express-5-safe route
patterns, but were not launched live (launching the server opens a browser on the user's machine).
Open issues
| Issue | Where | Status / notes |
|---|---|---|
DONE (2.6.0). Full Vitest (Node + jsdom SPA) + Playwright (E2E/visual/a11y) suite — 738 Vitest tests green (319 Node + 419 jsdom). See plans/testing.md. |
||
no-dupe-else-if / no-useless-escape lint warnings |
blocks/**.js, some regexes |
RESOLVED. The tech-debt sweep cleared all ESLint warnings; npm run lint (eslint + stylelint) is now 0 problems. |
| Live generation unverified end-to-end | the provider adapters (targets/shared/**) |
Fully exercising real image/text generation needs live provider keys (or a running SD WebUI); not done in CI. |
Build / run health
Headless gate re-run on 2026-07-09 (the maintenance sweep). Playwright E2E/perf, the docs build, and the SPA production build were not re-run in this sweep but are green in CI.
| Area | Status |
|---|---|
npm install (Node 24) |
✅ resolves clean |
npm test (headless gate: check:docs · lint · smoke · test:unit · test:web) |
✅ green (2026-07-09) |
npm run check:docs |
✅ all relative doc links resolve |
npm run lint (eslint + stylelint) |
✅ 0 problems (no warnings) |
npm run smoke |
✅ ES-module graph + all blocks load; sample prompt expands |
npm run test:unit (Vitest, Node — unit/integration/snapshot/regression) |
✅ 319 passed (35 files) |
npm run test:web (Vitest, jsdom — SPA unit/component/contract/integration) |
✅ 419 passed (63 files) |
npm run check:tidy |
✅ working tree tidy (no untracked non-ignored files) |
npm run test:e2e · test:perf:scenarios (Playwright — E2E/visual/a11y + perf) |
✅ green in CI (not re-run in this sweep) |
npm run docs (JSDoc doc-site) · targets/web SPA vite build |
✅ green in CI (not re-run in this sweep) |