v2.52.0
Random AI prompt + image generator for the Stable Diffusion WebUI -- a CLI and a local web UI.
Pre-release software. Provided as-is under Apache-2.0.
What's new in this release
2026-07-10 — Address CodeRabbit review across the mobile target (2.52.0)
Worked through the mobile-app review findings so the 2.52.0 PR's conversations resolve on merit:
- Stability/timeouts: a shared
fetchWithTimeout(AbortController + 120s, merges any caller signal)
now backs every network call inlib/imageProviders.js;lib/storage.jsraces remotedownloadAsync
against a 60s timeout so a stalledhttps:source can't hangsaveImageSrc;lib/keys.jssetKey
returns a success flag and the BYOK key field warns on a failed keystore write instead of looking
saved. - Error handling: GalleryScreen surfaces a load failure with a Retry (no more infinite "loading…")
and wrapsdeleteSelected; SingleScreendoConvertgained thebusyguard + serialization its
doResize/doUpscalesiblings already use. - Correctness: GenerateScreen insert-menu / block-palette tokens now target the active field
(Prompt or Negative), and result-row ids use a monotonic per-click batch counter so a fixed
promptSeedcan't collide ids and misattribute images across rows; the "Rewriting…" status always
clears on the copy/no-provider path. - Polish: ComfyUI
batch_sizefloor-clamped to ≥1 (parity with the WebUI path); custom list names
disallow/(they wouldn't round-trip in the flat lists dir);index.jsuses an explicit./App.js
import extension.
2026-07-10 — CodeRabbit review: harden mobile-parity tooling + metroLoader test
Addressed the CodeRabbit review items on the release-hardening code itself. The visual-parity dev server
now guards decodeURIComponent (a malformed escape like /% no longer crashes it before the fs try
blocks); build-metro-catalog.mjs derives generated .js import specifiers from the OUTPUT file's
directory (so a custom --out resolves correctly) instead of a hardcoded ../data/blocks/; and
tests/mobile/metroLoader.test.js tightened its known-value assertions (toBeTruthy + explicit
object-or-null / never-undefined checks) so a loader regression returning undefined can't slip past
not.toBeNull() / not.toThrow().
2026-07-10 — Release-harden 2.52.0: use the CodeQL-recommended path-traversal sanitizer
Follow-up to the traversal fix: containment guards (ternary, then early-return startsWith) were both
logically correct but CodeQL's taint tracker still saw req.url-derived data reach statSync/
readFileSync and kept the 3 high js/path-injection alerts. Switched to the exact sanitizer form the
js/path-injection help recommends — normalize() the request, strip any leading ../ / ..\
segments, then join under OUT — which CodeQL recognizes as sanitizing. Verified every traversal
variant (raw, ..%2f, %2e%2e, interior ../) stays inside the served directory; normal assets
resolve unchanged.
2026-07-10 — Release-harden 2.52.0: remove mobile dead code + align test assertions
Cleared the CodeQL "unused" notes and SonarCloud Reliability/Maintainability findings that blocked the
2.52.0 release PR — real cleanup, no gate-silencing. Removed 8 unused imports/variables in the mobile
target (App.test.jsx top-level Text, BlockPalette.test.jsx an unused getByText destructure,
GenerateScreen.test.jsx waitFor, lib/icons.js G, GenerateScreen.js ShuffleIcon,
SingleScreen.js TextInput + promptText imports + the upscaleProvider theme destructure). In
tests/mobile/single.test.js, aligned the three numeric detail assertions (Steps/CFG/Seed) with the
string-valued Object.fromEntries map Sonar infers, and switched toBe(null)/.length).toBe() to the
dedicated toBeNull/toHaveLength matchers.
2026-07-10 — Release-harden 2.52.0: earn metroLoader Node coverage (race-free)
Reverted the engine/core/metroLoader.js coverage exclusion (silencing a real gap) and instead EARNED
its coverage with tests/mobile/metroLoader.test.js — exercises every accessor and proves the loader
drives createEngine byte-identically to nodeLoader; metroLoader.js is now measured (~87%) rather
than hidden. Only the generated, gitignored engine/core/metroCatalogData.js stays excluded (data, not
logic), and it's now also excluded from Sonar analysis/coverage/duplication so the CI-built blob isn't
scanned. Made the parity check deterministic by moving the catalog build + a clean-state snapshot of
nodeLoader's names and parity generations into a Vitest globalSetup
(tests/setup/metro-catalog.globalSetup.js), captured before any worker spawns — so it can no longer
race manageFs.test.js's transient shared-FS fixtures or a concurrent-read scan miss on Windows.
2026-07-10 — Release-harden 2.52.0: fix path traversal in the mobile visual-parity server
CodeQL flagged three high-severity "uncontrolled data in a path expression" alerts in the mobile
visual-parity dev server (scripts/mobile-visual-parity.mjs): req.url flowed straight into
join(OUT, …) → statSync/readFileSync with no boundary check, so GET /../../etc/passwd could
escape the served directory. The server now resolves each request under an absolute OUT_ROOT and
rejects anything that leaves it (falling back to index.html), blocking raw and URL-encoded
(..%2f, %2e%2e) traversal while normal asset paths resolve unchanged.
2026-07-10 — Mobile: full Single-view parity + first automated test layer (2.52.0)
MINOR milestone. Rounds out the mobile (Android/Expo) target to web parity on the Single view and
stands up its first automated tests. See the detailed entries below; in brief: SingleScreen rebuilt to
full parity with the web SingleView (nav, layered prompt/negative, inline re-roll/variation, convert/
resize/upscale, lineage + derived strips, details/raw-JSON/copy, keyword cloud), the Generate/Gallery
parity gaps closed, and a jest-expo + React Native Testing Library suite (64 tests) covering every
mobile screen, menu, and lib module — wired into npm test (test:mobile). targets/mobile excluded
from the root ESLint + Prettier (Expo owns its own toolchain), matching targets/web.
2026-07-10 - Mobile: component tests for every screen/menu + lib unit tests
Completed the mobile automated test layer to the web suite's "every module covered" bar. 64
mobile-specific tests, all green:
- Component (jest-expo + RNTL, 40 across 10 suites): App shell (tabs + ⋯ overflow), SingleScreen,
GenerateScreen, GalleryScreen, ManageScreen, OverflowMenu (three-role picker / appearance / language
over the real registry), InsertMenu (DPL construct drill-in + insert), BlockPalette (search + groups +
chip insert), plus lib keys (secure-store) and storage (file-system index). - Unit (root vitest, 24 across 2 files): lib/single.js (16) and lib/imageProviders.js registry /
defaults / systemFor / structural invariants (8). - Added
accessibilityLabels to the remaining icon-only buttons (App ⋯) — a11y parity + test handles.
jest.setup mocks safe-area (useSafeAreaInsets), status-bar, and FlashList (header + rows + empty).
2026-07-10 - Mobile: component tests extended to Generate / Gallery / Manage
Grew the jest-expo + RNTL suite from Single to every screen (20 tests, all green): GenerateScreen
(composer renders, rolling fills the results feed, the gear opens the full settings sheet, live
preview), GalleryScreen (header/count/search/select/refresh/composer, search filter, multi-select,
empty state), ManageScreen (lists master view, create-opens-editor, open windowed editor, delete,
empty state). Added accessibilityLabels to the icon-only buttons exercised (Generate, Prompt
settings, Toggle live preview, Generate-here) — an a11y-parity gain with the web's aria-labels as well
as test handles. The shared FlashList test mock now renders ListEmptyComponent, and the safe-area
mock exports useSafeAreaInsets.
2026-07-10 - Mobile: jest-expo + RNTL test harness; component + helper tests
Stood up the mobile target's first automated test layer (it had none) to the bar of the web suite —
real component tests that mount the actual RN screens (the Android code path) through the jest-expo
renderer with native modules mocked:
- Harness:
jest-expopreset +@testing-library/react-native+ pinnedreact-test-renderer
(19.1.0, matching React 19.1.0);jest.setup.jsmocks the native leaves (expo-image, FlashList,
expo-file-system/legacy, expo-image-manipulator, expo-media-library, expo-sharing, expo-clipboard,
expo-secure-store, safe-area-context). Run vianpm --prefix targets/mobile test, wired into root
npm testastest:mobile. - Single component tests (
screens/__tests__/SingleScreen.test.jsx, 7): mounts the real screen and
asserts nav/position/tools render, the layered Prompt+Negative cards render from metadata, the details
table renders, the keyword cloud fires search, inline Re-roll fires the provider's generate adapter,
Back invokes its callback, and the empty state shows. - Single helper unit tests (
tests/mobile/single.test.js, 16, root vitest): layer normalization
(enriched + legacy), buildDetails, parseKeywords, linkChildren, sizeFromSettings, toMarkdown,
searchHaystack. - Excluded
targets/mobile/**from the root ESLint (Expo has its own toolchain), matchingtargets/web/**.
2026-07-10 - Mobile: Single view rebuilt to full web parity; Generate/Gallery gaps closed
Comprehensive web-to-mobile comparison of the remaining surfaces (SingleView.jsx + its subcomponents,
PromptComposer, Gallery), then brought the mobile app to parity:
- Single (
screens/SingleScreen.js, rebuilt): prev/next nav + position, open-full viewer, share
(expo-sharing) / save-to-Photos (expo-media-library) / delete overlay actions, Convert (format) +
Resize (scales) + AI Upscale via expo-image-manipulator, lineage header + parent link, derived-children
strips (re-rolls / variations / resizes) with in-flight placeholders, the prompt and negative each in
Sent/AI/Roll/DPL layers (copyable, with inline Re-roll / Make-variation), a curated details table with
raw-JSON toggle + Copy Markdown/JSON, and a clickable keyword cloud (AI rebuild + gallery search). New
lib/single.jsholds the pure layer/keyword/details/lineage/markdown helpers;lib/storage.jsnow
records the full sidecar (layers, negative, seed, size, settings, lineage) so the above populates. - Generate (
screens/GenerateScreen.js): negative-prompt tab (provider-gated), inline Images/Size
controls, a Wrapper modal that frames each rolled prompt via the shared engine (buildRoll parity), and
per-prompt "Generate images" with inline thumbnails. - Gallery (
screens/GalleryScreen.js): compact composer atop the grid with pending placeholder tiles;
keyword taps from Single land here pre-filtered. - Mandatory gate (
scripts/mobile-parity-check.mjs): added a Single surface with 24 feature markers
(no per-feature ignores) alongside Header 12 / Generate 16 / Gallery 9; all green. Web + iOS Metro
bundles build clean.
2026-07-09 - Mobile: automated web-parity check (ported catalogs)
Answering "how do we test parity": added scripts/mobile-parity-check.mjs (wired into npm test +
npm run mobile:parity) that asserts every catalog the mobile app copies from the web still matches the
web SOURCES, so drift fails CI:
- Accents —
themeData.js⇄theme/themes/*.json(ids + swatch + dark/light tones). - Locales —
themeData.js⇄i18n/config.js(the real locale is a registered web locale). - DPL insert categories —
dplInserts.js⇄ webdpl/dplInserts.js. - Image providers —
imageProviders.js⇄shared/*/config.js(each mobile provider is browser-direct on
the web, so it can actually run without a backend; the check also reports browser-direct image providers
not yet wired on mobile — currently leonardo).
To make the data importable in Node, the accent/locale DATA moved to an RN-freelib/themeData.jsthat
theme.jsre-exports. This complements the existing engine parity (metro-parity-check.mjs, proving the
mobile catalog output == the Node loader) and the manual Playwright-at-390px visual comparison. Three layers
now: engine/data parity (automated), ported-catalog parity (automated, new), visual/UX parity (screenshot).
2026-07-09 - Mobile: Gallery rebuilt for web parity — search, multi-select, bulk delete, metadata
Full comparison of the mobile Gallery against the web (Gallery.jsx + photo-gallery.css, read in full). The
mobile gallery was a bare grid; brought it to parity:
- Header: "Photo gallery" title + live count ("N images" / "· M matches"), a keyword Search (over the
prompt + provider), a Select button, and Refresh — matchingg-head. - Multi-select: a selection action bar (N selected · Select all · Clear · Delete N · Done), per-cell
selection ring + ✓ badge, long-press to enter select mode, bulk delete viastorage.deleteImages. - Metadata: images now save a prompt / provider / model record in
images/index.json(the mobile analog
of the web's per-image.jsonsidecars — one index so listing stays O(1) at the 100k max).saveImageSrc
writes it,listImagesmerges it, delete prunes it; Generate passes the prompt/provider/model. This is
what makes search and the Single-view details work. - Single view now shows the image's prompt + provider/model.
- Empty ("No images yet") + no-match ("No images match …") states.
- 100k performance: the grid is a recycling FlashList of uniform square cells, and each cell is a
memoizedCell(the web's<Thumb>pattern) takingselectedas a boolean — so a single selection
toggle re-renders only that one cell, not every visible one, and search/select stay smooth at the 100k max.
The prompt metadata is a singleindex.json(not 100k sidecar reads) so listing stays O(1). Verified the
header + empty state via web render (phone storage is a no-op there; the populated grid + multi-select need
on-device images to see).
Two web features deferred (they're coupled): the compact composer atop the gallery (generate-in-place)
and its pending placeholder cells — they need the composer extracted into a shared component, which is
the next step for full parity.
2026-07-09 - Mobile: overflow menu rebuilt to match the web structure (rows → sub-menus)
Owner flagged the ⋯ menu had strayed — I'd expanded every control inline with big section headers, which the
web doesn't do. Rendered the REAL web overflow (the targets/web build, Playwright at 390px) and matched it:
a list of compact control rows — Providers ▾, ⚙ Provider settings, 🎨 Appearance,
Language ▾ — each drilling into its own sub-menu, then the project links + legal pages + version (the
folded-in LinksMenu). No features lost / no regression: the provider picker + BYOK key live in the Providers
sub-menu; per-provider model / size now live in Provider settings (they were missing before); mode +
accent in Appearance; locale in Language. The web's NSFW row is intentionally omitted — the mobile build
is all-ages (owner's earlier call). Verified against the web render; the mobile overflow now matches it
row-for-row.
2026-07-09 - Mobile: Providers + API keys — BYOK image generation into the Gallery
Third of the three overflow controls, and the image-generation milestone. Only the browser-direct BYOK
providers work on mobile (no backend, like the online web build), so mobile ships those:
- lib/imageProviders.js: adapters ported from targets/web/shared//code/server.js for the
browser-direct image providers — OpenAI (DALL·E / gpt-image), fal.ai (FLUX/SD3.5), Stability
AI, Google Gemini. Eachgenerate({prompt,key})calls the provider API directly →{images}
(data:orhttps:URLs). - lib/keys.js: per-provider API keys in the OS keystore via
expo-secure-store(web-safe no-op). - lib/storage.js
saveImageSrc: saves adata:/https:/file:image into the Gallery (base64
decode / download / copy). - OverflowMenu: an Image provider section at the top — the provider picker + a secure API-key field
(+ "Get a key" link) — the web ProvidersMenu + ProviderGear. - GenerateScreen: when a provider + key are set, Generate rolls the prompts AND generates one image per
prompt via the adapter, saving each into the Gallery (live "Generating i/N…" status + errors); with no
provider it stays prompts-only. App bumps the Gallery viaonGenerated. - Ported OpenAI first (owner's pick). Verified the UI + an error-free bundle via web render; actual image
output needs the owner's API key + on-device network to confirm (can't be tested from here). The
hosted-proxy providers (most of the ~40) need the desktop backend and are intentionally omitted; NSFW
stays off the Play build. Follow-ups: Leonardo (submit-poll), per-provider model/size gear.
2026-07-09 - Mobile: Language picker in the overflow (Auto / English)
Added the Language control to the ⋯ menu, folded in above the links like the web LinksMenu. Options mirror
the web's real locale set — Auto (follow device) and English — persisted alongside the theme choice.
English is the only fully-authored language on both web and mobile (the web i18n config ships only English +
a dev en-XA pseudo-locale, which was skipped), so this is a real, persisted preference that a future
translated catalog plugs into; it doesn't change the UI language today (a note in the menu says so). Second
of the three overflow controls the owner picked; Providers + API keys (the image-generation milestone)
is next.
2026-07-09 - Mobile: theming — System/Dark/Light + accent picker (the web Appearance control)
Built a real mobile theming layer (the counterpart to the web ThemeProvider + tokens.css) — the first of
the three overflow controls the owner asked for:
- lib/theme.js: a ThemeProvider +
useTheme()hook serving the semantic token objectTfor the
current base (System follows the OS viauseColorScheme; Dark; Light) and accent. Mirrors the web's
dark + light palettes and all 9 accent presets (theme/themes/*.json: mint / teal / cyan / blue / violet /
magenta / pink / coral / amber). The choice persists to the app's document dir. - Every screen/component now reads
const { T } = useTheme()and builds its styles viamakeStyles(T)
(memoized) — required becauseStyleSheet.createcaches at module load, so theme-dependent styles must be
built per-render. Memoized rows (ResultRow / LineRow) call the hook themselves so FlashList stays smooth
(the context value is stable unless the theme changes). A mode/accent change re-themes the whole app live. - components/OverflowMenu.js: the ⋯ menu now leads with an Appearance section — the mode segmented
control (System / Dark / Light, with icons) + the 9-swatch accent grid — above the links, matching the
web ThemePicker folded into the overflow. - Verified via web export + Playwright: Dark + Violet recolored the tabs, editor, tools, generate button,
and FAB together. Next of the three: the Language picker, then Providers + API keys (the image-gen
milestone).
2026-07-09 - Mobile: header rebuilt to match the web (real logo, accent-strong tab, functional ⋯ menu)
Read the actual web header (App.jsx topbar, title-bar.css, view-switch.css, topbar-responsive.css, and
LinksMenu.jsx) and matched it:
- Brand is the real app logo (the wordmark is hidden on phones, like the web), replacing the invented
pencil square. - View-switch active tab now fills with
accent-strong(not plain accent) and the switch background is
the input tone — matching view-switch.css. - The ⋯ overflow was a dead button — it now opens the controls menu (
components/OverflowMenu.js): the
project links (GitHub, docs, fairyfox.io), the "get it" group (desktop app, self-host), the legal pages
(Privacy/Terms/Cookies →prompt.fairyfox.io/legal/*, the origin referenced in the legal HTML), and the
version — the web's compact LinksMenu folded into the overflow. Opens links viaLinking. The
Providers / NSFW / Theme rows will slot in here as those features land on mobile. - Added the link icons (GitHub / Book / Home / Download / Server / Shield / FileText / Cookie / ExternalLink)
tolib/icons.js.
2026-07-09 - Mobile: palette sub-tabs wrap, generate bottom-aligned, FAB clears the system nav
More on-device fixes:
- Palette sub-tabs still showed no text / wrong size — the horizontal
ScrollViewcollapsed the pills
on Android even with the items in a row View. Dropped the ScrollView entirely: the folder sub-tabs are now
a wrapping row (like the web nav, which also wraps), which lays out reliably and shows every folder. - Generate button now bottom-aligns with the tool-icon row (field bar
alignItems: flex-end) instead of
centering between the Prompts count and the tools. - Blocks FAB now sits above the phone's system nav buttons — its bottom offset uses the safe-area
bottom inset (useSafeAreaInsets) so it clears the Android navigation bar.
2026-07-09 - Mobile: Generate polish from on-device testing (sub-tab gap, wired +, generate alignment)
On-device fixes after owner testing:
- Palette sub-tabs / completion strip showed no text (collapsed buttons) —
gapon a horizontal
ScrollView'scontentContainerStyledoesn't apply on Android (it works under react-native-web, which is
why the render missed it), so the pills piled up at x=0. Wrapped the items in a rowView(wheregap
works) in both the palette folder sub-tabs and the DPL completion strip. - Gutter “+” is now interactive — appends a fresh line (was decorative).
- Field bar alignment — the round generate button is now pinned to the far right, separate from the
left-aligned Prompts count + tool icons (which wrap as a left cluster).
2026-07-09 - Mobile: faithful Generate rework — DPL Insert menu, live preview, real Blocks/Lists palette + completion
Did the proper pass on the Generate tab by reading the actual web source (not the screenshot). Three things
were conflated before and are now correct + faithful to the web:
- Insert ▾ is the DPL syntax menu (structure/chance/choose/repeat/flow/emphasis/code): a
category→constructs drill-down where each construct shows its name, description, syntax, and a live
example that re-rolls every second (porteddplInsertscatalog + the compact InsertMenu). It inserts a
DPL snippet, NOT a building block. (lib/dplInserts.js,components/InsertMenu.js.) - The eye is a live preview that re-rolls the current prompt every second; a click toggles it
(mobile has no hover). Was a one-shot before. - Building blocks is now the real palette (
components/BlockPalette.js) — a faithful port of the web
getBlocksover the metroLoader (lib/blockCatalog.js, reusingengine/nameOrder.js+
engine/gatedLists.js) plusfoldersOf(lib/blockCategories.js): a search box, Blocks / Lists
groups with counts, an All row + folder sub-tabs (scene/subject/style/…, artist/look/…), category
hints, and insertable group-pills. - NEW: DPL completion strip — typing a
{…}/{#…}token surfaces matching blocks/lists to
tap-complete (the mobile form of the web editor's autocomplete;getDplCompletionsported). lib/engine.jscentralizes the shared enginerun+expandOnce(clean example expansion). Verified via
web export + Playwright (Insert menu, a drilled category with live examples, Blocks + Lists palette); no
page errors.
2026-07-09 - Mobile: device-test fixes (safe-area, reliable editor, real Prompts count, tap targets)
On-device testing (owner) surfaced real issues; read the REAL web composer (components/PromptComposer.jsx)
for true parity, then fixed:
- Status bar no longer covers the tabs: switched to
react-native-safe-area-context's SafeAreaView
(react-native's is a no-op on Android) so the top bar insets below the status bar. - Prompt editor was garbled on-device (the highlight-overlay technique) - replaced with a plain
monospace input (gutter + DPL ✓/✕ + preview/gear corner). Reliable; live syntax coloring can return
later as a blur-swap. - Removed invented settings (keywords min/max, include artists, random-each-time) - they aren't on the
web composer face. The web puts Prompts-per-run as an editable count in the field bar (now mirrored),
and the gear opens prompt settings (minimal for now). Auto-fix / keyword-translate tools are shown
disabled (they need a text provider, which the mobile SFW build doesn't have) - matching the web. - Touch glitches ("buttons click other buttons") fixed: dropped the transparent editor overlay and the
hitSlopthat made neighboring toolbar buttons' tap areas overlap.
2026-07-09 - Mobile: composer + pill-nav fidelity pass (react-native-svg icons) + web⇄mobile parity rule
Second pass tightening the mobile UI to the web phone layout, and recording parity as a standing rule:
- App.js: reworked the top bar to the web's one-row header - dark brand mark (mint pencil) + the
enclosed pill view-switch (active tab green-filled) + a⋯overflow. Replaces the stacked
brand-over-scrollable-tabs bar. - screens/GenerateScreen.js: rebuilt the composer to mirror the web
PromptComposer- an Insert ▾
dropdown, a code-editor prompt box (line-number gutter1 ++{…}syntax highlighting via a highlight
layer under a transparent input, a ✓ valid indicator + eye/gear icons), a PROMPTS count, a tool
toolbar (wand · tag · brackets[active] · share · shuffle) + a round green generate button, and the
bottom-left green building-blocks FAB. - lib/icons.js (new): hand-written
react-native-svgline-icons matched to the web icon set (added the
react-native-svgdependency; Expo Go bundles it, so no native rebuild is needed). - Standing rule: keep the mobile UI and the web UI in parity by default (see
notes/systems/mobile.md). - Verified via
expo export --platform web→ Playwright at 390px (Generate/Gallery/Manage) against the web
reference render; no page errors. Runs on-device via Expo Go over the tunnel.
2026-07-09 - Mobile: reworked UI to match the web app's design (tokens + top-bar nav + composer)
Corrected the mobile UI to mirror the existing responsive web app (it already has a complete phone layout;
don't reinvent it):
- Design tokens (targets/mobile/lib/theme.js) mirrored from the web foundation
(styles/foundation/tokens.css): mint accent (#34e2a0, dark ink), canvas #1c1c1f, panels #232328, the
real text/border colors, 14px radius. Replaces the invented blue/near-black theme. - Top-bar navigation like the web SPA (brand + horizontally-scrollable view switch Generate/Gallery/
Single/Manage) - dropped the bottom tab bar and the top-right tab-name tag. - Home = composer (prompt + Generate) + a building-blocks bottom sheet (the palette, grouped from
the engine catalog: wildcards / expansion / fragment / prompt / scene / lists - chips insert {#name} or
{name}) + virtualized results (newest on top, Prompts header + count + clear-all), matching Home.jsx. - Single stacks image-over-details; Manage stays master/detail with a back control - all re-themed.
Verified: web render at 390px (screenshotted - mint theme + top-bar nav + working blocks drawer) + Android
Hermes bundle (697 modules, 3.25 MB .hbc). No VERSION bump yet.
2026-07-09 - Mobile: full 4-tab app (Generate/Gallery/Single/Manage) built to the max-load bar
Turned the mobile PoC into the real 4-tab app (an earlier wrong assumption scoped mobile to just Generate;
the phone HAS storage, so Gallery/Single/Manage all apply, backed by the filesystem):
- 4-tab shell (App.js): custom bottom tab bar; panes stay mounted so state/scroll persist on switch,
like the web SPA. Screens under targets/mobile/screens/. - Generate: editable prompt + quick-pick chips + settings (prompts/keywords/artists/seed) + batch
generation via the shared engine. Results in a VIRTUALIZED FlashList with memoized rows (1000-prompt max). - Gallery: recycling FlashList + expo-image (disk-cached, downsampled thumbnails, recyclingKey) over
uniform cells - the RN equivalent of the web's windowed grid (100k-image max). - Manage: windowed 100k-line editor - UNCONTROLLED per-line inputs write to a ref (typing never
re-renders the list), FlashList virtualization, responsive filter, stable per-line ids for add/delete. - Single: one image up close (expo-image). Storage (lib/storage.js): phone-local via expo-file-system,
web-safe no-op so the react-native-web verification still renders. - Deps: @shopify/flash-list, expo-image, expo-file-system, expo-media-library, expo-secure-store,
expo-clipboard. Verified: web render of all tabs (screenshotted) + Android Hermes bundle (696 modules,
3.24 MB .hbc).
Honors the supported max load (100k gallery / 1000 prompts / 100k Manage) per notes/reference/performance.md
- decisions/architecture.md. Next: image generation (reuse targets/web/shared providers + SecureStore) to
fill the Gallery; wire the Manage overlay into runtime generation. No VERSION bump yet.
2026-07-09 - Mobile: runs on-device (Expo Go), pinned to SDK 54, clean engine alias (copy removed)
Got the mobile PoC running on a real Android phone (Moto G Power, Android 16) via Expo Go, and cleaned up
how the app pulls in the engine:
- Pinned to Expo SDK 54 (RN 0.81.5, React 19.1.0). create-expo-app defaulted to SDK 57, which the
Play/App Store Expo Go cannot run - store Expo Go is pinned to SDK 54 (Expo policy, May 2026), so the
phone showed "requires a newer Expo Go." Downgrading to 54 (expo install --fix) made it run. - Clean engine link via a Metro alias (resolver.extraNodeModules: engine -> ../../engine) + watchFolders
- nodeModulesPaths. No copy, no symlink/junction. Replaces the junction (broke on SDK 54 Metro) and the
interim copy. App imports the engine asengine/.... Verified: web bundle 235 modules + runs on-device.
- nodeModulesPaths. No copy, no symlink/junction. Replaces the junction (broke on SDK 54 Metro) and the
- Removed scripts/link-engine.mjs; setup is now just
catalog. Added web (react-native-web/react-dom) +
tunnel (@expo/ngrok) deps for verification + on-device connection.
Root cause of the resolution pain: the repo root is an ESM (type:module) package, which Metro will not
resolve ACROSS - so plain ../../engine relative imports never worked; the alias resolves the real path and
sidesteps it. Proper fix (engine as a workspace package) logged as a follow-up. Also fixed the local
emulator boot (AVD had hw.gpu.enabled=no + 2GB -> swiftshader_indirect + 4GB, boots ~47s) but headless
screencap hits a host-GPU assertion on this machine, so UI verification uses the react-native-web render.
No VERSION bump yet.
2026-07-09 - Mobile: Expo (New Architecture) scaffold + engine bundles to Hermes for Android
Built targets/mobile as an Expo SDK 57 / React Native 0.86 app (New Architecture, Hermes) and PROVED the
shared engine bundles for Android: expo export --platform android -> a 2.7MB Hermes bytecode bundle
(623 modules), resolving the engine, the generated static catalog, all block generators, and lodash/
compromise. Metro resolves poorly across the monorepo boundary (repo root is an ESM type:module
package), so the engine is linked IN-project via a junction (targets/mobile/engine -> ../../engine, via
npm run link:engine) with the engine's runtime deps (lodash, compromise) installed locally; default
Metro config, all resolution stays inside the project root. PoC App.js runs the real engine through
metroLoader (generate + reroll). Reproducible setup: npm run setup (link:engine + catalog), wired as
prestart/preexport hooks. No VERSION bump yet (compilation proven; not yet run on a device, no full UI).
Next: boot an emulator to see it run, then port the tabs.
2026-07-09 - Mobile (Android/Expo) foundation: Metro catalog generator + metroLoader, parity-proven
Started the Android target as a React Native / Expo (New Architecture) build that reuses the isomorphic
engine, rather than a WebView wrapper (Tauri mobile / Capacitor) or a native rewrite. This commit is the
de-risk FOUNDATION only (no Expo install yet):
engine/core/metroLoader.js- a third isomorphic loader besidenodeLoader(fs + createRequire)
andbrowserLoader(Vite glob). Reads a STATIC generated catalog synchronously: no glob, no fs, no
dynamic require (none of which Metro supports).compileDplbridge mirrors the browser one.scripts/build-metro-catalog.mjs- generatesengine/core/metroCatalogData.js: staticimports
for every.jsblock generator (Metro-bundlable) + inlined.dpl/.txt/.group/.json/presets.
Two tiers:full(GitHub edition, NSFW included) andsfw(all-ages Play edition - everynsfw-token
block/list/group/preset physically omitted, nsfw group references stripped, plus an all-ages scrub hook
for list vocabulary). NSFW gating reuses the engine's ownhasNsfwToken.scripts/metro-parity-check.mjs- go/no-go gate: provedmetroLoaderdrives the unchanged engine
identically tonodeLoader(89 blocks + 88 lists identical built-in; 150 seeded generations byte-for-
byte equal). SFW build verified to carry no adult content or references.- npm
metro:catalog+metro:parity. Generated catalog is gitignored (build artifact).
No VERSION bump - foundation only, not a shippable target yet.
2026-07-09 — Maintenance sweep: branch cleanup, status.md reconcile, + a maintenance-sweep runbook
A periodic "close the books" pass, run at the owner's request (full maintenance / clean things up). No
VERSION bump — dev-dependency + docs/notes only.
- Branches. Merged Dependabot PR #43 (
vitest+@vitest/coverage-v84.1.9→4.1.10, dev-dep patch)
intodevafter surfacing it, then deleted every merged feature branch — 6 localfeature/*and 2
remote (gallery-composer-a11y-seo,user-overlay) — leaving onlymain+devon both ends with no
open PRs. - status.md reconcile. The current-state doc had drifted from the shipped tree: version line
2.46.0→2.51.1; CLI (2.50.0) + ComfyUI (2.51.0) marked as shipped targets rather than "planned";
shipped branches no longer labeled "pending review"; build-health + open-issues tables refreshed with
freshly-run numbers (lint 0 problems, unit 319, web 419) and the now-resolved lint-warning issues
retired. - New runbook. Added
reference/maintenance-sweep.md— the
repeatable full-sweep procedure (audit → triage → close branches → ship → reconcile docs → verify),
composing the existing git-workflow / repo-hygiene / versioning standards with hard safety rules — and
a fairyfox proposal report recommending the hub adopt it cross-project (proposal-only; hub untouched). - Verified
npm test+npm run check:tidygreen, then shippeddev→main(a no-op release since
VERSIONis unchanged) and back-mergedmainintodev.
How to get it
Prefer not to build? Download a pre-built edition:
- Desktop app -- Windows
.msi/.exe(or a portable.zip), macOS.dmg, Linux.AppImage/.deb(attached below). Self-contained; nothing to install first. random-ai-prompt-2.52.0-online.zip-- the online edition as static files; host it anywhere, or just use https://prompt.fairyfox.io.random-ai-prompt-2.52.0.tar.gz-- source tarball (run with Node 24:npm installthennpm start).random-ai-prompt-2.52.0-docs.zip-- the generated documentation site.
What's Changed
- build(deps-dev): bump the engine-minor-and-patch group across 1 directory with 2 updates by @dependabot[bot] in #43
- Ship dev -> main: 2026-07-09 maintenance sweep (no version bump) by @junebug12851 in #49
- Release v2.52.0 by @junebug12851 in #50
Full Changelog: v2.51.1...v2.52.0