Newest entry on top.
Shipped 2.7.25 (gallery + single view + provider epic) to main
"Close and ship." Merged feature/gallery → dev (--no-ff), which delivered the whole stacked epic
(feature/providers + the gallery/single-view work — 36 commits). First CI run on dev failed: only the
Playwright visual-regression specs (topbar / sidebar / full-page snapshots), since the top-bar now has
the Generate/Gallery/Single tabs and the providers+gallery UI shifted the layout — the functional + a11y
specs passed. (The local format:check "182 files" scare was just the Windows CRLF working copy vs the LF
committed blobs; CI on Linux is LF, so format:check was green there — nothing to commit.)
Fixed the visual-baselines.yml workflow, which had broken in the engine-split (root npm ci with no
root package.json): added working-directory: engine-v3, npm install, and a repo-root-relative artifact
path. Ran it on dev, downloaded the linux-visual-baselines artifact, committed the three refreshed
*-chromium-linux.png baselines. CI on dev then green (check + gui + e2e).
Released PATCH dev → main (--no-ff; VERSION already 2.7.25, tag absent): release.yml cut tag
v2.7.25 + the GitHub Release, CI green on main. Netlify deploys the gui from main. The Pages docs site is
intentionally paused (pages.yml is workflow_dispatch only until npm run docs is reconciled to the
engine-v3 split) — not deployed this release, as expected. Cleanup: deleted the spent feature/gallery +
feature/providers branches (local + remote); removed the throwaway 175100000001–003 files from
engine-v1-2/output; stopped the v1-2 server.
Three persistent views (Generate / Gallery / Single) — state + scroll retained
Owner wanted the views to retain state across tab switches for the session, the single-image view promoted to its own header tab (Generate · Gallery · Single), generated images to open into that full view (Back pops right back to Generate where they left off), and scroll position kept only if there's a non-hacky way.
The clean answer: keep all three views mounted the whole session and toggle visibility with a CSS class
(.view-pane / .view-pane.on → display:none/flex). Because the scroll containers are never unmounted,
React state, form inputs, and scrollTop all persist for free — no manual save/restore (which would've been
the hacky route, so it's not done). Shared coordination state (items feed, gallery query, current
single image, returnTo, magick) lifted into App.
App.jsxrewritten: lifted state, three.view-panes, the 3-tab switch, feed/magick load, and the open/navigate/delete/search handlers.go("single")lands on the last image or a random one the first time; provider picker shows only in Generate.Gallery.jsx→ controlled grid-only (propsitems/loading/query/onQueryChange/onOpen/onRefresh); the single page extracted toSingleView.jsx(standalone view; keyboard nav gated onactiveso the hidden view doesn't eat arrow keys; empty state when no image).Home.jsx+PromptResult.jsx: a generated image now opens in the Single view viaonOpenImage(path)(App resolves the served path to a feed item, refreshing the feed if needed) instead of a new tab; middle-click still opens a new tab.- CSS:
.view-paneshow/hide + generic.view-pane > *fill.
Verified: lint 0 errors, smoke, gui build, test:web 43. No VERSION bump (unreleased 2.7.25 line).
Photo gallery + per-image JSON metadata sidecars (2.7.25)
Owner asked to bring the old v1-2 image feed to v3: a header switch (upper-left) flipping between
image generation and a photo gallery, with the gallery driven by JSON sidecars like v1-2 — every
image in output/ paired with a .json holding the original prompt, the DPL, the AI translation, and the
full provider + settings snapshot. Built it on feature/gallery (stacked off feature/providers, which
holds the provider/output infra the gallery depends on — dev is 31 commits behind and lacks
vite-plugin-api.js/output.js).
What changed:
- Sidecar on save.
POST /api/image(gui/vite-plugin-api.js) now accepts ametaobject and writes<base>.jsonnext to the image.Home.makeBatchassembles the record (prompt sent,promptOriginalengine roll,aiTranslationauto-fix rewrite,dpl, negative, provider, and a settings snapshot with API keys stripped) andingestImage(src, meta)passes it through. Image delete now also unlinks the sidecar. - Feed. New
GET /api/feedscansoutput/, pairs each image with its parsed sidecar, returns newest first (savedAt → mtime fallback). Tolerates a missing folder / corrupt sidecar. - Gallery view. Repurposed the previously-unused
Gallery.jsxinto the full view: masonry grid of lazy thumbnails (wide/tall by natural aspect), keyword search (searchHaystackover prompt/DPL/provider), and a click-through detail modal showing the whole sidecar with copy/open/reveal/delete. Newsrc/lib/gallery.js+ styles for.view-switch/.gallery-view/.g-grid/.g-detail. - Header switch.
App.jsxgained aGenerate | Gallerysegmented control in the brand row and renders Home or Gallery byview; the provider picker hides in gallery mode.
Local-only by design (the feed needs the dev server's fs) — a static/online build yields an empty gallery
with an explanatory note. Verified green: lint (0 errors), format, smoke, gui build (browser-glob gate),
test:unit 86, test:web 40 (added gallery.test.js, 5 tests). Bumped VERSION/package.json
2.7.24 → 2.7.25 (PATCH per the VERSION file's "ordinary features are PATCH" guidance). Committed ce398c3
on feature/gallery, pushed.
Follow-up fix: the gallery clipped past the bottom instead of scrolling — body is overflow:hidden
and <main> is a flex frame, so (like Home's .main-col) the view must own its scroll. Made
.gallery-view a flex:1; min-height:0; overflow-y:auto scroll container with an inner .g-inner
(max-width:1500px; margin:0 auto) preserving the centered width. gui build green.
Follow-up: single-image view is now its own page, not a popover. Per owner, replaced the modal
Detail overlay with a dedicated Single page that replaces the grid (matches v1-2's /single): a
back bar + prev/next (arrow-key) nav through the search-filtered feed, image left (sticky) / metadata
right, reusing the .g-meta-* / .g-settings / action styles. Swapped the .g-detail* modal CSS for
.g-single* page CSS. Delete now lands on a neighbor image (or back to the grid when it was the last).
Lint 0 errors, gui build green, test:web 40 (gallery lib tests unaffected).
Single page built out properly: layered prompts, negative AI translation, ImageMagick export
Owner asked to migrate the v1-2 single full-view page into v3 "but actually have it good," with the richer v3 data (DPL + AI-translation versions of both prompt and negative), an app-like look, the keyword/button cloud, and the server reporting ImageMagick so the format-conversion actions can be gated. Dropped the obsolete actions (animation/variations/upscale/rerolls/select/parent).
- Layered sidecar + negative AI translation (generation change). Sidecar is now nested:
prompt:{dpl,roll,ai,final}+negative:{dpl,roll,ai,final}.Home.makeBatchnow also AI-translates the rolled negative when auto-fix is on (was DPL-expand only), sends the translated negative, and caches both prompt+negative rewrites on the prompt entry (no re-billing on re-gen). Fixed an existing gap where auto-render lost the DPL in the sidecar (now passed via apromptDplArg). lib/gallery.jsgainedpromptLayers/negativeLayers/promptText(nested + legacy-flat tolerant);searchHaystacknow indexes every layer.lib/magick.jsadded (fetchMagick,convertUrl).- Rebuilt
Gallery.jsxSingle: Prompt/Negative cards (layers as copy-able blocks, dupes collapsed), curated Details table + collapsible All-settings + Raw JSON, clickable keyword cloud (chips filter the gallery), actions row (Open/Reveal/Download PNG/Convert & download dropdown/Delete). New.g-card/.g-text-row/.g-detail-table/.g-cloud/.g-actionsstyles replacing the old.g-meta-*/.g-detail-actions. vite-plugin-api.js:GET /api/magick(cached detect +magick -list format→ writable still formats, allowlist-filtered) andGET /api/image/convert?file=&format=(first-frame convert → download). Magick isn't on this machine's PATH, so the menu stays hidden (verified the unavailable path).
gallery.test.js rewritten for the nested schema (+ layer-helper tests). Verified: lint 0 errors, smoke,
gui build, test:web 43, node --check on the plugin + libs. No VERSION bump (unreleased 2.7.25 line).
fairyfox adopt run 3: verification-floor clarification (hub 0.9.3)
Scheduled fairyfox check refreshed the mirror (hub dev force-pushed again → 42b6ea4 → 6777a73,
0.9.2 → 0.9.4). Changelog-span scoping found the only node-facing change is 0.9.3: the express-auth
language now states the verification floor is never skipped — an express-authorized/automated adopt
skips only the redundant confirmation pause, never verification (full floor: build/tests + ## Verify/
compliance + project-constraint checks, run before and after), with a hard "can't verify → don't
auto-apply, fall back to check-report-wait". The scheduled phase reported and applied nothing; owner
then green-lit ("yes please"). Adopted the wording into CLAUDE.md + cross-project-sync.md. Skipped
0.9.3's reports_through filename-list change (hub-internal .last-seen.yml, not carried here) and the
0.9.4 hub data/blog/meta pass. Notes/CLAUDE.md-only — no VERSION bump. Committed on feature/providers
(where the prior 0.9.2 adoption f0cf652 lives; dev is behind). Report:
notes/fairyfox-reports/2026-06-26-adopting-updates-3.md.
fairyfox adopt run 2: express-auth ledger + release-tagging fix (hub 0.9.2)
Scheduled fairyfox check (hub dev force-pushed → mirror 42263fe → 42b6ea4, 0.9.2) surfaced the
0.9.0–0.9.1 standards delta plus two flags; owner green-lit adoption and asked to fix the tagging flag.
Adopted, notes/CLAUDE.md-only (no VERSION bump):
- Release tagging (the real fix).
CLAUDE.md+git-workflow.mdhand-tagged (git tag … && git push --tags) in all three release paths, butrelease.ymlderives/creates the tag itself and skips if the tag already exists — so a hand-pushed tag = silent no-op release. Removed every hand-tag; added a "Who creates the tag — CI, not by hand" section (recorded as a deliberate divergence from the hub's hand-tag example).deployment.mdalready described CI-owned tagging, so this just makes the docs consistent. - Express-authorization ledger (
hub/authorizations.yml): carve-out added toCLAUDE.md+cross-project-sync.md— an active ledger entry thatcoversa change lets an interactive adopt skip only the redundant confirmation pause; every other safety step stays, and an unattended/scheduled check still applies nothing. - process-reports: combined check→adopt = one report; real-
hub_versionanchor rule; check-only-on-not-yet-adopted inline exception. compliance: ledger note on the sync row.
This run is the combined report notes/fairyfox-reports/2026-06-26-adopting-updates-2.md; folded in and
deleted the interim 2026-06-26-check-only.md. Verify loop run; committed on dev.
Image-gen UI build-out (2.7.6 → 2.7.10) — feature/providers
After the first live ComfyUI run (2.7.5), iterated the UI hard with the owner. Arc: 2.7.6 sampler +
scheduler also coerced against ComfyUI's /object_info (after the checkpoint fix, "Euler" vs "euler"
failed validation); 2.7.7 central output folder — /api/image ingest + /api/output/<file> serve,
so every provider's images land in engine-v3/output/ and display same-origin (fixed Comfy Desktop's 403
on direct /view); 2.7.8 UI redesign — provider picker in the header, capability-driven
provider-controls box, gear slimmed to prompt knobs, prompt counter, dropped keyword/artist counts +
auto-add; 2.7.9 provider box collapse + info tooltips (_shared/fieldInfo.js) + no-auto-save key
(sessionKeys.js, explicit Save/Clear) + negative as a DPL textarea (expanded before send) with a
random roll; 2.7.10 per-prompt image batches (PromptResult.jsx) with a clear Generate/More
button and per image/batch/prompt management — remove, delete-from-disk, reveal in Explorer, open in
default app (/api/image/delete|reveal|open).
Verified each step: web build + test:web (35). Dev-middleware changes (2.7.7, 2.7.10) need a dev-server
restart. Still open from the owner's spec: tighter visual polish (can't see render here), galleries (image
click currently opens a new tab), and Phase 3 submit/poll hosted adapters.
ComfyUI live run: checkpoint auto-resolve + readable errors (2.7.5) — feature/providers
Got ComfyUI working end-to-end on the owner's machine (RTX 5060 Laptop, 8GB). Downloaded SD1.5 fp16
(~2GB) into Comfy Desktop's shared checkpoints folder, confirmed ComfyUI sees it, and ran a real
generation through ComfyUI's /prompt API (produced raptest_00001_.png) — the exact submit→poll→/view
path our adapter uses. Then the app's image button showed "[object Object]": the app sent
model.safetensors (stale/blank checkpoint — the user's persisted providerParams.comfyui predates the
default I set, and saved params override schema defaults), ComfyUI 400'd with value_not_in_list, and
forward() threw new Error(errObject). Fixes: ComfyUI adapter now auto-resolves the checkpoint from
/object_info (configured → first-installed → clear error); localDirect.js readableError() surfaces
error.message + node_errors. Verified: web build, test:web 33. (Reminder: saved per-provider params
override schema defaults — that's the namespacing working as designed, but stale saved values can shadow
new defaults.)
Route local-direct through the dev server — no browser CORS (2.7.4) — feature/providers
Hit while helping the owner get a live ComfyUI run working. Diagnosis on their machine: ComfyUI (Comfy
Desktop) was up on 8188 but (a) had no checkpoint model installed (its model list was empty — so it
can't generate anything, even in its own UI), and (b) Desktop launches without --enable-cors-header, so a
browser→8188 call is CORS-blocked. Owner's call: "all those calls should go through a server, no reason to
hit it from the browser."
- Fix:
localDirect.js(postJson/getJson) now posts to a new/api/forwardroute in the Vite middleware (vite-plugin-api.js) — localhost-only, server-sidefetch— so no CORS. Image URLs stay direct (<img>display isn't CORS-gated). Hosted already went through/api/generate, so the browser now makes zero direct provider calls. Updated the local-webui contract test (asserts the forwarded url+body). - Set ComfyUI default checkpoint to
v1-5-pruned-emaonly-fp16.safetensors. - User-side (in progress): downloading SD1.5 fp16 (~2GB) into Comfy Desktop's shared checkpoints folder
(
%LOCALAPPDATA%\Comfy-Desktop\ComfyUI-Shared\models\checkpoints); ComfyUI can't generate until a checkpoint exists. Dev server must be run by the user in a terminal (npm run web) — backgrounding it via remote automation port-flapped against their own instances.
Verified: web build, test:web 33. VERSION/pkg → 2.7.4.
Provider UI re-add: capability-driven settings + image gen (2.7.3) — feature/providers
Phase 2, continuing the same branch. Re-added the image-generation UI that was stripped during the SPA rework, now on the new provider framework.
What changed.
- Capability-driven Settings.
Settings.jsxbackend section renders the selected provider's ownsettings.jsfields (a smallProviderFieldswitch over type; select options from the schema'sdatalists). New hookgui/src/lib/useProvider.js—useProviderSettings(id)lazy-loads the schema + resolves option-data;providerMode(id)(dialect → engine mode);flattenForProvider(settings, defaults)merges the namespaced params + mode for adapters/formatters. - Per-provider param namespacing. Knobs persist under
settings.providerParams[id]; an effect fills a provider's defaults on first load without clobbering saved values. Switching providers no longer cross-contaminates (fixed the would-be SD "Euler" vs Comfy "euler" shadowing). - Provider owns the dialect.
modeis set from the provider on select; the standalone Mode dropdown and the hardcoded global Image/Negative groups were removed (those knobs are per-provider now). - App re-gained the Settings drawer + a gear button. Home gained image generation (per-prompt image
button for api-tier →
Gallery, in-memory only) and Copy-prompt now formats through the provider for the syntax tier (Midjourney params).buildPromptsinjects the provider dialect mode. - Added
providerParams: {}to default settings.
Verified. lint 0 errors; web build (provider chunks code-split); test:web 33 (+ Settings.test.jsx:
provider select present, BYOK key appears for OpenAI, no Mode control). VERSION/pkg → 2.7.3.
Heads-up. Playwright visual baselines need a refresh (home gained a gear + image results); not in the
headless gate — run test:e2e:update deliberately. Next: submit/poll hosted adapters (Stability/fal/
Replicate/BFL/Ideogram/Leonardo); surface per-provider presets in the UI; live ComfyUI + OpenAI runs.
Provider framework foundation + plain dialect (2.7.2) — feature/providers
Owner asked for real, native, first-class support for many image providers — not a one-size-fits-all
minimum. Worked through the model together: a provider sits on a support ladder (API → syntax/dialect
→ plain text), each declares only the capabilities it has (expand/contract), and providers own
everything (config, code, data, settings schema, presets). Key correction from the owner: plain must
keep emphasis, rendered in words — don't discard valuable behavior, just reformat it.
What changed (Phase 1, all on feature/providers off feature/v3-layout-reorg).
- Native core
plaindialect.src/helpers/randomEmphasis.jsgainedprocessPlain+ a"Plain"branch: rolls the same emphasis level, then prefixes an intensifier (prominent/strongly emphasized/dominant) or hedge (subtle/faint/barely-there), overridable viasettings.plainEmphasisWords/plainDeEmphasisWords. No list-stage change (non-NAI/MDJ already use the SD function set; Plain adds no parens so the NAI brace-rewrite is untouched). gui/providers/framework. Self-contained folder per provider (config.jsmanifest,settings.js,presets/,code/,data/), auto-discovered viaimport.meta.glob._shared/dialects.jsmaps a provider's dialect → enginemode._shared/transport/=localDirect,hostedProxy(client),submitPoll(server, for the Phase-3 async APIs).- Storage subsystem
gui/storage/— browser (online/stripped localStorage) + localFile (local.jsonvia/api/storage, browser fallback);presetStore(providerId)for provider-owned presets. - Transport plumbing —
gui/server/dispatch.js(shared), the Netlify function rewired to it, and a new Vite dev-middleware (gui/vite-plugin-api.js, wired intovite.config.js) serving/api/generate+/api/storagelocally..gui-storage.jsongitignored. - First providers — local-webui (ported real), comfyui (real workflow-graph local-direct), openai
(hosted client +
code/server.js), midjourney (syntax tier, data-driven--paramcatalog). - Old
gui/src/lib/providers/{localWebui,hostedProxy}.jsremoved;index.jsre-exports the new registry (keepsSettings.jsx/imports working).providers.test.jsrewritten (registry + Midjourney contract); addedtests/unit/randomEmphasis.plain.test.js.
Verified. lint 0 errors; smoke; test:unit 86; test:web 31; web build. VERSION/engine-v3 pkg → 2.7.2.
Next. Re-add the image-gen UI in Home/Settings (provider dropdown, capability-driven knobs, image
results, Copy-prompt for syntax tier; drop the standalone Mode control); submit/poll hosted adapters; live
ComfyUI run once it finishes installing. Design captured in notes/plans/providers.md.
Layout reorg: flat blocks, data/sources, web-app → gui (2.7.1)
Owner-requested cleanup on feature/v3-layout-reorg, committed in focused chunks.
What changed.
- Flat blocks. Moved
data/blocks/v3/*up todata/blocks/*and ripped out the now-vestigial version machinery (v1/v2 were deleted in 2.7.0;v3/was the lone tree).block.jsbecame a single flat pool; thev3/-prefix filters leftnodeLoader/browserLoader;promptFilesAndSuggestionsshedv1Files/v2Files. Critically, the SPA carried the same machinery —promptEngine.jsbuilt a per-generationGENSkeyed onstartsWith("v3/")(now empty → empty Blocks picker) andisExpansionKeymatchedv3/expansion/(now never) — soHome.jsx's version superset switch +promptEnginewere de-versioned too. Added atest:webassertion that the Blocks list has real chips (guards exactly that regression). - data/sources/.
artists.csv/danbooru.csv/nai-tag-expirement.jsonmoved there; build-script paths and.prettierignoreupdated (the minified nai JSON must stay out of prettier). - web-app → gui. Folder rename + every config/code reference (68 occurrences across 39 files).
vite.configresolves the engine root via.., so it's name-independent. - Upgrade-2-0.md root duplicate removed (canonical copy is in
engine-v1-2/); dropped from build-docsREPO_DOCS.
Landmines hit.
npm run formaton this Windows checkout reflows every file's line endings (CRLF↔LF) — ~200 files show as modified butgit diffis empty (git normalizes them to no-ops). It also genuinely expanded the minifiednai-tag-expirement.jsonby 8k lines once it left thedata/*.jsonignore glob — hence the newdata/sources/prettier-ignore. Lesson: stage explicitly, nevergit add -A, andgit restore .clears the no-op churn.- The
v3/prefix was load-bearing, not just a folder — it was the canonical address of the active catalog and gated a real feature (auto-begin/end). Flattening required code edits, not just agit mv.
Verified: smoke, npm --prefix gui run build, test:unit (83), test:web (28), lint 0 errors. PATCH → 2.7.1.
Not merged to main (awaiting owner go-ahead). Still pending: a fuller notes sweep of v1/v2/web-app in the
deeper reference/ docs (e.g. blocks-architecture.md); the long-standing netlify.toml engine-v3 path
reconciliation; and the preset-system rethink (26 combinatorial JSONs — see the chat summary).
Adopted hub updates: process-reports + compliance audit
Scheduled fairyfox check-for-updates run surfaced new hub standards; owner gave the go-ahead to adopt in the same session.
Refresh snag. Upstream hub dev was force-pushed (c5659f8 → 42263fe), so the documented
pull --ff-only aborted. Recovered with git fetch + git reset --hard origin/dev on the reference
clone only (disposable, git-ignored mirror — no project history touched). Both old + new commits were
retained, so the old→new diff was exact.
What changed upstream (three themes): a new process-reports feedback loop, a new standards
compliance audit, and git-flow wording finalized to --no-ff tagged releases. The git-flow piece was
already adopted here (2026-06-25), so only the first two were genuinely new.
Adopted (on a feature/adopt-hub-process-reports-compliance branch; notes/process-docs only, no
VERSION bump, no main release):
notes/fairyfox-reports/— new folder: README + this run's process report (2026-06-26-adopting-updates.md).notes/reference/process-reports.md+notes/reference/compliance.md— project's-eye-view copies of the two new standards.## Verify (is it being followed?)sections added toreference/git-workflow.mdandreference/versioning.md.reference/cross-project-sync.md— "hub also readsnotes/fairyfox-reports/" model, a process-report step in the check flow, the force-push refresh fallback, an anti-recursion line.CLAUDE.md— fairyfox flow now ends with a process report (even check-only) and names the compliance audit; new notes-maintenance trigger row; force-push fallback noted.notes/README.md,status.md, changelog — wired in.
Report written for the hub. Per the new standard, the run's process report flags the force-push/
--ff-only friction and the check-only chicken-and-egg (a check-only run can't write into
notes/fairyfox-reports/ if the folder isn't adopted yet) as feedback for the hub to improve the runbook.
Used PowerShell throughout (never the bash sandbox).
Released 2.7.0; synced deep-dive notes; fixed CI
Wrapped the engine-split / v3-only work into a tagged release and cleared the remaining safe follow-ups.
Deeper notes sync. Added structure banners to the systems/* deep-dives (overview, core-engine, cli,
server, web-app, README) + context/project.md: engine-v3 = active project (v3-only, no expansions),
engine-v1-2 = frozen pre-revival CLI/server. The cli/server deep-dives now read as engine-v1-2 history.
(Full line-by-line body rewrites deferred — banners orient the reader; low value to rewrite each para.)
CI was red — fixed. npm ci failed on Linux with npm's @emnapi optional-dependency lockfile bug
(two @emnapi/runtime versions pulled transitively; a Windows-generated lock omits the Linux-resolved
edges, and I can't generate a Linux lock — no bash sandbox per standing rule). Tried regenerating the lock
overrides(pinned@emnapi/*) — still red on Linux. Settled on switching CI/release/pages installs tonpm install(reconciles the lock on Linux at runtime; locks + overrides still pin). CI green after.
Released 2.7.0 (MINOR). Retargeted release.yml + pages.yml to run in engine-v3/ (VERSION read
stays at repo root; release docs-zip is continue-on-error; pages auto-deploy paused to
workflow_dispatch only — see below). Bumped VERSION + engine-v3/package.json to 2.7.0 on a
release/2.7.0 branch, merged --no-ff to main + tagged v2.7.0, merged back to dev, pushed.
Release process trap (note for next time): release.yml gates on "tag v$VERSION doesn't exist yet"
(it expects to create the release+tag from a main push), but the git-flow standard has us create + push the
tag manually. Pushing main --tags together made release.yml see the tag already present → it skipped
(10s no-op, no GitHub Release). Published the v2.7.0 release directly with gh release create + a source
tarball. To reconcile: either push main without the tag (let release.yml tag), or relax the gate for
workflow_dispatch.
Doc-site (still broken — the main remaining follow-up). npm run docs (build-docs.mjs) resolves
notes/ + assets/ at the repo root while src//web-app/ moved under engine-v3/, and it runs jsdoc
with a config that's no longer co-located. Needs build-docs.mjs to use two base dirs (repo root for
notes/assets/README, engine-v3 for src/data/web-app + jsdoc cwd/config). Until then the release docs-zip is
skipped and Pages auto-deploy is paused.
Used PowerShell throughout (never the bash sandbox).