Newest entry on top.
online build fully static — browser-direct generation (2.11.0)
Owner's call after the function-limit discussion: don't restrict users, keep it free, and don't let the
10s timeout / 125k-invocation cap make it unusable. Answer: stop using a backend for the online build —
call providers straight from the browser (BYOK key → provider, never our infra). Ran a live CORS
preflight against all hosted endpoints from prompt.fairyfox.io:
- CORS OK → browser-direct: OpenAI, Gemini, Grok, Stability, Leonardo, fal.ai.
- No usable CORS → must stay proxied (locked online): Replicate, BFL/FLUX, Ideogram.
Implemented: the 6 get transport: "browser-direct"; their generate.js calls their own server.js
directly (dropped callProxy). src/lib/rewrite.js calls the rewrite adapter directly for
browser-direct providers (OpenAI/Gemini/Grok). The 3 non-CORS stay hosted-proxy and are locked in the
online build like local providers — greyed, tooltip "can't be called directly from a browser… use the
full version", click → GitHub. availableProviders() online excludes hosted-proxy; App online-fallback
picks the first browser-direct image provider; lockedHint(feature, reason) gained a reason arg.
Net: the deployed site uses no serverless functions → no cap, no 10s timeout, $0, overnight bulk
runs work (cost on the visitor's key). Functions + dispatch.js kept for the local dev proxy (desktop
full version uses them for the 3 non-CORS providers). Staying on Netlify (owner likes it + the domain).
MINOR 2.11.0. lint clean, online build green (529 modules), npm test green (100 + 51). Next: redeploy.
serverless proxy fix: adapter is not a function (2.10.5)
First real online test run: image gen + auto-fix both failed — /api/generate and /api/rewrite
returned 502 with {"error":"adapter is not a function"} (confirmed by curling the live endpoint).
Root cause: Netlify's function bundler returns an ESM export default as a namespace ({default: fn}),
so dispatch.js's static default imports of the provider server/rewrite adapters weren't callable in
the bundle (fine locally under native ESM — why the test suite + dev server never caught it). Fix: an
asFn() helper that accepts a function or {default} and unwraps it, applied to every entry in
serverAdapters and rewriteAdapters. Redeployed; re-tested the live endpoints with a dummy key — now
returns OpenAI's "Incorrect API key" (i.e. the adapter runs and the request reaches OpenAI). So a real
BYOK key generates. /api/magick 404 is expected/benign (local-only). PATCH 2.10.5. lint clean,
51 jsdom tests green. (Possible follow-up: the proxy maps every upstream error to 502 — a bad-key error
is arguably a 400/401; could pass the upstream status through. Cosmetic, left for later.)
Netlify deploy live at prompt-fairyfox.netlify.app + netlify.toml path fix
Stood up the online demo on Netlify (owner gave the go-ahead, connected the Netlify connector, OK'd
installing the CLI). Created site prompt-fairyfox (team junebug12851), installed netlify-cli
26.1.0 locally, netlify login, linked the repo folder, deployed --prod --build.
Found + fixed a latent netlify.toml bug on the first real deploy: the toml still pointed at a
root-level gui/ (pre-engine-split), so npm --prefix gui → ENOENT. Tried base = "engine-v3" — the
build command then ran in engine-v3 (good) but publish/functions still resolved from the repo root
(the toml's location), so the deploy dir wasn't found. Settled on no base, explicit
engine-v3/-prefixed paths (npm --prefix engine-v3/gui ..., publish = engine-v3/gui/dist,
functions = engine-v3/gui/netlify/functions) — unambiguous for both CLI and any future git-CD. Deploy
then went green: 45 files + 2 functions, online build (VITE_ONLINE in the resolved config).
Attached custom domain prompt.fairyfox.io via netlify api updateSite. fairyfox.io is not on
Netlify DNS (only darkprincess638.com / popupmc.com zones exist on the account), so the owner adds
one CNAME prompt → prompt-fairyfox.netlify.app at the fairyfox.io DNS host; Netlify auto-SSLs
after it resolves. Current setup is a manual CLI deploy (not git-connected) — noted continuous-deploy
options in reference/deployment.md. No VERSION bump (deploy config only). app itself unchanged since
2.10.4.
online build: disable-with-link instead of hide (2.10.4)
Owner wants a public online demo at prompt.fairyfox.io (Netlify), and wants the local-only
features shown disabled rather than hidden so visitors can see what the full version adds. The
VITE_ONLINE build already existed but it hid Gallery/Single, the NSFW toggle, and local providers.
Flipped that to greyed-out-with-tooltip + a click-through to the GitHub full version:
- New
gui/src/lib/online.js— single home forONLINE,FULL_VERSION_URL,lockedHint(feature),openFullVersion(). App.jsx— tabbar always renders; Gallery/Single get.is-locked(lock badge + tooltip, click opens the full version) when online;NsfwTogglealways renders withlocked={ONLINE}. Also: if a local provider is the saved default (ComfyUI is), online falls back to the first hosted provider so Generate works out of the box.NsfwToggle.jsx—lockedprop: greyed, tooltip, click → full version; never reportsonwhen locked.ProvidersMenu.jsx/ProviderPicker.jsx— show all providers; local ones render.is-locked(🔒 "full version" badge, greyed, non-selectable, click → full version) when online.availableProviders()kept as-is for the default-fallback logic.styles.css—.vs-tab.is-locked/.lock-badge,.switch.is-locked,.ps-item.is-locked/.ps-lock.netlify.toml—[build.environment] VITE_ONLINE = "true"so a Netlify build is the online variant with no dashboard config.
Verify: lint 0 errors, prettier --check clean on touched files, online build green
(VITE_ONLINE=true npm --prefix gui run build), npm test green (100 Node + 51 jsdom). Wrote the
prompt.fairyfox.io setup steps (connect repo → branch main → add prompt CNAME → HTTPS) into
reference/deployment.md. PATCH 2.10.4. The Netlify connect + DNS are owner-side dashboard actions.
default wrapper: rays back in (2.10.3)
Owner liked god rays for the default end, fine tossing the rest. Restored {#rays} as the ~50% bullet on
DEFAULT_WRAPPER_SEED.end ({#fx}, {#artists}\n- {#rays}). PATCH, released. SPA build + gui tests green.
default wrapper de-spammed (2.10.2)
Owner: "remove the default wrapper trash too like masterpiece and best quality." Found it in
gui/src/lib/wrapperStore.js — DEFAULT_WRAPPER_SEED.start was literally masterpiece, best quality, highly detailed and the END piled on intricate detail/sharp focus/wide shot/{#rays}/{#dap}.
Emptied the START; trimmed the END to just {#fx}, {#artists} (kept the random art-style/artist variety —
that's the generator's actual feature — dropped the quality words, forced framing, and art-site tags). Also
fixed the WrapperFab placeholder that suggested masterpiece, best quality. No test pinned the default,
so safe; SPA build + gui tests green. Released 2.10.2 (PATCH). (If the owner wants the default fully
neutral, dropping {#fx}/{#artists} too is a one-line follow-up.)
tail cleanup (2.10.1) — catalog-wide sweep
After shipping 2.10.0, swept the whole .dpl catalog for leftovers: grepped for inline otherwise: colon
bugs, the known typos, and dead bare-# refs — all clean. Reviewed the two untouched categories:
fixed the coffecup content typo (expansion/), and de-scattered the user/beach-merk community
submission (doubled {#city} + duplicated synonyms removed, off-theme {#color} crystals dropped, waves
made intensity-aware, credit preserved). Confirmed the {#legacy}/{#dap} quality-filler packs are
opt-in only (no auto-pull). Bumped to 2.10.1 (PATCH) and released dev → main. The block
refactor is now complete top to bottom.
engine: auto-append re-resolution (root-cause fix for the {#rays}-style leak)
Fixed the underlying engine quirk: block.js now appends auto-{#fx}/{#artists} as tokens
before the resolver loop (instead of pre-rendered output after it), so nested {#…} inside them resolve.
This is the cause behind the literal {#rays} leak the fx.dpl content fix had only symptom-patched —
any future auto-appended generator with nested tokens is now safe. New integration test; 0/50 leaks with
auto-fx + auto-artists on; Node suite 99 → 100 green; SPA build green. (Self-audit note: earlier in the
session a too-broad git checkout -- <dir> reverted my own uncommitted edits to 6 files mid-batch; I
rewrote + verified them and confirmed via git fsck / git diff HEAD / local==origin that nothing
committed was ever lost. Tightened my own rule: never run a directory-wide checkout while uncommitted work
exists, and stage with one explicit path per git add.)
prompt/ batch — Phase 2 batch 5 (final category)
Last category. prompt/ is mostly JS builtins (artist/keyword repeaters, danbooru d, the random
suggestion engine) — random by design, left alone. The one real fix: fx.dpl's trailing nested {#rays}
token → inlined as god rays, volumetric lighting, killing the literal-{#rays} leak that auto-{#fx}
caused (it's appended after the resolver loop, so nested tokens didn't re-resolve). Verified 0/40 leaks
with auto-fx + auto-artists on. Phase 2 is complete across all five categories (scene · fragment ·
subject · style · prompt). Possible follow-ups: make the engine re-resolve auto-appended fx/artists (root
cause of the quirk), and decide whether to fold the whole feature/dpl-intensity branch toward main as
2.10.0.
style/ refactor — Phase 2 batch 4 (the intensity overhaul, content)
Renamed the owner-flagged anime-irl → anime-realism (clean break): file moved, meta description map
key updated, stale anime-irl.json removed, new anime-realism.json generated, and the
{#underwater-anime-irl} expansion that pointed at it fixed (it used the dead bare #anime-irl form, so
it had been a no-op — now {#anime-realism}, resolving again). De-scattered anime-realism + retro-poster
(dropped {#expressive}/{#weather}/{instrument}) but kept the artist lines — those define the
look (the owner's "some files use artists, anime-irl is one"). Fixed isometric typos (blackground,
doubled soft colors). Deliberately left the rest of the style recipes alone — their render tags are
functional style definitions, not filler. smoke + Vitest (99+51) + SPA build green. Remaining:
prompt/ (the whole-prompt builders) is the last category; then a sweep for the auto-{#fx} literal-token
quirk noted earlier.
subject/ refactor — Phase 2 batch 3 (the intensity overhaul, content)
The knight→hill/castle scatter the owner flagged is fixed: knight no longer pulls {#landscape} or
[[castle]], and its render-farm Auto End (unreal engine 5 / octane / ray tracing / hdr / …) +
hyperrealistic/detailed face/detailed body filler are gone — it's now a focused character
({#portrait} framing, one-of armor, {emotion}, {#general-state}, intensity detail conditions).
Portrait family de-scattered (portrait-person/portrait-princess dropped {#expressive}/{#weather}/
{instrument}) and bug-fixed: the inline otherwise: colon artifact (which also hit beach/portrait)
and sceptor→scepter. Left the entity*.js polymorphic type-system sidecars alone — they're legit JS.
smoke + Vitest (99+51) + SPA build green. Remaining batches: style/ (anime-irl etc. — the
non-descriptive-name + artist-list cases) and prompt/ (the whole-prompt builders).
fragment/ refactor — Phase 2 batch 2 (the intensity overhaul, content)
Polished the garnish pool (fragment/). Fixed the two typos surfaced last batch (accesories in
room-state, mesmorizing in mystical), trimmed room-state/general-state's over-long damage piles, dropped
filler, and made the magnitude-y fragments intensity-aware ({intensity} clutter / crystal formations /
snow drifts / embers / foliage / schools of fish / wear). Left the 6 already-minimal pools (color, glow,
neon, water, weather, expressive) untouched — they're correctly small. Descriptions unchanged → no meta
regen. smoke + Vitest (99+51) + SPA build green. Next: subject/ (person, animal, knight, the
portrait-* family — the knight→hill scatter you flagged).
scene/ refactor — Phase 2 batch 1 (the intensity overhaul, content)
Started the content refactor with the scene/ category (owner's pick). Decisions confirmed: default intensity stays 50% everywhere; scene first. Kept scene filenames (they're already clear — the rename "clean break" applies where a name is genuinely unclear, e.g. anime-irl in style/, not here), so no cross-ref/SPA/preset breakage — the value this batch is content focus, not renames.
All 26 scene/*.dpl rewritten: de-scatter (no scene/subject pulling another whole scene/subject —
fragments like {#weather}/{#nature} stay as garnish, but {#city}/{#settlement}/{#portrait-*}/
{#ruins} cross-pulls removed), trimmed synonym piles, fixed interrior/accesories typos, wired in the
ignored lists ({view},{time},{mood},{style/*}), and made them intensity-aware ({intensity}
magnitude words + [>70%]/[<25%] detail conditions; great-tree/great-bridge bias up with
{intensity +40/30%}). Converted the 5 JS sidecars (futuristic, great-bridge, great-tree, space,
spaceship) to pure DPL — the giant/huge/massive size helpers became the {intensity} keyword — and
deleted the .js. Regenerated meta sidecars. Eyeballed low/high-intensity output per scene; smoke +
Vitest (99+51) + SPA build green.
Found (pre-existing, deferred): {#room-state} carries an accesories typo (fragment batch); and
generate()'s auto-{#fx}/{#artists} is appended after the resolver loop, so a nested token inside
fx (e.g. {#rays}) can survive as a literal — an engine/auto-append quirk, not a scene issue.
Next batch: fragment/ (color, glow, weather, nature, eerie, mystical, room-state, …) — the garnishes
every scene leans on.
DPL intensity dial — engine + tests (2.10.0, Phase 1 of the block refactor)
Kicked off a large block overhaul (owner request: strip "masterpiece/best quality" filler, stop generators dragging in scattered keywords like knight→hill / anime-irl→portrait, rename/split/focus files, lean on the expanded lists, and add an intensity dial). Agreed scope decisions up front: auto-scale and explicit conditions; default 50% when unspecified (top-level and nested); rename with a clean break (no aliases) for the content phase; engine first, then content in batches.
Phase 1 (this entry) — the engine feature, on feature/dpl-intensity:
src/core/dpl/dpl.js—ctx.intensity(default 50,0→1, clamp 1–100);[weight|cond]/[cond]bracket parsing before and after the bullet, tolerant of|or space and either order, and refusing non-spec brackets so[[castle]]/[deemph]/[a:b:0.5]stay payload; conditions as hard pre-gate include/exclude; auto-scaling of probability gates (scaleGate, not the bare-otherwise) and repeat/choice counts;{intensity}/{intensity%}/{intensity-num}inline tokens with a word ladder; relative±NN%modifiers on the keyword and on nested{#name ±NN%}refs (rewritten to absolute).src/core/stages/block.js—{#name NN%}parsing threaded throughexpandGen/pickFrom/runintomod.default(...,intensity); loaders (nodeLoader,browserLoader) pass intensity to JS sidecars.- Tests — DPL unit block + 2 engine-pipeline integration tests; seeded snapshot updated for the 50%
default.
npm testgreen (lint 0 errors, 99 Node + 51 web), SPA build clean. - Design note:
notes/reference/intensity-design.md.
Open for the owner: confirm the 50% default feels right at the top level (vs 100%); it's one
constant (DEFAULT_INTENSITY) to retune. Next: Phase 2 — refactor the .dpl catalog in category
batches (fragment → scene → subject → style → prompt), using intensity + the lists, renaming for clarity.
Provider header redesign shipped (2.9.0)
Owner asked (in Cowork) to "move the API key to the header," and over a long live-iteration loop that grew into a full header/composer rework — corrected my course several times (the early "regroup one combo" answer was superseded mid-session by "a Providers dropdown with two stacked combos"). Landed as one MINOR.
- Header → one Providers dropdown + a gear + NSFW.
ProvidersMenu.jsx(new) holds two rich pickers (ProviderPicker.jsx, new, recreated from the deletedProviderSelect): Image (Local incl. Plain / Online) and Text (Off + OpenAI/Gemini/Grok). Two-column grid (picker + fixed-width key) so rows align. - BYOK key → header (
ApiKeyField.jsx, new): compact pill, save/clear icons inside the field, info tip- Get-a-key kept. Keyed by provider id → same provider on both rows shares one key (shown once).
- Provider settings → header gear (
ProviderGear.jsx, new) wrapping a now-bareProviderBox(card + collapse removed). Negative-prompt field pulled out of the gear. - Composer Prompt/Negative switch (
Home.jsx): segmented switch on the insert bar (only when the provider supports negatives) flips the editor between prompt and the per-provider negative; inserts/clear/preview follow the active field.DplEditoralready syncs externalvalue, so the swap just works. - Removed
ProviderSelect.jsx; stripped the rewrite select + key fromSettings.jsx(now in the dropdown). Auto-fix / keyword toggles unchanged on the prompt box.
Process notes (rough edges):
- Long, fast feedback loop — many of the messages were live CSS nits (save icon offset, grid reflow, name cutoff, the green tier tag wrapping). Each was a small fix; the AskUserQuestion on the combo grouping was worth it but its answer evolved a message later, so I re-cut the design.
- Deferred: a per-provider preset manager + migrating the old v1–v2 flat presets — flagged as the next scoped piece, not built here (it revives the removed preset system and needs its own design pass).
- Windows visual baselines regenerated locally (
test:e2e:update); the Linux set still needs thevisual-baselines.ymlworkflow before themainrelease CI is green. - Bumped root
VERSION+ engine-v3package.json2.8.0 → 2.9.0 (MINOR).
Keyword tooling + DPL insert toolbar shipped (2.8.0)
Owner asked (in Cowork) for keyword features on the single view + composer, then "ship it … normal procedures including testing." Built the keyword strand, found the composer/single-view files already carried an uncommitted in-flight DPL insert toolbar, and — on the owner's call — shipped both together as one MINOR release.
- Keyword parser
gui/src/lib/keywords.js(new): strips SD/NovelAI weighting + attention syntax, splits on commas/newlines (multi-word tags intact), de-dupes. Display keeps accents; a de-accented lowercased key drives dedupe + gallery search (owner asked whether to keep accents — answer: keep for display, fold only for matching). - Single view
SingleView.jsx:KeywordsCardprefers a savedmeta.keywordslist, else parses; new "Rebuild with AI" button → keyword-mode rewrite → alphabetize → persist to sidecar. Details block became a real<table>. New propssettings+onMetaUpdatethreaded fromApp.jsx. - Sidecar write-back:
POST /api/image/meta(vite-plugin-api.js) shallow-merges a patch into the<base>.jsonsidecar;updateImageMeta()inlib/output.js. GallerysearchHaystacknow includes saved keywords. - Composer
Home.jsx:autoKeywordtoggle (tag icon) beside the wand;makeBatchchains fix → keyword-translate on prompt + negative, independently, cached on the entry. - Rewrite pipeline:
KEYWORD_SYSTEM+systemFor(mode)(_shared/rewriteSystem.js); adapters take asystemoverride;modethreaded throughlib/rewrite.js→ both proxies →dispatchRewrite.
Process notes (rough edges):
- The working tree arrived dirty: the in-flight DPL insert bar (untracked
DplInsertBar.jsx,dplInserts.js+ edits toDplEditor/dplLanguage/promptEngine/ProviderBox/WrapperFab), an untracked fairyfox report, and ~140 files showing as modified that were CRLF-only noise (git diffempty; only ~20 had real content diffs). Staged specific real-diff files only — nevergit add -A; left the CRLF noise alone. - CI on
devHEAD was already red before this work: the Playwright job had 5 failures —home.spec/accessibility.specstill selecting<textarea>(gone since the 2.7.26 CodeMirror switch) and stale Linux visual baselines. Fixed the selectors (.prompt-input .cm-content, click +pressSequentially) and regenerated baselines via thevisual-baselines.ymlworkflow as part of getting the release green. engine-v3/VERSIONdoesn't exist — the canonicalVERSIONis at the repo root (CI reads it). Bumped rootVERSION+package.jsonto 2.8.0.
DPL boxes → CodeMirror editors (feature/dpl-codemirror, 2.7.26)
First of a planned four-branch GUI/DPL UX pass the owner asked for (the others, not yet started:
sidecar category priority + an nsfw sidecar flag; result/gallery polish — preview icon, click-to-copy,
tooltips, gallery thumbnail actions; an online-build variant). Owner chose CodeMirror 6 (over a custom
highlighter) and separate feature/* branches grouped by coupling.
Built a reusable controlled editor and a DPL language for it:
gui/src/components/DplEditor.jsx— a CodeMirror 6EditorViewwrapper behaving like a<textarea>(value+onChange). Echo-safe (tracks the latest value in a ref, only re-dispatches on a real external change) and reconfigures its placeholder via aCompartmentso the prompt box's rotating suggestion still shows without a remount. NocloseBrackets— it would fight the brace-completion (double}).gui/src/lib/dpl/dplLanguage.js— aStreamLanguagetokenizer +HighlightStyle(tag → CSS class) + a brace-aware completion source. Mirrorssrc/core/dpl/dpl.js; line-leading keywords only.getDplCompletions()added topromptEngine.js(flattensgetBlocks()into de-duped token entries).- Wired into the prompt box (
Home.jsx), the negative box (ProviderBox.jsx), and the wrapper Start/End boxes (WrapperFab.jsx). styles.css: a "DPL editor (CodeMirror)" section dressing.cm-*to match the old textareas, the autocomplete dropdown themed to the app, and--dpl-*token colors in:root+ the light override.
Deps added to gui/package.json: @codemirror/{state,view,language,autocomplete,commands} +
@lezer/highlight (12 packages). Recorded in reference/dependencies.md.
Verification: npm run lint 0 errors (18 pre-existing warnings, none in new files); npm test green
(smoke + Node 86 + SPA 43); npm --prefix gui run build green. Not yet done: Playwright visual
baselines shift with the prompt box and need npm run test:e2e:update; branch not yet merged to dev
(awaiting owner go-ahead). VERSION + engine-v3 package.json bumped 2.7.25 → 2.7.26 (PATCH, per the
versioning guide — ordinary feature).
Dynamic-prompt sidecars: priority + nsfw flag (feature/dynprompt-sidecar, 2.7.27)
Second of the four-branch GUI/DPL UX pass (branched off dev, independent of feature/dpl-codemirror).
Two sidecar-driven catalog changes:
- Category
priorityorders the category/folder pills within the Blocks tab (lower = higher, default 1000). RefactoredgetBlocks()(gui/src/lib/promptEngine.js) to assemble category groups (anyGroup/dynCatGroups/specialGroup, each{ priority, name, pill, entries }) and sort them;anyis pinned to 0,specialto 9000. The metadata script writes the curated priorities (prompt 200 … user 700), giving Any · Prompt · Scene · Subject · Style · Fragment · User · Special. Per the owner's clarification, this reorders the pills inside Blocks, not the Blocks/Lists top tabs. nsfwsidecar flag = hard-hide when the NSFW switch is off.getBlocks({ includeAdult })drops flagged generators (and empty categories), and the engine's{#name}stage (src/core/stages/block.js) gates onloader.readBlockMeta(key)?.nsfw === true || isGatedBlock(key)— so flagged generators are unpickable, excluded from{#any}/groups, and resolve to"". The name-token rule still applies; the flag is the no-token escape hatch (empty set today).Home.jsxre-memoizes the palette onsettings.includeAdult.
Touched: scripts/dynprompt-meta/write-dynprompt-meta.mjs (PRIORITY map + NSFW set; sidecar now
{ description, priority?, nsfw? }), regenerated 86 sidecars; block.js (dropped the hasNsfwToken
import for the unified isNsfw); promptEngine.js; Home.jsx. New SPA test asserts the pill order.
Verification: lint 0 errors; npm test green (smoke + Node 86 + SPA 44); gui build green. VERSION +
engine-v3 package.json → 2.7.27 (assumes it lands after 2.7.26). Not merged to dev (awaiting owner
go-ahead). Branches 3–4 (result/gallery polish; online-build) still to come.
Result/gallery polish (feature/gui-result-polish, 2.7.28)
Third of the four-branch GUI/DPL UX pass (off dev, independent of the earlier two). Four small UX changes
the owner asked for:
- Corner preview. Moved the
LivePrevieweye out of the.field-barinto a new.composer-cornercluster pinned to the prompt box's upper-right, styled as a bare icon (.preview-corner). The clear-✕ joined the same cluster (no longer absolutely positioned on its own). - Click-to-copy prompt text.
PromptResult.jsxlost the "copy" button; the prompt text is now click-to-copy via the existing provider-awareonCopy, with the full text shown on hover (title). - DPL hover example. New
DplHoverCodesubcomponent: hovering the source-DPL<code>shows the full DPL plus an example re-rolled every 1s (expandPrompt, fx/artists off — like the chip tooltips).PromptResultnow receivessettings(passed fromHome). - Gallery thumbnail actions. Reworked
Gallery.jsx'sThumbfrom one<button>into a.g-openbutton + a shared.img-actionsoverlay (open / reveal / delete), revealed on.g-cell:hover.App.jsxpasses its existingdeleteItemasonDelete; open/reveal call theoutput.jshelpers directly.
Touched: Home.jsx, PromptResult.jsx, Gallery.jsx, App.jsx, styles.css. No engine/test changes.
Verification: lint 0 errors; smoke OK; SPA suite 43 green; gui build green. VERSION + engine-v3
package.json → 2.7.28 (assumes it lands after 2.7.27). Not merged to dev. Branch 4 (online-build)
still to come.
Online build = stripped Generate-only variant (feature/online-build, 2.7.29)
Fourth and last of the GUI/DPL UX pass (off dev, independent). All in App.jsx, gated on
const ONLINE = import.meta.env.VITE_ONLINE === "true":
- No tabbar online. The
.view-switchheader tabs and the Gallery + Singleview-panes are only rendered when!ONLINE; the view stays "generate".HomegetsonOpenImage={ONLINE ? undefined : …}, so a generated image opens in a new tab (the existing<a target=_blank>) instead of the (absent) single view. - NSFW off, no toggle.
<NsfwToggle>is only rendered when!ONLINE, and the mount effect coercessettings.includeAdulttofalseonline so a stored preference can't leak adult content through. - No browser image storage / feed. The mount effect skips
loadFeed()online (setsloadingItemsfalse); images are shown in-memory only, never persisted.
Verified both build modes: npm --prefix gui run build and VITE_ONLINE=true … build both green; lint 0
errors; SPA suite 43 green; smoke green. VERSION + engine-v3 package.json → 2.7.29 (assumes it lands
after 2.7.28). Not merged to dev.
Pass complete — four feature branches pushed, none merged
All four branches of the UX pass are committed + pushed, each verified, none merged to dev (awaiting
the owner): feature/dpl-codemirror (2.7.26), feature/dynprompt-sidecar (2.7.27),
feature/gui-result-polish (2.7.28), feature/online-build (2.7.29). They fork from dev independently,
so they share trivial doc/VERSION conflicts on merge — intended to land in version order. The Playwright
visual baselines will need the usual Linux refresh after the GUI branches merge.