Skip to content

v2.52.0

Choose a tag to compare

@github-actions github-actions released this 11 Jul 00:04
· 98 commits to main since this release
944f6d4

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 in lib/imageProviders.js; lib/storage.js races remote downloadAsync
    against a 60s timeout so a stalled https: source can't hang saveImageSrc; lib/keys.js setKey
    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 wraps deleteSelected; SingleScreen doConvert gained the busy guard + serialization its
    doResize/doUpscale siblings 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
    promptSeed can't collide ids and misattribute images across rows; the "Rewriting…" status always
    clears on the copy/no-provider path.
  • Polish: ComfyUI batch_size floor-clamped to ≥1 (parity with the WebUI path); custom list names
    disallow / (they wouldn't round-trip in the flat lists dir); index.js uses 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-expo preset + @testing-library/react-native + pinned react-test-renderer
    (19.1.0, matching React 19.1.0); jest.setup.js mocks 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 via npm --prefix targets/mobile test, wired into root
    npm test as test: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), matching targets/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.js holds the pure layer/keyword/details/lineage/markdown helpers; lib/storage.js now
    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:

  • AccentsthemeData.jstheme/themes/*.json (ids + swatch + dark/light tones).
  • LocalesthemeData.jsi18n/config.js (the real locale is a registered web locale).
  • DPL insert categoriesdplInserts.js ⇄ web dpl/dplInserts.js.
  • Image providersimageProviders.jsshared/*/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-free lib/themeData.js that
    theme.js re-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 — matching g-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 via storage.deleteImages.
  • Metadata: images now save a prompt / provider / model record in images/index.json (the mobile analog
    of the web's per-image .json sidecars — one index so listing stays O(1) at the 100k max). saveImageSrc
    writes it, listImages merges 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
    memoized Cell (the web's <Thumb> pattern) taking selected as 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 single index.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 rowsProviders ▾, ⚙ 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. Each generate({prompt,key}) calls the provider API directly → {images}
    (data: or https: 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 a data: / 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 via onGenerated.
  • 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 object T for the
    current base (System follows the OS via useColorScheme; 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 via makeStyles(T)
    (memoized) — required because StyleSheet.create caches 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 via Linking. 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)
    to lib/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 ScrollView collapsed 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)gap on a horizontal
    ScrollView's contentContainerStyle doesn'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 row View (where gap
    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
    (ported dplInserts catalog + 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
    getBlocks over the metroLoader (lib/blockCatalog.js, reusing engine/nameOrder.js +
    engine/gatedLists.js) plus foldersOf (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; getDplCompletions ported).
  • lib/engine.js centralizes the shared engine run + 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
    hitSlop that 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 gutter 1 + + {…} 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-svg line-icons matched to the web icon set (added the
    react-native-svg dependency; 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 as engine/.... Verified: web bundle 235 modules + runs on-device.
  • 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 beside nodeLoader (fs + createRequire)
    and browserLoader (Vite glob). Reads a STATIC generated catalog synchronously: no glob, no fs, no
    dynamic require (none of which Metro supports). compileDpl bridge mirrors the browser one.
  • scripts/build-metro-catalog.mjs - generates engine/core/metroCatalogData.js: static imports
    for every .js block generator (Metro-bundlable) + inlined .dpl/.txt/.group/.json/presets.
    Two tiers: full (GitHub edition, NSFW included) and sfw (all-ages Play edition - every nsfw-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 own hasNsfwToken.
  • scripts/metro-parity-check.mjs - go/no-go gate: proved metroLoader drives the unchanged engine
    identically to nodeLoader (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-v8 4.1.9→4.1.10, dev-dep patch)
    into dev after surfacing it, then deleted every merged feature branch — 6 local feature/* and 2
    remote (gallery-composer-a11y-seo, user-overlay) — leaving only main + dev on 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:tidy green, then shipped devmain (a no-op release since
    VERSION is unchanged) and back-merged main into dev.

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 install then npm 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