Newest entry on top.
Performance profiling pass + loader memoization: ~280× faster generation (2.28.18, branch dev)
First full profiling sweep of engine-v3 (core engine · loaders/startup · GUI/Vite build), then applied the one big, safe win and verified.
Method. Backgrounded Node micro-benchmarks from engine-v3/ (timed engine.generate() over
representative prompts; wrapped fs.* with call counters for one generate()); npm run test:perf for
build + bundle.
Findings. A single generate() cost ~56 ms and did 208 readdirSync + 222 readFileSync.
Root cause: nodeLoader.readListLines rebuilt the whole list-name set on every call (≈4 recursive tree
walks per distinct list), and the reserved keyword wildcard (listResolve.js) re-unioned the entire
vocabulary (~48 ms) — and the list store clears its per-prompt cache on reset(), so both recurred every
prompt. browserLoader did the same logical work at SPA runtime (no fs, but rebuilt the name set +
wildcard union per prompt). The GUI build was already healthy (671 modules in ~350 ms; 785 KB gzip within
the 900 KB budget; vendor/prompt-data/Manage already chunked + lazy) — left as-is.
Fix (memoization; no behavior change). nodeLoader caches its directory walks, sorted catalogs, full
name set, and keyed reads (readListLines/meta/group), + a refresh() to drop them; browserLoader
caches allNames() + resolved-line sets; block.js hoists resolvePool out of the per-token
loop. Safe because these loaders only read a static catalog — the Manage tab's live edits go through the
separate runtimeLoader.
Result. generate() ~56 ms → ~0.2 ms (≈280×); per-prompt fs ~430 syscalls → ~0 once warm.
Verify green: lint 0 errors, smoke, Node unit 235, web 237, gui build within budget. New report:
reference/performance.md. PATCH 2.28.17 → 2.28.18.
Text-provider expansion, round 3 (final): Hugging Face + Meta Llama; 18 total (2.28.0, branch dev)
The last two simple-key OpenAI-compatible text providers (config-only, makeChatRewrite in dispatch.js):
Hugging Face (router router.huggingface.co/v1, default meta-llama/Llama-3.3-70B-Instruct) and Meta
Llama API (api.llama.com/compat/v1, Llama-3.3-70B-Instruct). Both hosted-proxy + rewrite: true +
textOnly; best-effort default models. MINOR 2.27.0 → 2.28.0. Verify: node --check, lint 0 errors, smoke,
gui build, test:web 196 — green.
Text providers: 18 — OpenAI, Gemini, Grok, OpenRouter, Groq, DeepSeek, Mistral, Together, Perplexity, Fireworks, Cerebras, Qwen, Moonshot, AI21, Anthropic, Cohere, Hugging Face, Meta Llama. The simple-key / no-fancy-auth list is now complete; remaining (Bedrock/Azure/Vertex/watsonx/NIM = fancy auth; local Ollama/LM Studio = keyless) are out of the owner's criterion.
Text-provider expansion, round 2: 7 more (Text dropdown 9 → 16) (2.27.0, branch dev)
Per the owner's criterion (simple key + CORS/proxy, no fancy auth):
- OpenAI-compatible (config-only,
makeChatRewriteentry indispatch.jsrewriteAdapters): Fireworks (api.fireworks.ai/inference/v1), Cerebras (api.cerebras.ai/v1), Qwen/DashScope (dashscope-intl.aliyuncs.com/compatible-mode/v1), Moonshot/Kimi (api.moonshot.ai/v1), AI21 (api.ai21.com/studio/v1). Allhosted-proxy+rewrite: true+textOnly. - Bespoke (
_shared/bespokeRewrite.js, plain-key but non-OpenAI shape): Anthropic (Messages API,x-api-key+anthropic-version,content[].text) and Cohere (v2/chat, Bearer,message.content[].text). Registered inrewriteAdapters. - All proxied (online-locked, work locally); model defaults are best-effort and may drift. MINOR 2.26.0 → 2.27.0. Verify: node --check, lint 0 errors, smoke, gui build, test:web 196 — green.
- Skipped (fancy auth): Bedrock (SigV4), Azure OpenAI / Vertex (AD / service accounts), watsonx, NIM, Baseten/Anyscale (deployment-specific) — out of scope per the owner. Local OpenAI-compatible servers (Ollama/LM Studio/vLLM) are keyless (no key) — a separate future add (needs settings passed through the rewrite proxy).
Text-provider expansion: OpenAI-compatible rewrite framework + 6 providers (2.26.0, branch dev)
Generalized the text (prompt-rewrite) role so OpenAI-compatible LLM APIs are one-line additions, then added six providers (Text dropdown 3 → 9).
_shared/openaiCompatRewrite.js—makeChatRewrite({ baseUrl, model, label })returns a rewrite fn (POST{baseUrl}/chat/completions, Bearer, the system+user messages, readchoices[0].message). Works in the browser (abrowser-directprovider'sloadRewrite) and server-side (the/api/rewriteproxy'srewriteAdapters) — same plainfetch.- Providers: OpenRouter + Groq are CORS-enabled →
browser-directwith aloadRewrite(online- capable). DeepSeek, Mistral, Together, Perplexity →hosted-proxy+rewrite: true(CORS not assured) with amakeChatRewriteentry indispatch.jsrewriteAdapters; online-locked, work locally. AlltextOnly: true. - Framework:
rewriteProviders()now matchesloadRewrite || rewrite;ProvidersMenuexcludestextOnly(andupscaleOnly) providers from the image picker via a sharednotImageRolefilter, so text-only providers show only in the Text row.metaForalready returns{}for unknown ids (safe). - MINOR 2.25.0 → 2.26.0. Verify: node --check, lint 0 errors, smoke, gui build, test:web 196 — green. The long tail (Fireworks, Cerebras, Qwen, Kimi, local Ollama/LM Studio/vLLM via the same factory) is now a config-only add.
Provider expansion, phase 3f: five async-job enhancers (2.25.0, branch dev)
The owner asked for the six async/job enhancers. Did five; Pixelbin skipped (CDN transform-URL model
— upload-to-storage + sr.upscale() URL with account cloud/zone — doesn't fit the POST-image proxy).
All via the /api/upscale proxy, registered in dispatch.js; best-effort (researched endpoints, can't
live-test):
- WaveSpeed (Real-ESRGAN) — submit
v3/wavespeed-ai/real-esrgan→ pollv3/predictions/{id}/result(submitPoll),outputs[0]. - Claid / Let's Enhance — sync multipart
v1/image/edit/upload(file+dataJSON withrestorations.upscale: smart_enhance) → outputtmp_url. - Deep-Image.ai —
rest_api/process_result(x-api-key, data-URIurl+ decoded 4× dims) → returnsresult_urlor ajobwe poll viarest_api/result/{job}. - neural.love — submit
v1/images/process(quality_enhance4×) → pollv1/images/orders/{id}(submitPoll); request/response shape approximate. - VanceAI — unified jobs API:
POST /v1/jobs→ poll/v1/jobs/{id}→/v1/jobs/{id}/result(manual loop, since the result link is a separate endpoint).
submitPoll runs fine server-side. MINOR 2.24.0 → 2.25.0. Verify: node --check (×5), lint 0 errors,
smoke, gui build, test:web 196 — green. Upscaler set: 17 (4 in-repo, 10 hosted enhancers, 3 local).
The cleanly-integrable hosted enhancer list is now essentially exhausted (remaining are premium/RapidAPI/
URL-model).
Provider expansion, phase 3e: Clipdrop + Venice enhancers (2.24.0, branch dev)
Two more upscale-only enhancers via the proxy pattern (dispatch.js upscaleAdapters):
- Venice AI (
v1/image/upscale, Bearer, JSON base64image+scale1–4 → image bytes; uncensored, no sfw tag). Normalizes both binary and JSON responses to a data URL. - Clipdrop (
image-upscaling/v1/upscale,x-api-key, multipartimage_file+target_width/target_height→ image bytes). Clipdrop needs explicit output dims, so the server decodes the source size from the bytes (PNG IHDR / JPEG SOF) and requests a 4× target capped at 4096. - MINOR 2.23.0 → 2.24.0. Verify: node --check, lint 0 errors, smoke, gui build, test:web 196 — green.
- Upscaler set: 12 providers (4 in-repo, 5 hosted enhancers [DeepAI, Picsart, Segmind, Venice, Clipdrop], 3 local). Remaining hosted enhancers are async/premium/niche best-effort — diminishing returns.
Provider expansion, phase 3d: ComfyUI AI upscale — local set complete (2.23.0, branch dev)
ComfyUI upscaling needs a multipart image upload + a graph run, which the JSON forward proxy can't
do, so it goes through the server-side /api/upscale path (like Replicate):
comfyui/code/upscale-server.js(Node): decode the inlined image →POST /upload/image(multipart) → resolve the upscale model (self-heal against/object_info/UpscaleModelLoader) → submit the minimal graph LoadImage → UpscaleModelLoader → ImageUpscaleWithModel → SaveImage to/prompt→ poll/history→ return the/viewURL (the/api/upscaleendpoint fetches it back to a data URL).comfyui/code/upscale.js(client →callUpscaleProxy, passingcomfyUrl+comfyUpscaleModelin params);config.jsgainscapabilities.upscale+loadUpscale;settings.jsadds acomfyUpscaleModelfield (blank = auto). Registered indispatch.jsupscaleAdapters.- Requires an upscale model in
ComfyUI/models/upscale_models. Best-effort; verify live. - MINOR 2.22.0 → 2.23.0. Verify: node --check, lint 0 errors, smoke, gui build, test:web 196 — green.
- Upscaler set: 10 providers (Stability, fal, Leonardo, Replicate · DeepAI, Picsart, Segmind · Forge, SD.Next, ComfyUI). Local SD upscaler set complete. Next: new generation providers (Krea/Firefly) + remaining best-effort hosted enhancers.
Provider expansion, phase 3c: modernized A1111 adapter + Forge/SD.Next local upscalers (2.22.0, branch dev)
Owner picked "modernize A1111 adapter" (the local-webui generate adapter Forge + SD.Next share was stale).
- Modernized
local-webui/code/generate.js—sampler_index→sampler_name(confirmed via web search: A1111/Forge/SD.Next deprecatedsampler_indexand silently ignore it, which is why the sampler/ seed didn't take), added a separatescheduler("Automatic" — avoids the sampler+scheduler autocorrection bug),batch_size, andsend_images/save_images. Updated the contract test (tests/providers.test.js) to the new shape. - Local Extras upscaler (
local-webui/code/upscale.js, shared) —/sdapi/v1/extra-single-imagewithR-ESRGAN 4x+; the endpoint is independent of txt2img, so it's stable.forge/config.js+sdnext/config.jsgaincapabilities.upscale+loadUpscale. No key → a free local upscaler. App.upscaleImagenow passes the live app settings (+ the upscaler'sproviderParams) instead of the source image's frozen snapshot, so the local WebUI URL is honored for upscaling.- MINOR 2.21.0 → 2.22.0. Verify: node --check, lint 0 errors, smoke, gui build, test:web 196 — green.
- Upscaler set: 4 in-repo + 3 hosted enhancers + 2 local (Forge/SD.Next) = 9 providers. Next: ComfyUI upscale (graph) and/or new generation providers (Krea/Firefly).
Provider expansion: local SD upscalers DEFERRED — stale A1111 adapter (notes only, branch dev)
Started a local Extras-tab upscaler (/sdapi/v1/extra-single-image) shared by Forge + SD.Next, but the
owner flagged that the local-webui A1111 adapter is old and likely no longer works against current
A1111/Forge/SD.Next. Backed out the half-written local-webui/code/upscale.js rather than ship an
upscaler on an untrustworthy foundation (the Forge/SD.Next configs were never touched). Recorded the
deferral in notes/plans/provider-expansion.md: local SD upscalers are blocked on a local-webui
modernization + live re-verify pass (re-check the txt2img contract — sampler_index →
sampler_name, current param names) before adding the Extras upscaler; ComfyUI separately needs a
model-specific graph. Notes-only — no version change.
Where the upscaler set stands: 4 in-repo (Stability, fal, Leonardo, Replicate) + 3 hosted enhancers (DeepAI, Picsart, Segmind) = 7 providers. Remaining hosted enhancers are async/niche/best-effort (diminishing returns); local SD deferred; new generation providers (Krea/Firefly) are the next real-value frontier but each is a researched best-effort module.
Provider expansion, phase 3b: Picsart + Segmind enhancers; Upscaler row locked-online (2.21.0, branch dev)
- Two more enhancers (proxy path,
upscaleOnly): Picsart (tools/1.0/upscale,X-Picsart-API-Key, multipart +upscale_factor, JSONdata.url) and Segmind ESRGAN (v1/esrgan,x-api-key, JSON base64image+scale, returns image bytes → server returns a data URL). Both registered indispatch.jsupscaleAdapters. Verified endpoints/auth via web search; best-effort BYOK. - Upscaler/Enhancer row now locked-online instead of hidden (owner: "nice for the user to see it
exists").
ProviderPickergained alocked/lockReasonprop — a locked trigger shows 🔒 + a tooltip and routes to the full-version link (the tab pattern), no dropdown.ProvidersMenualways renders the row;locked={ONLINE}with reason "upscaling needs the desktop single-image view"; the key field is still local-only. - MINOR 2.20.0 → 2.21.0. Verify: node --check, i18n regen, lint 0 errors, smoke, gui build, test:web 196 — green. Three enhancers now (DeepAI, Picsart, Segmind); more to follow + local SD upscalers + new generation providers.
Provider expansion, phase 3a: upscale-only providers + Upscaler/Enhancer row; DeepAI (2.20.0, branch dev)
New provider category for the ~15 enhancement-only services. Owner confirmed they're upscale-only (no
generation) and — since AI upscale lives only in the local-only single view — the whole feature is
online-locked, so no per-provider CORS research is needed; all route through the local /api/upscale
proxy.
- Framework: a provider may set
upscaleOnly: true(capabilities.upscale + loadUpscale, no loadGenerate).ProvidersMenunow: (a) excludesupscaleOnlyfrom the Image picker groups, and (b) adds a third row, "Upscaler / Enhancer" (below Text) — a picker over every upscale-capable provider (the in-repo ones + the enhancers) + a key field; selection issettings.upscaleProvider. The row is hidden whenONLINE(single view is local-only). Shared-key handling when the chosen upscaler is also the image/text provider. - DeepAI (
providers/deepai/, first upscale-only enhancer):upscaleOnly, routed via the proxy —code/upscale.js(client →callUpscaleProxy) +code/upscale-server.js(multipart POST toapi/torch-srgan,api-keyheader, returnsoutput_url); registered indispatch.jsupscaleAdapters. Verified the endpoint/auth/response via web search. - MINOR 2.19.0 → 2.20.0, ships via
release/2.20.0. Verify: node --check, i18n regen, lint 0 errors, smoke, gui build, test:web 196 — green. - Next: the remaining enhancers (Topaz/Magnific/Claid/Picsart/…) drop in the same shape; then local SD upscalers + new generation providers.
Provider expansion, phase 2c: Leonardo + Replicate upscale — in-repo set complete (2.19.0, branch dev)
- Leonardo (
leonardo/code/upscale.js, browser-direct, best-effort) — the 5-step Universal Upscaler:POST /init-image(presigned S3) → upload the source blob to S3 →POST /variations/universal-upscaler(initImageId,upscaleMultiplier: 2) →submitPollGET /variations/{id}until agenerated_image_variation_genericentry is COMPLETE → fetch result(s) to data URLs (CORS-fallback to the raw URL). Flagged best-effort; verify against live API. - Replicate (proxy path — it's
hosted-proxy, not browser-callable): new/api/upscaledev endpoint inlines the local image as a data URI, callsdispatchUpscale→replicate/code/upscale-server.js(Real-ESRGANnightmareai/real-esrganvia/v1/models/.../predictions+Prefer: wait), then fetches the result server-side and returns data URLs (so the browser never hits Replicate's CORS). New pieces:callUpscaleProxy(hostedProxy.js),replicate/code/upscale.js(client),upscaleAdapters+dispatchUpscale(server/dispatch.js). Online build never calls it (single view is local-only), so no Netlify function needed. - Both configs gain
capabilities.upscale+loadUpscale. The in-repo upscaler set is now complete: Stability + fal (2.18.1/2.18.2) + Leonardo + Replicate (2.19.0). - MINOR 2.18.2 → 2.19.0 (milestone), ships via
release/2.19.0. Verify: node --check (server files), lint 0 errors, smoke, gui build, test:web 196 — green. - Next per
notes/plans/provider-expansion.md: local SD upscalers (ComfyUI/forge/sdnext Extras), then new generation + hosted upscale-only providers.
Provider expansion, phase 2b: fal.ai upscale adapter (2.18.2, branch dev)
Second loadUpscale adapter. fal/code/upscale.js runs fal-ai/esrgan (Real-ESRGAN ~4×) via the
synchronous fal.run endpoint (Authorization: Key …), verified the input (image_url + scale) and
output (image.url) schema via web search. Key save-model detail: the input image is inlined as a
data: URI (fal can't reach a localhost /api/output path) and the fal-CDN output is fetched back
into a data: URL so the /api/image ingest (data/localhost only, for SSRF safety) persists it as a
tracked Resizes child. fal/config.js gains capabilities.upscale + loadUpscale.
- PATCH 2.18.1 → 2.18.2, ships
dev → maindirect. Verify: lint 0 errors, smoke, gui build, test:web 196 — green. - Leonardo (TODO) is heavier —
POST /init-imagepresigned S3 upload →variations/universal-upscaler(upscaleMultiplier≤ 2) → poll. Replicate (TODO) ishosted-proxy, so it needs an upscale action routed through the proxy dispatch (browser can't call Replicate; CORS). Both flagged innotes/plans/provider-expansion.md.
Provider expansion, phase 2a: Stability AI upscale adapter (2.18.1, branch dev)
First real loadUpscale adapter, proving the phase-1 framework end-to-end. stability/code/upscale.js
posts the source image (read from its served URL → blob) as multipart to the v2beta fast upscaler
(stable-image/upscale/fast, synchronous ~4×, base64 back) — same auth/multipart shape as the existing
generate adapter (verified the endpoint + image field via web search). stability/config.js gains
capabilities.upscale: true + loadUpscale, so SingleView's resize → AI group now lists AI
Upscale · Stability AI (key-gated) and App.upscaleImage runs it into the Resizes strip.
- PATCH (small feature) → 2.18.0 → 2.18.1, ships
dev → maindirect. - Verify: lint 0 errors, smoke, gui build (browser glob picks up the new adapter), test:web 196 — green.
- Next: fal (Topaz), Replicate (Real-ESRGAN, async poll), Leonardo (Universal Upscaler), then local
engines + the new hosted services per
notes/plans/provider-expansion.md.
Deploy fixes: re-enable GitHub Pages (engine-v3 doc-build straddle) + Netlify continuous deploy (branch dev)
After the 2.18.0 release the owner noted the Pages docs and the Netlify app weren't updating. Both root causes were infra, not the release:
- Pages —
pages.yml'spush:[main]trigger had been paused (onlyworkflow_dispatch) becausenpm run docsbroke in the engine-v3 split. Fixedscripts/build-docs.mjsto straddle two roots:root= engine-v3 (codesrc/+data/, transpiled-JSXtmp/, the pinned jsdoc binary) andrepoRoot= its parent (notes/,assets/,README.md,list-credits.md,jsdoc.config.json). JSDoc now runs fromrepoRootvianode engine-v3/node_modules/jsdoc/jsdoc.js(notnpx jsdoc, which fetched a different version);jsdoc.config.jsonsource.includereaches intoengine-v3/srcetc.; the site writes to repo-rootdocs/jsdoc(sopages.ymluploadsdocs/jsdoc). JSDoc exits 1 on recoverable TS-style type-expression warnings yet still writes all 594 files — the build now tolerates that whenindex.htmllanded. Re-enabled thepush:[main]trigger. GitHub Pages source was alreadybuild_type: workflow(Actions).npm run docsis green (exit 0). - Netlify — the site (
prompt-fairyfox, id927e1b3b-…,prompt.fairyfox.io) is not git-connected (manual-CLI history), so pushes never rebuilt it. Added.github/workflows/netlify-deploy.yml— on push tomainit runsnetlify deploy --build --prod, gated on aNETLIFY_AUTH_TOKENsecret (safe no-op until the owner adds it; site id is public in the workflow env). Also triggered a manual prod deploy to refresh the live site to 2.18.0 now. - Updated
notes/reference/deployment.md(Pages straddle, Netlify continuous-deploy, "Not done yet"). - CI/infra only — no version bump. Ships
dev → main(release.yml no-ops since VERSION is unchanged; the main push now fires pages.yml + netlify-deploy.yml).
Provider expansion, phase 1: AI-upscale framework + NSFW content-policy soft-lock (2.18.0, branch feature/single-derive-ancestry)
Owner pushed to support many more providers (incl. AI upscalers — I'd wrongly assumed none offered it) and
to soft-gate SFW-only providers in NSFW mode. Phase 1 lays the reusable backbone (fully internal, no
external API guessing); per-provider integrations are scoped in notes/plans/provider-expansion.md.
- AI-upscale contract —
capabilities.upscale+loadUpscale()adapter.App.upscaleImage(item, providerId)loads the adapter, runs{ image, key, settings }, andingestImages the result as aderivedKind: "resize"child (Resizes strip + live placeholder, like a derive).SingleViewbuilds the resize menu's AI group fromproviders.filter(p => p.capabilities.upscale && p.loadUpscale), key-gating each. Dormant until a provider ships an adapter (then it lights up automatically). - NSFW soft-lock (
lib/contentPolicy.js) —contentPolicy: "sfw-only"onopenai/gemini/ideogram/stability.softLockedForNsfw(provider, includeAdult)drives: a neutral 🔒 + tooltip on the Providers picker option (ProviderPickersoftLock, still selectable), and a one-timeconfirm("NSFW mode is on — proceed?") before generate (Home.buildPrompts) / derive / upscale (App). Never hard-blocks, never states a restriction — per the owner's explicit instruction. - Roadmap —
notes/plans/provider-expansion.md: capability assessment (text/image/upscale) of the owner's provider list + phase order (in-repo upscale → new generation providers → hosted upscale-only → local upscalers), with "verify endpoints against current docs" flagged throughout. - Verify: i18n regen, lint 0 errors, smoke, gui build, test:web 196, test:unit 226 — all green. Bump 2.17.0 → 2.18.0. Not released.
Single view, round 2: inline layer actions, live strips, resize, View Raw, uniform locks (2.17.0, branch feature/single-derive-ancestry)
Reworked the single-view actions per owner feedback on the first cut.
- Inline actions on the prompt layer rows (replacing the separate "Make another" card): the DPL-source
row gets a re-roll + vary link (styled like the copy link); Sent to model and Translated
each get a vary link. Both ask for confirmation (
window.confirm).derive.jsgained afinalsource (vary the sent text verbatim); sources are nowdpl/final/ai/roll. - No page change on derive (better UX) — App no longer navigates to a pending image. Instead a new
derivationslist ({id, parentPath, kind}) drives live spinner placeholders in the matching strip; on completionrefreshAndResyncreloads the feed and re-syncs the open image in place. - Two→three strips below the image (
DerivedStrips): Re-Rolls, Variations, Resizes, each showing that kind's children (clickable) + live placeholders. Moved out of the meta column; a compactLineageHead(type + parent link) stays on the right. - Resize (upsize/downsize) — new
POST /api/image/resize(ImageMagick-resize N%) writes a tracked child (derivedKind: "resize", ancestry like a re-roll).derive.js#resizeImage+RESIZE_SCALES(¼× ½× 2× 4×). An AI Upscale entry sits in the same menu, gated oncapabilities.upscale(no provider declares it yet, so it shows locked — see the next entry / follow-up to actually wire AI upscalers). - Uniform locks — the Convert and Resize menus are no longer hidden when ImageMagick is missing; they're shown greyed with a 🔒 + tooltip (matching the online-locked tabs) so users can discover the feature.
- Details panel — a View Raw toggle flips the table to a syntax-highlighted raw-JSON view
(
syntaxHighlightJson); Copy as Markdown + Copy as JSON buttons (the old bottom raw<details>is gone). - Verify: i18n regen, lint 0 errors, smoke, gui build, test:web 196, test:unit 226 — all green. Bump 2.16.0 → 2.17.0. Not released. Follow-up requested: wire real AI upscale providers (Stability/fal/Replicate/ Leonardo already in-repo; Topaz/Magnific/Claid/etc. as new providers), NSFW-gated where applicable.
Single view: re-roll / variation + tracked ancestry, Markdown export, table fix (2.16.0, branch feature/single-derive-ancestry)
Ported the v1-2 single-image actions to the v3 SPA and tidied the details table, per an owner request ("variety + re-roll button, look at how v1-2 did it; tracked ancestry; copy-as-markdown; fix the table — OpenAI images carry foreign ComfyUI metadata").
lib/derive.js(new) — the v3 port of v1-2'sloadRerollData.deriveImage({item, kind, source})resolves a prompt from a captured layer (dplre-resolves the recipe throughgeneratePrompt;ai/rollreuse the verbatim text), reuses the parent's provider + settings snapshot, forces a fresh seed (v1-2 deliberately never copied the seed), runs the provider adapter, and ingests a child sidecar carryingparent+derivedKind/derivedSource. Gated bycanDerive(providercapabilities.seed) andhasSource. Seedless providers (OpenAI) lock the cluster with a tooltip rather than failing.- Ancestry, scan-built (
gallery.js#linkAncestry) — exactly v1-2'sindexImages.deepLinkidea: each child names itsparent, and the feed scan hangs it off that parent'schildrenlist (self-healing — a child whose parent is gone is just unlinked). Wired intoApp.loadFeed. New tests ingallery.test.js(+3). SingleView.jsx— a "Make another" cluster (Re-roll = DPL; Variation = DPL / Translated / Original, each enabled only when that layer exists); a pending placeholder (spinner + the resolved prompt) while the new image generates, since a derive opens a fresh single-image target (App holds apendingcurrent); a Lineage card (type label + parent link + child-thumbnail strip); a Copy as Markdown button on the Details card; and a capability-gated table — rows for sampler/steps/cfg/seed are hidden when the provider declares themfalse, andREST_DROPstrips app-orchestration keys from "All settings".App.deriveImageorchestrates: optimisticpendingcurrent → run derive (prompt fills in first) →loadFeed(re-links ancestry) → land on the new child; errors revert to the source image.- Snapshot leak fixed at the source (
Home.jsx#cleanSnapshot) — sidecars now store a provider-scoped settings snapshot (app keys,providerParams, nested objects, empties dropped), so a future OpenAI image no longer carries another provider's sampler/steps. The table's capability gating covers already-saved ones. - Verify:
npm run i18n(regenerateden.json+ pseudo), lint (0 errors), smoke,guibuild (browser glob),test:web196,test:unit226 — all green. Onfeature/single-derive-ancestry; not released.
Finish SPA localization — the two deferred DPL-technical modules (2.15.0, branch feature/i18n-react-intl)
Closed out the one gap left from the 2026-06-28 i18n pass: the two DPL-technical lib modules that were shipped English-only are now localized, so SPA coverage is complete (~480 messages).
validateDpl.js— the editor lint/status diagnostics. Localized withdefineMessages+ react-intl, but as a non-React isomorphic module it takes an optionalintlarg:validateDpl(text, intl?). When none is passed it uses a module-levelcreateIntl({locale:"en"})fallback, so the existing message-asserting tests (tests/validateDpl.test.js, 9) and any non-React caller get byte-identical English with no changes. Literal braces ({list},{#generator},'{','}') are passed as message arguments so ICU substitutes them verbatim instead of trying to parse them as placeholders — the rendered English still matches the tests' substring assertions exactly.dplInserts.js— the DPL insert teaching catalog. Converted from a staticDPL_INSERTSarray to agetDplInserts(intl)builder:label/hint/desclocalized;syntax/template/examplestay literal DPL. The one brace-bearing description (call→{#name}) passes the token as an arg.- Call sites threaded
intlthrough:DplStatus(already haduseIntl) now callsdplStatus(value, intl); theDplEditorCodeMirror linter became amakeDplLinter(intlRef)factory reading a liveintlRef(the editor is built once on mount, so a ref keeps the locale current);DplInsertBarmemoizesgetDplInserts(intl).
Verification (all green): npm run i18n (extract + pseudo) → 480 messages, en-XA regenerated;
npm --prefix gui run build; npm test at engine-v3 root — lint (0 errors, 18 pre-existing warnings),
smoke OK, test:unit 128, test:web 60 (incl. the 9 validateDpl tests unchanged); npm run lint:i18n
clean. Still 2.15.0 (same feature) — notes/changelog/status/decisions updated to mark the modules done.
Not yet released — awaiting the owner's go-ahead to merge feature/i18n-react-intl to main.