Tutorial: 2026-06-21

2026-06-21

2026-06-21

Newest entry on top.

DPL Phase 4 polish 3 — "Blocks", global any/any-ver, default wrapper (CODE)

Owner: rename the group "Blocks" (not Prompts); make any version-locked + add a global any-ver shown under "any" on every version (no version prefix — it's global); and build a sensible default wrapper from common v2 start/end framing, applied when nothing else is loaded.

  • Engine (dynamicPrompt.js): added {#any-ver} (+-sfw/-nsfw) — picks one generator across ALL generations; {#any} stays the current-version (v3) wildcard. Both unprefixed/global.
  • SPA (promptEngine.js): the "any" wildcard is now one GLOBAL set (any + any-ver, no {#v2/any}), shown under the "any" category on every version tab. Renamed the v3 block/group to Blocks (Home.jsx head + block title). renderWrapperPart() added so wrapper boxes are real DPL (bullets/probability) rendered to tokens before generation.
  • Default wrapper (wrapperStore.js DEFAULT_WRAPPER): START masterpiece, best quality, highly detailed; END (DPL) {#fx}, {#artists} + 50% intricate detail/sharp focus/wide shot/<rays> + 35% <dap> — distilled from the most common v2 endings (intricate ×16, highly detailed ×6, dap ×5, sharp focus ×5, wide shot ×5, + auto fx/artists). Home.buildPrompts applies settings.wrapper ?? DEFAULT_WRAPPER, rendering the wrapper per prompt. WrapperFab popover gained a built-in Default entry (active when nothing chosen) alongside None.
  • Verified: lint clean, smoke OK, dpl-engine-check ({#any}/{#any-ver}) OK, web build 597 modules.

DPL Phase 4 polish 2 — v3 drops full/partial; prefix in button labels (CODE)

Owner: v3 doesn't use full/partial anymore — remove type: from the v3 .dpl files; v3's submenu under "Prompts" is just Prompts (one list); and the v1/v2 chip labels must show the root prefix (the syntax you'd type).

  • Stripped type: full/type: partial from all 80 v3 .dpl files (PowerShell, LF/no-BOM). v3 generators now carry only description / suggestions / script.
  • Classifier (promptFilesAndSuggestions.js): v3 is one pool — every active generator is a "prompt" (whole set is the suggestion pool minus suggestions: off); partialRegular stays empty. v1/v2 keep their full flag (frozen, still split).
  • SPA (promptEngine.js): three dynVersioned blocks — Prompts (v3 only, all generators, one tab), Full prompts / Partial prompts (v2/v1 only). The navbar shows v3 → one "Prompts" sub-tab; v2 → Full
    • Partial; v1 → Full. Chip + group labels now show the token's inner text — bare for v3 (cave, scene), prefixed for v1/v2 (v2/cave, v2/scene) — so the button reflects exactly what gets typed.
  • Verified: lint clean, smoke OK, dpl-engine-check OK, web build 597 modules.

DPL Phase 4 polish — first-class v1/v2, prefix tokens, wrapper-in-field-bar (CODE)

Owner feedback: "to access a non-default generation, root-prefix with v#/ then the normal token; the buttons must reflect this. v1/v2 are frozen but still first class (categories, partials, the same system). Put the wrapper as an icon-only button in the prompt-box field bar, not a floating FAB."

  • Engine made generation-generic (core/stages/dynamicPrompt.js): one expandGen(name, tag) handles v3 (tag "") and the frozen v1/v2 identically — suffix resolution, implied folder groups, and the {#any} wildcard all work per generation ({#scene}, {#v2/scene}, {#any}, {#v2/any}, {#v1/castle}, {#v2/cave} verified). Frozen generations only differ in being prefix-addressed + forcing fx/artists off.
  • Loaders gained dynPromptGroupDirsAll() + dynPromptForcedPrefixDirsAll() (all generations; the engine/UI filter by prefix) in both nodeLoader and browserLoader.
  • SPA building blocks (promptEngine.js) rebuilt generation-aware: v1/v2/v3 each browse the same way — categories, full/partial, {#any} — with tokens {#<short>} for v3 and {#v<n>/<short>} for v1/v2 (short = computeButtonNames, not full paths). Fixes the missing v2 partials/categories and the broken -v1 tokens.
  • Wrapper moved into the prompt-box field bar as an icon-only .field-act button (popover anchored above the bar); removed the floating FAB (Home.jsx, App.jsx, WrapperFab.jsx, styles.css).
  • Verified: 25 DPL tests, smoke, dpl-engine-check (incl. group + wildcard per generation), web build (597 modules) — all green.

DPL Phase 4 — wrapper UI + SPA v3 building blocks (CODE)

Built the "wrapper" UI and reconciled the SPA building-block panel with the v3-default engine.

  • Wrapper (owner spec): a START + END pair that frames every prompt (v3 root layer = open + middle + close). New web-app/src/lib/wrapperStore.js (localStorage presets {name:{start,end}}) and components/WrapperFab.jsx — a bottom-right floating button whose popover lists saved wrapper presets (apply one / None) with a Manage presets button that opens a modal: preset naming/management above, the two side-by-side Start/End editors below. Selected wrapper lives in settings.wrapper (shared via the share-link); Home.buildPrompts frames generation as start, prompt, end. Styled to match the app (tokens, pill button, popover, modal) in styles.css.
  • SPA building blocks updated for Phase 3: v3 is the default set (bare {#name}); the navbar superset toggle is now v3 / v2 / v1 (v3 default); frozen v1/v2 chips emit path-prefix tokens ({#v1/castle}, {#v2/scene/cave}) instead of the removed -v1 suffix (promptEngine.js, Home.jsx).
  • Verified: npm test (lint 0 errors, smoke green, v3 generators active in the suggestion); npm --prefix web-app run build green (597 modules). Start/end UX open question in v3-layers.md resolved.

DPL Phase 3 — loader/engine integration: v3 is the default catalog (CODE)

Wired v3 .dpl (+ JS sidecars) into the live engine, both loaders, the dynamic-prompt stage, and the classifier. Owner calls during this phase: v3 is the default (bare {#name}); v1 and v2 are frozen and addressed only by their path prefix {#v1/…} / {#v2/…} (the new folder/path system — no -v1/-v2 suffix); and v1/v2 must stay fully functional without code changes (kept on disk, loadable).

  • core/dpl/dpl.js — the parser/renderer (Phase 1) compiles .dpl → the {default, full, suggestion_exclude} module shape.
  • core/nodeLoader.jsloadDynamicPrompt compiles .dpl (cached) with a JS-sidecar bridge (script: / {js:} / insert js:, relative or root-absolute paths) and still requires .js for v1/v2; dynamicPromptNames lists v1+v2+v3 (a .js is a generator only without a same-name .dpl); group/force-prefix dirs scoped to v3.
  • core/browserLoader.js — mirror via Vite: ?raw glob for .dpl, sidecar bridge resolving modules from the bundle by joined key; v2 included; groups/force-prefix scoped to v3.
  • core/stages/dynamicPrompt.js — three-way split: bare → v3 (expandActive), {#v1/…} / {#v2/…}expandFrozen (prefix strip + suffix resolve within that generation, autoAdd off). {#any}/groups over v3.
  • promptFilesAndSuggestions.js — buckets v1/v2 as frozen (v1Files/v2Files, full-path tokens, out of the suggestion pool), v3/user → userFiles, the rest → active v3 pools.
  • Verified: npm run smoke green; new scripts/dpl-engine-check.mjs expands {#cave}, {#v3/scene/cave}, {#v1/castle}, {#v2/scene/cave}, {#v2/cave}, sidecar-backed {#vibrant-art}/{#space}/{#entity}/ {#random-words} end-to-end with lists resolving and no leftover tokens; npm --prefix web-app run build green (595 modules, .dpl bundled); lint clean. Docs: reference/prompt-dsl.md updated to v3-default + path-prefix addressing; next-steps 7a added.

DPL Phase 2 — COMPLETE: style + prompt + user converted (CODE)

  • style/ (19): pure .dpl for the publicprompts templates (3d-*, comic, fluffy-animal, funko-3d-print, gold-pendant, lowpoly, needle-felt, plushie, psychedelic, space-hologram, sports-logo, sticker, anime-irl, retro-poster — autoAdd=false dropped); .js sidecars for silhouette (sentence interpolation) and vibrant-art (space-joined colorful()).
  • prompt/ (builtins, all .js sidecars): random-words, random, simple-random, extra-random, artists, d (+ _force-prefix marker for {#prompt/…} parity); fx is the one pure-.dpl partial.
  • user/ beach-merk → pure .dpl (its direct-import composition became {#city}/{#nature} tokens; the partial-case switch became one of (60% nothing)).
  • Phase 2 done: the entire v2 catalog is mirrored in data/dynamic-prompts/v3/; v1 + v2 kept frozen. Lint clean, 25 engine tests green, all v3 .dpl render via dpl-validate.mjs. Next: Phase 3 (wire the node + browser loaders so .dpl/sidecars actually load in the live engine).

DPL Phase 2 — subject category converted (CODE)

  • Converted subject/ (12 generators, 17 files). .dpl+.js sidecars for the entity type-system: entity.js (core polymorphic picker, ported from v2) + thin wrappers animal/person/living-entity/ entity-name that call entity(…, kind/nameOnly). Pure .dpl: wildlife, portrait, portrait-animal, portrait-person, portrait-princess, knight, and furry (its per-item glow/colour clothing became a one of nested inside a repeat 0 to 5 times block — a nice proof the language covers the count+choice combo). Lint clean, all render.

DPL Phase 2 — scene category converted (CODE)

  • Converted the whole scene/ category to v3 (29 files). Pure .dpl: beach, castle, city, cave, house, landscape, log-cabin, mountains, micro-city, micro-landscape, park, room, ruins, school-room, ship, store-interior, storefront, underwaterscape, vehicle, zoo, settlement. .dpl+.js sidecars for the logic/space-join cases: futuristic (dedup flags + else-if), great-bridge + great-tree (size() helper + inline interpolation), space (maybeAddSize + switches), spaceship (space-joined adjectives). Faithful to v2 (one-of-with-miss for weather-fx, maybe/otherwise condition blocks, hand-emphasis ((x))/[x] passthrough).
  • All render via dpl-validate.mjs; sidecar .js pass node --check; lint clean (one ported no-dupe-else-if false-positive marked intentional — _.random() re-rolls).

DPL Phase 2 — bullet-default fix + fragment category converted (CODE)

  • Fidelity fix in dpl.js: a bare simple-clause bullet now defaults to 50% (was rendering as always-on). Structural bullets (one of/repeat/maybe/otherwise/block) stay unconditional; plain (non-bullet) lines stay always-on; otherwise pairs only with an authored gate, not a defaulted 50%. Caught via the cave/vehicle validate output emitting every clause. Added tests (default-50%, case-sensitive ref) — 25 checks green.
  • Converted the whole fragment/ category to .dpl (color, eerie, mystical, water, lava, crystal, glow, neon, nature, underwater, expressive, ice, general-state, room-state; weather already done). Spot-checked outputs match v2 (color's 50%-nothing split, nature's gated repeat loops, water's gated one-of-with-miss, glow/neon pick-one, maybe/otherwise condition blocks). scripts/dpl-validate.mjs renders all green.

DPL Phase 1 — engine built + verified, v3 samples converted (CODE)

First real code. Owner: "implement it; make a v3 folder; convert v2; later the prompt box uses DPL by default and 3 wrapper-preset boxes." Phased to protect the working app — Phase 1 = engine + samples (non-breaking).

  • src/core/dpl/dpl.js — DPL parser + weighted-layer renderer. compileDpl(source, bridge){ default, full, suggestion_exclude } (the existing generator-module shape, so engine/loader untouched). Supports: front-matter (type/description/suggestions/script), =×≥3 headings, first-indent unit, plain (always) vs - bullet (50% default), [n] weights (auto from 1000, local recursive sort), gates (NN%/maybe/NN% chance/otherwise chain), one of + N of/A to B of (digits) with (NN% nothing) + weighted options, repeat N times/A to B times (gate-first vs per-iteration), +call/insert/go to/go back, and the JS bridge (script: / {js:path} / insert js: + ctx section/prompt/list/expand). Section lookup is case-sensitive; counts are digits only (owner calls).
  • scripts/dpl-test.mjs — 22 checks (weights, gates, choice/multi, miss, repeat modes, maybe/otherwise, local call, case-sensitivity, front-matter, JS bridge). Green. scripts/dpl-validate.mjs renders the real v3 files.
  • data/dynamic-prompts/v3/ — converted fragment/weather, prompt/fx, scene/cave, scene/vehicle to .dpl; all render correctly (+cave-type/+weather-fx resolve, picks/gates/repeats work).
  • Lint clean, prettier-formatted. Not yet wired into the loaders/engine (Phase 3) — purely additive so far.

v3/DPL — weight syntax + build order settled (no code)

Owner decisions: auto weights start at 1000 (+1 per line); explicit weight = [n] at the start of a line/block (e.g. [900]) — recommended over @900, safe because A1111 [..]/salt [..] only appear inside payload text; a section/ref carries a weight (include-site > section-declared > auto); and DPL is built first because it's wired into the v3 engine (start/end UX waits on it).

  • reference/dpl-design.md: added a "Weights (v3 layers)" subsection (auto-1000, [n] syntax, section/ref weights + precedence, marker rationale) and updated the grammar sketch.
  • plans/v3-layers.md: moved auto-start/[n] syntax into Resolved mechanics, added a Build-order section (DPL first), trimmed the open questions to start/end UX + read-only vars + auto-weight collisions.
  • plans/next-steps.md: item 7 (DPL) reframed as the active next build wired into v3; item 8 updated.

v3 — weighted-layer engine concept captured (exploratory, no code)

Owner explained the v3 direction (the model the DPL authors for): v1/v2 build an ordered string (position = meaning); v3 orders by weight over a layer tree. Every file/section/line is a layer; the user's prompt box is the root and each building block a child. Weights are plain sort numbers (not A1111 attention) — lower = rendered closer to the front; a line auto-gets the next number unless an explicit weight (syntax TBD) leads the line/block. Files have no weight (modularity); their inner sections/lines do. Render = recursive depth-first, each layer sorts its own children (words never leave their layer; no global scale; ties = document order). The engine does no de-duplication — that's a non-goal. Big goal: retire "full prompts" so regular blocks stop auto-emitting start/end framing (the real v2 duplication source) — the engine supplies start/end blocks (leaning presets over a settings page; maybe 3 boxes), so building blocks are mostly "middle". Building blocks read read-only variables, never define them; wants deep engine↔blocks↔user integration (mostly future).

  • Wrote exploratory ../../plans/v3-layers.md (model, retiring full prompts, read-only vars, DPL's role, and the open mechanics: weight scope, sort key, tie-break, start/end UX, weight syntax). Linked from dpl-design.md scope note and next-steps item 8. No source changed.

DPL — design revision 3: two-way JS↔DPL bridge (no code)

Owner: JS must also be able to grab/call DPL sections from code (a section executes and returns its string), and JS can hand control to the DPL side which returns results — symmetric, recursable.

  • Updated ../../reference/dpl-design.md §6 (renamed "The JavaScript bridge (two-way)"): split DPL→JS (script: / {js:path} / insert js: path) from JS→DPL via a ctx object — ctx.section(name) (run a local section), ctx.prompt("#name") (run another generator), ctx.list(name) (pull a list value), ctx.expand(snippet) (run an inline DPL/token string) — all returning the rendered string, plus read-only ctx.settings/ctx.random. Added a worked detail-stack.js example, a coverage-table row, and refined open questions (bridge surface + recursion limits, local-section vs global-prompt resolution). No source changed.

DPL — design revision 2: data-not-code, JS holds all logic (no code)

Owner sharpened the boundary: no in-language programming — remove counters, flags, variables, arguments, and counter-driven loops; all of that goes to referenced JavaScript (relative to the .dpl, or root-absolute from project root with a leading /). JS reference is not only inline calls but also block insertions. Flow is linear top-to-bottom: go to jumps and naturally falls downward, with go back as the return. one of may pick more than one (two of:, 1 to 3 of:). Indentation unit = the first indent in the file (tab or N spaces, then consistent).

  • Rewrote ../../reference/dpl-design.md: reframed the language as "data, not code"; deleted the counters/flags section and all variable/argument/repeat while constructs; added the three JS forms — script: (whole file), {js:path} (inline value), insert js: path (block insertion) — with relative/root-absolute path resolution; rewrote flow around go to / go back / +name (inline call) / insert name (block), with the heading-boundary fall-through rule; added N of / A to B of multi-select; added the first-indent indentation rule; moved the whole entity family back to JS (script: entity.js). Updated the requirements table, grammar sketch, and open questions. No source changed.

DPL — design revision: control flow, gating, repetition (no code)

Owner feedback on the proposal: drop auto-fx/auto-artists (v3 will differ) and the adult front-matter key (NSFW inferred by name only, to avoid mixups); section underline is ≥3 = (not exactly 4); no start/middle/end framing (that's v2 — v3 differs) but keep Start as the entry section; expand branching (different branches, jumps, loops, counters, probabilities); handle gating with simple syntax options; and fix repetition so it isn't always "gate-then-repeat" — allow ungated loops and per-iteration probabilities.

  • Rewrote ../../reference/dpl-design.md: removed the three front-matter keys; set the heading underline to =×≥3; reframed lines as plain (always) vs bullet (50% default) in document order with no enforced anchor/tail. Added a gating cheat-sheet (NN%, maybe:/otherwise:/NN% chance: blocks, chained otherwise NN% chance:), explicit repetition modes (repeat N times ungated; NN% repeat A to B times gate-first; repeat N times: NN% … per-iteration; repeat while COUNTER > 0 with a safety cap), counters & flags (set/clear/add/subtract, → set flag on a chosen option, when/unless guards), and branching/jumps (+name call vs go to name jump, branch: weighted jump, conditional jumps). Showed entity rewritten in pure Tier-1 using the state model, updated the requirements-coverage table and grammar sketch. Added a v3 clarifier to dpl-language.md. No source changed.

DPL — expanded language proposal (no code)

Follow-up to the mockup analysis. Owner: "understand the comprehensive needs of the v2 generators and propose a much more complete language; keep it Markdown-readable and usable by non-programmers; referencing JavaScript inside it (separate file/functions) is fine."

  • Read a broad structural sample of v2 generators — subject/{entity,animal,portrait,portrait-person,knight}, fragment/{color,general-state,weather,expressive,fx}, prompt/{random-words,random,d}, style/vibrant-art, scene/{city,vehicle}, user/beach-merk — plus helpers/keywordRepeater.js. Extracted the full pattern set: weighted/empty choices, grouped if/else blocks, count-loops, flags set-on-pick read later (entity), parameters + filtered pools (entity), settings read/write directives (auto-fx off), space-joined sub-builders (colorful), direct-import composition (beach-merk), and helper builtins.
  • Designed a two-tier language: Tier 1 is pure Markdown (YAML front-matter for metadata/directives, setext headings for sections, - bullet clauses with NN%, maybe:/otherwise: indented blocks, one of (NN% nothing):, N to M of: repetition, unchanged {list}/{#prompt}/<exp> tokens) — proven to cover the large majority of the catalog with worked rewrites. Tier 2 is a JS escape hatch (script: whole-file delegation, {js:fn} inline value) for the logic-heavy ~10% (entity, random-words, random, d, vibrant-art's colorful). Compiles to the existing (settings,…) => string contract so the engine is untouched.
  • Wrote ../../reference/dpl-design.md: goals, the Tier-1 spec, real-generator rewrites, the Tier-2 hatch, a requirements-coverage table (every v2 pattern → exemplar file → coverage), a grammar sketch, and open questions. Cross-linked from reference/dpl-language.md; added next-steps item 7. No source changed.

DPL (Dynamic Prompt Language) — mockup analysis (no code)

Owner: "begin making the DPL; two revisions are proposed in assets/mockup but largely incomplete. Analyze and fully understand both mockups, write up the understanding in notes, and analyze what makes a full vs partial prompt in the v2 JS (start/middle/end layering + balancing)."

  • Read both mockups against the generators they encode: mockup-of-dpl-language.txt = v2/scene/beach.js (winter/tropical via *fail chains + named sections), mockup-of-dpl-language2.txt = v2/scene/cave.js (rev 2 adds the select N name: pick-one group). Confirmed the grammar: ==== ==== banners, =- key: value + =- full prompt metadata, name:/start: sections, plain base line (START), */*NN%/*fail optional clauses (MIDDLE), +name subroutine call, #name embed, {name} list, (…) weight group, and the trailing bare line (END). These map 1:1 to the JS _.random()<p accretion idiom.
  • Documented the full vs partial model: full = START anchor + balanced MIDDLE body (independent ~50% coin-flips, some weighted, *fail/select for mutual exclusion) + unconditional END context line, then the engine auto-appends {#fx}/{#artists}. Partial = empty base, all-MIDDLE ", frag" clauses, no END, full flag absent. =- full promptexport const full = true, read by promptFilesAndSuggestions.js.
  • Wrote it all up in new ../../reference/dpl-language.md (incl. a grammar table, the full/partial structural breakdown, and an open-questions list for the parser/compiler design); cross-linked from reference/dynamic-prompts.md and reference/prompt-dsl.md. No source changed.

prompt/ category rename + "Prompts" navbar grouping (2.5.0)

Owner: "remove force group entry on user; engine should be called prompt and force-prefix; within it danbooru→d, drop -prompt, and bare random→random-words; for the category, have Prompts with a single v1/v2, then full/partial below; reverse v1/v2 order; better category descriptions (no group mechanics)." Also confirmed v1 has no partials (all v1 generators are full).

  • Renames. git mv v2/engine v2/prompt; within: randomrandom-words, then random-promptrandom (order matters to avoid collision), simple-random-promptsimple-random, extra-random-promptextra-random, danboorud; added a v2/prompt/_force-prefix marker. No sibling/relative breakage except extra-random.js's ./random-prompt.js import (→ ./random.js), caught by the web build. Regenerated the 121 sidecars (updated meta D map + better, group-free category descriptions). dynPromptTags key updated to v2/prompt/d.
  • Default. settings.prompt / engine fallback / SPA defaults repointed {#random}{#random-words} so the default still produces the keyword pile (now that bare {#random} resolves to the composite).
  • Reverted user force-group. Removed v2/user/_enable-group-list; {#user} is not a group (auto-rule: the 1-file folder doesn't qualify).
  • Navbar. getBlocks tags the two dynamic blocks with subLabel full/partial; Home.jsx renders a single "Prompts" group head with one v1/v2 switch (order v1 v2, v2 default) and full/partial sub-tabs, with Expansions/Lists/Special as normal tabs.
  • Verified {#prompt/random}, {#prompt/d}, {#d}, {#random-words}, {#prompt} group, default gen; lint 0 errors, smoke green, web build 495 modules.

Pick-one groups + {#any} wildcard + Full/Partial navbar (2.5.0)

Owner reversed the earlier "no groups / no wildcard" calls once reframed: "implement the list auto group entry that selects one but instead of one word it would be one generator… #any and its sfw and nsfw variants… do this for expansions too." Plus a navbar spec: "full and partial separation in category; v1/v2 as superset links on the navbar next to Dynamic/Partial, v2 default; remove the v1/v2 toggle from category text."

  • Pick-one groups (engine). Re-added dynPromptGroupDirs/readDynPromptGroup + new expansionGroupDirs/readExpansionGroup in both loaders (autoGroupListDirs over the names, .group files, _enable/_disable-group-list markers). Dyn stage: {#folder}pickFrom(dynGroupMembers,…) runs one random member (resolvePool includes group dirs so {#scene} suffix-matches v2/scene). Expansion stage: <folder> (or .group) splices one random member. All gate-aware via hasNsfwToken.
  • {#any} family. {#any} / {#any-sfw} / {#any-nsfw} pick one generator from the whole v2 catalog with {keyword}-style variant semantics; the variant suffix is parsed ONLY for {#any} so a real nsfw-token generator name still resolves directly. dynPromptManifest.js restored RESERVED_ANY/ isReservedAny/dynGroupMembers.
  • SPA navbar. getBlocks now emits two dynVersioned blocks — "Dynamic prompts" (full) and "Partial prompts" (partial) — each folder-grouped with clickable {#folder} pills (+ a clickable {#any} "any" pill in the full tab), carrying variants.v2/variants.v1. Expansion folder pills are clickable <folder> too. Home.jsx: dynVer state drives the blocks via effItems; v1/v2 rendered as superset links on the cat-tabs navbar (v2 default); the inline chip-area toggle removed.
  • Verified: temp checks ({#scene}, {#fragment}, {#any}/-sfw/-nsfw, <lighting>, <detail>, direct {#beach}) all resolve; npm run lint 0 errors, npm run smoke green, npm --prefix web-app run build 493 modules. Bumped to 2.5.0. Un-rejected the group/wildcard entry in decisions/rejected.md (kept the lasting rule: a group/wildcard resolves to one concrete generator, never a line union).

Dynamic prompts: {#name} sigil + gating/wildcard + uniform SPA (2.4.0)

Owner follow-ups after the 2.3.0 parity pass: "there needs to be uniformity… # doesn't work for dynamic prompts because we need the / and the UX is bad — use {#…} like {#random}… bring over a lot of the standards we skipped." Then, mid-pass: "no clickable group folders for dynamic prompts — they're not lists of words, they're scripts with a specific input and output… no group entry lists."

  • Sigil → {#name}. Stage regex /\{#([\w/-]+)\}/g; loop guard prompt.includes("{#"); list stage hardened with a p1.startsWith("#") skip so {#x} is never mis-pulled as a list. Migrated 204 internal refs in 54 v2 generators via scripts/migrate-dynprompt-sigil.mjs (line-based, skips comments, idempotent (?<!\{) guard). v1 has zero internal # refs (verified) so it stayed frozen. Updated non-generator refs: core/engine.js + settings.js + web settings.js defaults ({#random}), promptFilesAndSuggestions.js suggestion builder, genImg.js, Home.jsx. Left the classic src/web/* jQuery #id selectors + Pug frontend alone (legacy).
  • Standards. NSFW gating by name token (isGatedDynPrompt = hasNsfwToken || gatedDynPrompts), applied in the stage (gated → "") and the suggestion pools; src/dynPromptManifest.js (dynPromptTags).
  • Group + wildcard reversal. First implemented implied {#folder} groups (random member) + .group files + enable/disable markers, plus a {#any} random-pick wildcard; the owner rejected the whole "pick one of many" model for dynamic prompts ("they're scripts with a specific input and output", "#any — this isn't a list where we need to pick out an entry"), so all of it was removed — folders are pure organization and every {#name} names one concrete generator. Recorded in decisions/rejected.md.
  • Uniform SPA. getBlocks collapses Full/Partial/User/V1 into one Dynamic prompts block with category-folder pills (plain labels, not clickable groups), and carries an itemsV1 variant; Home.jsx renders a v1/v2 toggle (dynMode) on the chip-area header.
  • Verified: gates resolve end-to-end (temp expansion checks for {#scene/beach}, {#castle-v1}, {#random}), npm run lint 0 errors, npm run smoke green, npm --prefix web-app run build 493 modules. Bumped VERSION + package.json → 2.4.0.

Dynamic prompts brought to parity with lists/expansions + v2/ reorg (2.3.0)

Owner: "upgrade the dynamic prompts to the same new standards we set for lists and expansions including on the ui side and the file side" → "reorganize completely, add to a v2 folder at root, move user submitted ones to v2 as well" → "don't mess with classic server… in notes only use the new one, the classic is being replaced, read-only reference only."

First committed the 18 leftover ../../src helper-import fixups as their own commit (completing 2.2.2) so the reorg landed clean. Then:

  • Reorg (file side). Wrote scripts/reorg-dynprompts-v2.mjs with an explicit name→category map; it moved the 79 top-level generators + user-submitted/beach-merk into data/dynamic-prompts/v2/{scene,subject,fragment,style,engine,user}/ (v1/ frozen) and rewrote every relative import by resolving the old→new absolute path (so ../../src/helpers/…../../../../src/… from a v2 file, and cross-category siblings like ../fragment/nature.js / ../scene/city.js are correct). 80 files moved.
  • Suffix resolution. core/stages/dynamicPrompt.js now imports resolveName, splits the catalog into v1/ (reached via #name-v1) vs v2 (reached bare), and resolves #name by path suffix against the right subset; #user-name strips the alias and resolves into v2/user/. Every old #beach/#fx/#artists reference still works.
  • Loaders. nodeLoader + browserLoader got readDynPromptMeta, dynPromptForcedPrefixDirs, a _-prefix skip, and compareNames sorting in dynamicPromptNames (nodeLoader reuses namesUnder).
  • Sidecars. scripts/dynprompt-meta/write-dynprompt-meta.mjs (mirror of the expansion meta script) wrote 121 <name>.json files (113 generators + 8 folders); v1 are auto-described as frozen mirrors.
  • Classifier + SPA. promptFilesAndSuggestions.js classifies by path (v1/→V1, v2/user/→User, else full/partial via the full flag) and stores each v2 generator by its computeButtonNames token (bare, since basenames are unique) so #token + the random-suggestion builder keep working. The SPA (web-app/src/lib/promptEngine.js) keeps the Full/Partial/User/V1 sections (owner's choice, not folder pills) but adds description tooltips, compareNames sort, and shortest-#token display.
  • Gotcha caught by the build. 10 v1/* generators import the shared entityBasicKeywords from the moved entity.js; smoke never loads v1 (the classifier skips it), so only vite build flagged the unresolved ../entity.js — repointed to ../v2/subject/entity.js. Lesson added to CLAUDE.md + the new architecture note: always run both gates.

Verification: npm run lint 0 errors, npm run smoke green (suggestion now emits suffix-resolved bare tokens), npm --prefix web-app run build 492 modules green. Classic server + prompt-modules/ left untouched (read-only legacy). Docs: new data/dynamic-prompts/README.md + reference/dynamic-prompts-architecture.md, updated reference/dynamic-prompts.md, status.md, CLAUDE.md. Bumped VERSION + package.json to 2.3.0.

Moved dynamic-prompts/ from src/ to data/ (2.2.2)

Owner: "move dynamic prompts from src to data — it's an exception." The #name generators are executable .js but are authored like the rest of the prompt content, so they now live under data/dynamic-prompts/ (the one deliberate src/data/ exception, recorded in decisions/architecture.md and CLAUDE.md). git mv src/dynamic-prompts data/dynamic-prompts (history preserved; v1/ + user-submitted/ came along). Loader path edits: src/prompt-modules/dynamic-prompt.js requires now prefixed ../../data/; core/nodeLoader.js joins rootDir/data/dynamic-prompts; core/browserLoader.js glob → ../../data/dynamic-prompts/**/*.js. The generator files import shared helpers out of src/, so their relative imports were rewritten (18 of them): top-level ../helpers/…../../src/helpers/… and ../promptFilesAndSuggestions.js../../src/…; v1/ ../../helpers/…../../../src/helpers/…. The first build caught exactly those 18 unresolved imports; after the rewrite npm run smoke (node + legacy loaders) and npm --prefix web-app run build (browser glob, 370 modules) are both green, lint 0 errors. Doc note: editing files via PowerShell Get-Content|Set-Content / positional Set-Content silently no-op'd on this machine — [System.IO.File]::ReadAllText/WriteAllText was reliable (added to the PowerShell-not-bash caution context).

Expansions: rename detail/legacy* + port _force-prefix (2.2.1)

Owner: "remove 'detail' from legacy since it's in the folder and require the prefix." Renamed detail/legacy-detaildetail/legacy and detail/legacy-person-detaildetail/legacy-person (the detail/ folder now carries that meaning) and added a detail/_force-prefix marker, so the editor shows/inserts <detail/legacy> / <detail/legacy-person>. Ported _force-prefix to the expansion side: generalized nodeLoader markedDirs(marker, base) + new expansionForcedPrefixDirs(), a **/_force-prefix glob + generalized markerDirs(files, marker, seg) in browserLoader, and fed it to computeButtonNames in promptEngine. Updated the one code reference (src/dynamic-prompts/futuristic.js: <legacy-detail><detail/legacy>) and the meta-script keys. As with lists, force-prefix is display-only — suffix resolution still works — so the prefix is surfaced for context, not hard-required. npm test + vite build green.

Expansions brought to parity with the list system (2.2.0)

Owner: "we did a lot of work on lists with folder nesting and groups and tooltips and conventions … lets bring what we can of that to expansions." Ported the portable parts of the keyword-list modernization to data/expansions/, leaving out the list-only machinery that doesn't fit copy/paste snippets (per owner: groups / clickable folder pills aren't needed — expansions just copy/paste). Decisions captured via the question tool: full parity, and leave expansion names/content as-is (zero-risk to existing <name> references).

What changed:

  • Folder nesting + path-suffix resolution. git mv'd the 9 flat expansions into category folders — detail/ (legacy-detail, legacy-person-detail), style/ (pixelart, dap), lighting/ (rays, candlelight), subject/ (coffecup, flower-pic), scene/ (underwater-anime-irl) — basenames unchanged. readExpansion now resolves a bare/partial <name> by path suffix via the shared resolveName(), so every existing <rays> / <legacy-detail> reference in the dynamic prompts still resolves untouched. Discovery is recursive in both loaders (namesUnder() in nodeLoader; **/*.txt glob in browserLoader) and skips _-prefixed internal files (same convention as lists).
  • Description sidecars → tooltips. Added a <name>.json per expansion + a <folder>.json per category (14 files), written by the new re-runnable scripts/expansion-meta/write-expansion-meta.mjs (mirror of write-list-meta.mjs). Both loaders gained readExpansionMeta(name).
  • SPA token cloud categorized. promptEngine.getBlocks builds the "Expansions" block as folder categories with a (non-clickable) category pill per folder + description tooltips and short computeButtonNames tokens, mirroring the Lists block. The classic Pug editor stays flat (consistent with how it renders lists).
  • Docs. New data/expansions/README.md and notes/reference/expansions-architecture.md (records what was ported and what was deliberately left out and why).

Not ported, on purpose: random-union groups, clickable group/folder pills, SFW/NSFW file splitting, _force-prefix (all expansion basenames are unique). Content review: the 9 snippets are hand-authored and benign — no safety filter needed.

Verification: npm run lint + npm run smoke (npm test) and vite build green. Version bumped 2.1.0 → 2.2.0 (MINOR; a feature). Committed on dev; master ship held for the owner's go-ahead per the deployment hold.