# Changelog

## [0.2.55] - 2026-05-20

This release replaces v0.2.54's opt-in-pack design with the simpler
model the workflows already use: each `optional_models:` entry is a
**download-on-demand variant** (Flux 1, Flux 2, ERNIE, Wan 2.1, LTX
2.3, etc.) the user picks at the workflow's runtime radio. The
workflow's radio label gains a "(install required)" suffix when a
variant's files aren't on disk, and the workflow renders an inline
install affordance instead of the generate UI when that variant is
chosen. Settings now exposes per-pack `[Install]` buttons so the
user can pre-install a variant from outside the workflow.

`compute_install_state()` remains the single source of truth, but
its blocker rule simplifies to: an installed workflow is blocked iff
any of its `models:` (required) entries are missing on disk. Optional
packs are never blockers -- they don't gate the workflow.

This collapses the "two sources of truth" that v0.2.54 left in the
Troubleshoot page: the Blockers section and the Problems-Missing
list now scope to the same set (required-only) and report the same
count.

### Changed
- **`compute_install_state` blocker rule simplified.** Only required
  models count. Removed `missing_opt_in`, `enabled_packs` fields from
  the returned state (and from `get_available_workflows` workflow
  dicts).
- **`get_all_models_from_installed_manifests()` scoped to required.**
  Drops optional-model dedup union; just returns
  `get_models_for_workflow_ids(installed)`. The wide-universe
  `get_all_models_from_manifests()` (used by
  `scan_for_models_multi`) keeps optional models so the scanner can
  recognise pre-existing on-disk files for future pack installs.
- **Settings "Optional model packs" expander -- per-pack Install
  button.** Each pack row shows the pack key, file count, total
  size, and a status badge: `:green[Installed]`,
  `:orange[Partial (n/N)]`, or `:grey[Not installed]`. The
  `[Install]` button downloads all of the pack's models (mirrors
  `_do_download_models` for required sets). No Uninstall in this
  release -- file deletion is risky to share across packs and is
  better handled from disk.
- **Workflows.py blocker banner trimmed.** No more "opt-in pack"
  bullet list; only required-missing files appear.
- **Troubleshoot Blockers section trimmed.** Same -- required-missing
  only.

### Removed
- `<userData>\comfy_labs_user_state.json` -- no longer written or
  read. Existing files become orphaned but harmless; they can be
  deleted manually.
- `COMFY_LABS_USER_STATE_PATH` env var.
- `workflow_manager.get_enabled_packs()`,
  `workflow_manager._read_user_state()`,
  `workflow_manager._pack_key_of()`, `_enabled_packs_cache`.
- HTTP bridge routes on `:9876`: `GET /user-state`,
  `POST /user-state/enabled-packs`, `POST /invalidate-install-state`.
- `src/main.js` helpers `userStatePath`, `readUserStateSync`,
  `writeUserStateSync`, `setEnabledPack`.

### Fixed
- **Troubleshoot Problems-Missing count.** Was 18 on a healthy
  install (every optional variant the user hadn't picked appeared as
  red [MISSING]). Now matches the new Blockers section -- 0 on a
  healthy install, N when N required files are genuinely absent for
  an installed workflow.

## [0.2.54] - 2026-05-20

This release replaces the three-views-disagree problem with a single
source of truth: `workflow_manager.compute_install_state()`. Settings,
the Workflows sidebar gate, the Troubleshoot page, and the launch-time
scan all read from the same helper. An installed workflow is "blocked"
when any required model is missing on disk -- or when the user has
opted into an optional model pack whose files aren't on disk yet. On
launch, if any blockers exist, the BrowserView routes straight to
`/Troubleshoot` instead of `/` so the user sees what to fix before
trying to generate.

### Added
- **`compute_install_state()` -- single source of truth.** New helper in
  `python/workflow_manager.py` returning per-installed-workflow state
  (`installed`, `compatible`, `enabled_packs`, `missing_required`,
  `missing_opt_in`, `blocker`, `ready`, `status`). Cached process-wide;
  invalidated by `install_workflow`, `uninstall_workflow`, pack toggle,
  and Troubleshoot's Rescan button. `get_available_workflows()` now
  delegates per-workflow status to this helper (its public shape is
  unchanged).
- **Opt-in optional pack persistence.** New
  `<userData>\comfy_labs_user_state.json` (Electron-managed, schema
  `{enabled_packs: {workflow_id: [pack_key, ...]}}`) and Python helper
  `workflow_manager.get_enabled_packs(workflow_id)` that reads via the
  new `COMFY_LABS_USER_STATE_PATH` env var. Optional models count as
  blockers only when their `pack:` key is opted in -- so
  `Z-Image-Fun-Controlnet-Union-2.1.safetensors` (part of the optional
  Z Image base pack on Concept Magic / Concept Magic LoRA) no longer
  appears as a phantom-missing entry on a Z Turbo-only install.
- **Settings page: Optional model packs toggle.** Each installed
  workflow's card now exposes an "Optional model packs" expander with
  a toggle per pack. Toggling POSTs to the new
  `/user-state/enabled-packs` route on the 9876 bridge, invalidates the
  install-state cache, and reruns so all three views (Settings,
  Workflows, Troubleshoot) refresh together.
- **Troubleshoot: Blockers section.** A new top-of-page block iterates
  `compute_install_state()` and lists each blocked workflow with its
  exact missing required + opt-in files (filename + subdir + size).
  When no installed workflow is blocked, the page shows
  `:green[All N installed workflows are ready.]`.
- **Launch-time scan -- auto-route to Troubleshoot on blockers.**
  `checkWorkflowReadiness` (in `src/main.js`) now calls
  `compute_install_state(force_refresh=True)` and surfaces
  `blocker_count` + `blocker_ids` to the renderer. `App.jsx` extends
  its existing `initialPath` decision: `blocker_count > 0` ->
  `/Troubleshoot`, else `ready_count == 0` -> `/Settings`, else `/`.
- **Workflow page: blocker gate.** Clicking a workflow whose
  `compute_install_state` blocker flag is set now shows an inline
  banner listing the missing files with a pointer to Troubleshoot,
  instead of letting the user click Generate and watch ComfyUI error
  at queue-time.
- **New 9876 bridge routes** (in `src/main.js`):
  `GET /user-state`, `POST /user-state/enabled-packs`,
  `POST /invalidate-install-state`.

### Fixed
- **Troubleshoot phantom "missing" entries.** The page no longer
  iterates the full catalog union -- it scopes the scan to installed
  workflows only. Before: a fresh install reported ~18 missing models
  because every catalog workflow's required + optional models were
  checked, including workflows the user never installed (Flux1-Dev,
  Flux2, ERNIE, Gemma, LTX-2.3, the non-Turbo Z-Image-Fun ControlNet
  that pairs with the opt-in Z Image base pack, etc.). After: only
  models belonging to actually-installed workflows appear -- and even
  then, opt-in pack models only count as missing once the user has
  opted into the pack via Settings.

### Migrated
- The catalog-wide `get_all_models_from_manifests()` is retained in
  `workflow_manager.py` for the scan-existing-library path
  (`model_manager.scan_for_models_multi`), which deliberately wants a
  wide net to recognize pre-existing on-disk files for future
  installs. The new
  `get_all_models_from_installed_manifests()` is used only by the
  user-facing Troubleshoot scan.

## [0.2.53] - 2026-05-20

### Fixed
- **Extend Image silent hang (HIGH).** Clicking "Extend Image" would spin
  forever with no result, no progress, no error when ComfyUI rejected the
  workflow at queue-time (e.g., missing model file, node validation
  failure). `queue_prompt` now raises a new `ComfyUIRejectedError` with
  the parsed `node_errors` payload instead of silently returning a None
  `prompt_id`, and `submit_and_wait` propagates rather than swallowing.
  The Extend Image generate handler now wraps each generation in
  try/except, renders an actionable `st.error` on the first failure, and
  breaks the loop so users don't wait through `num_gens * 600 s` of dead
  spinner. Affects `python/comfyui_api_helper.py`,
  `python/comfy_lab.py`, `python/workflows/extend-image/ui.py`.
- **Workflow update counter disagreement (MEDIUM).** Renderer StatusBar
  ("12 workflow updates available") and Streamlit Settings
  ("Updates Available (0)") disagreed because the StatusBar compared
  manifest SHAs while Settings compared semver. Both now compare semver
  versions; the StatusBar reads the local manifest's `version:` field
  and matches it against the catalog's `wf.version`. Affects
  `src/main.js`.

### Changed
- **`concept-magic` and `concept-magic-lora` manifests** -- added
  `sha256: TODO` stubs to both `controlnet_union` and
  `z_image_controlnet_union` entries (4 entries across 2 manifests) with
  a comment explaining the deliberate byte-size coincidence between the
  Turbo and non-Turbo ControlNet variants. Stubs unblock future
  hash-based audit tooling in Troubleshoot; populate the hash by running
  `huggingface-cli download <repo_id> <repo_file>` and `sha256sum`.

## [0.2.52] - 2026-05-20

### Added
- **Three new shared UI helpers** in `python/comfy_lab_ui.py`:
  `prompt_input()` (standard 120 px prompt textarea), `advanced_expander()`
  (single-label "Advanced settings" wrapper, no model-name suffix), and
  `result_area()` (success-row helper for "Generated in X.Xs -- Seed: N").
  Foundation for ongoing workflow UI consistency.
- **Workflow smoke-test harness** at `test-evidence/smoke_test_workflows.py`.
  Stubs Streamlit + WorkflowAPI and invokes `render(api)` on every workflow;
  catches import / signature / call-surface regressions in seconds without
  needing a running Streamlit. Useful for verifying future helper migrations.

### Changed
- **Concept Magic** and **Concept Magic LoRA**: prompt label is now
  "Prompt" (was "Describe the desired output"); prompt textarea bumped from
  100 px to 120 px to match the rest of the app; the "Advanced settings"
  expander no longer carries a model-name suffix like
  "(Z Image Turbo)".
- **Image to Video**: prompt textarea bumped from 100 px to 120 px;
  Step 2 button reads "Apply Post-process" (was "Run Post-process").
- **Upscale Image**: primary button reads "Upscale Image" (was bare
  "Upscale").
- **Make PBR Tileable Textures**: primary button reads "Generate Texture
  Set" (was "Make Tileable") -- describes the actual deliverable.
- **Text to Image** and **Image to Image**: internally migrated to the new
  `prompt_input()` helper. No visible change (label / height / placeholder
  already matched the spec).
- **Extend Image** and **Inpaint Image**: "Advanced settings" expander now
  goes through the shared `advanced_expander()` helper. No visible change.
- **Product name consistency.** "Comfy Labs" (no hyphen) now reads
  correctly on the Workflows landing-page browser tab and on the
  Troubleshoot Paths "downloads go to" line. Both spots previously read
  "Comfy-Labs".
- **9 workflow manifests** bumped; the 7 that adopted new helpers
  (text-to-image, image-to-image, image-to-video, concept-magic,
  concept-magic-lora, extend-image, inpaint-image) require
  `min_app_version: "0.2.52"`. Older shells refuse the install with a
  clear "Update Comfy Labs first" message rather than crashing at
  render-time.

## [0.2.51] - 2026-05-19

### Added
- **Troubleshoot Paths button** in the Electron toolbar (Stethoscope icon).
  Opens a new Streamlit diagnostic page that scans your models folder,
  compares actual locations against the canonical paths declared in each
  workflow manifest, and lets you register pre-existing files at their real
  locations with a single Fix Paths click. No files are moved, copied, or
  hardlinked. Files genuinely missing from disk can be fetched via a
  Download Missing button that uses the existing download pipeline.
- **Registry-first model detection.** Comfy Labs now keeps a registry at
  `<MODELS_DIR>/.comfy_labs_manifest.json` recording the actual on-disk
  location of every known model. `is_model_installed` consults the registry
  before any canonical-path check, so files stored in your pre-existing
  ComfyUI layout (e.g. text_encoders/ instead of clip/, or diffusion_models/
  root instead of diffusion_models/flux/) are recognized without forcing
  hardlinks. Registry schema is v3 (absolute, forward-slashed paths); v2
  manifests auto-migrate on first read.
- **Separate downloads folder support.** The Troubleshoot page can configure
  a Comfy-Labs-owned downloads folder distinct from your pre-existing
  models folder. When set, future downloads land there and the uninstaller
  surgically removes only that folder, leaving your existing model tree
  untouched. ComfyUI's `extra_model_paths.yaml` emits two base_path blocks
  so loaders find files in either tree.
- **Duplicate detection** in the Troubleshoot page. If a known model file
  exists at more than one location under your models folder (hardlinks or
  true copies), the page surfaces both paths so you can decide which to
  keep.
- **Diffusers cache layout detection** in the model scanner. Pre-existing
  ComfyUI installs that stored HF Hub diffusers caches (BiRefNet,
  sdxl-base, sdxl-vae) under `models--<repo>/blobs/refs/` are now
  recognized correctly. Previously the scanner only matched top-level
  model files and falsely flagged these as missing.
- **`COMFY_LABS_DOWNLOADS_DIR` env var** plumbed through every Python
  subprocess. When unset, downloads land in MODELS_DIR (the original
  behavior). When set (via the new downloads_folder config), downloads
  land at that separate path.
- **ARCHITECTURE.md** added at the repo root -- canonical technical map
  for future Claude sessions.

### Changed
- **`get_all_models_from_manifests`** (in workflow_manager.py) now unions
  `models[]` and `optional_models[]` across every catalog workflow. The
  wizard's first-run scan and the Troubleshoot diagnostic now both
  discover opt-in model packs (Flux 1, Flux 2, ERNIE, Mistral, Ministral,
  Z Image base) without those being invisible to the registry.
- **Restart Now no longer auto-relaunches.** The native restart dialog
  shown after onboarding-complete and after setting up a separate
  downloads folder now calls `app.quit()` only -- the user re-opens
  Comfy Labs themselves. Avoids a blank-window race condition seen with
  `app.relaunch()` on some setups.
- **`scan_for_models` skips known cache dirs.** `.hf_cache`, `.cache`,
  `.locks`, `__pycache__`, `.git`, `.workflows`, `.wheels_cache`, `blobs`,
  `refs` are pruned from the walk. Prevents the scanner from registering
  HF Hub internal cache paths (which can be pruned by HF Hub) ahead of
  canonical paths.
- **All 12 workflow manifests** had their per-workflow version bumped and
  `min_app_version` aligned to 0.2.50 as part of the UI consistency
  pass.
- **Manifest subdir corrections.** `text-to-image` and `image-to-image`
  optional models now declare the correct subdirs: `flux1_dev_fp8`,
  `flux1_dev_fp16`, `flux2_dev` use `diffusion_models` (not
  `diffusion_models/flux`); `flux2_mistral` and `ernie_ministral` use
  `text_encoders` (not `clip`). Matches ComfyUI convention where LLM
  text encoders live under `text_encoders/` and CLIP variants under
  `clip/`.

### Removed
- **`PRD.md`** archived to `docs/archive/architecture/2026-05-19/`.
  v0.1.0 Phase 1 PRD draft from 2026-04-28; superseded by current
  source + the new ARCHITECTURE.md.

## [0.2.49] - 2026-05-17

### Fixed
- **Image to Ortho Views: upload no longer crashes, Generate now
  produces visible results.** Two PIL bugs in the create-ortho-views
  plugin's atomic-write code (`img.save(<path>.tmp)` was raising
  `ValueError: unknown file extension: .tmp` because PIL infers the
  format from the extension): one on initial image upload, one on the
  final per-session sheet write. Both `save()` calls now pass
  `format="PNG"` explicitly. As a side-effect, pressing Generate used
  to look like "Generating..." then nothing -- the sheet-write
  exception was being caught and immediately wiped by the post-
  Generate `st.rerun()`. (Plugin bumped to 1.0.4 -- ships in the next
  catalog-mirror publish.)
- **Workflow dispatcher no longer double-renders on plugin crash.**
  `_render_workflow_plugin` in `python/Workflows.py` used to return
  False when a plugin's `render()` raised after registering any
  widgets, which caused the legacy mode-dispatch block to try to
  register the same widget keys a second time and fatally crash with
  `StreamlitDuplicateElementKey`. Now returns True whenever a plugin
  was found, error or not -- the user sees the actual plugin error
  instead of an unhelpful duplicate-key trace. Also fixed a stale
  `wf_id` reference in the diagnostic log line.

### Changed
- **ComfyUI-Manager network chatter is silenced for all users.**
  `src/main.js` now ensures `<install_dir>/comfyui/user/__manager/
  config.ini` has `network_mode = offline` and `db_mode = local`
  before every ComfyUI spawn. Pre-seeds the file on fresh installs;
  patches drifted values on existing installs. Eliminates the
  startup "FETCH ComfyRegistry Data: N/146" burst and all egress to
  `api.comfy.org` from inside ComfyUI, matching the fully-local
  design rule. None of Manager's network features were used by Comfy
  Labs anyway (custom nodes pinned in bootstrap, models served from
  our own catalog mirror, users never see ComfyUI's UI).
- **Ortho Views: better error visibility.** Pipeline errors now
  persist across the post-Generate rerun (stashed in session state
  and rendered at the top of the page) instead of flashing for a
  millisecond before being wiped. Also dropped the orphan composite-
  preview expander that referenced a `_composite.png` file that
  graph.py never actually wrote, and aligned the "Final Sheet"
  display path with what `_write_sheet` actually emits.

## [0.2.48] - 2026-05-16

### Added
- **Concept Magic add-on can now request Z Image Base over HTTP.** Both
  `/v1/generate` and `/v1/generate/lora` (plus their legacy un-prefixed
  aliases) accept a new `variant` form field, with allowed values
  `z_turbo` (default, distilled, 9 steps) and `z_base` (non-distilled,
  42 steps cfg=3). The bridge forwards the selection to the
  concept-magic / concept-magic-lora plugin graph builders, which pick
  the matched diffusion model + ControlNet + LoRA loader topology.
  Omitting the field preserves prior behavior. Invalid values return a
  400 with a clear error. Bridge OpenAPI version is now 3.1; the
  `/v1/health` `version` field still tracks the app version, which
  add-on clients use for compatibility checks.

### Changed
- **cm_bridge generation now goes through the plugin graph builders.**
  The previously inlined Concept Magic / Concept Magic LoRA workflow
  JSON in `python/cm_bridge/concept_magic_docker.py` has been replaced
  with calls to `python/workflows/concept-magic/graph.py` and
  `python/workflows/concept-magic-lora/graph.py`. Side-effect: per-
  workflow graph updates pushed via the catalog mirror now also reach
  the Blender add-on path without requiring an app rebuild, in line
  with the "per-workflow updates are the architectural goal" rule.
- **Behavior change for `/v1/generate/lora` callers using
  `lora_clip_strength`.** The plugin graph builder uses a single LoRA
  strength for both model and CLIP on z_base (or model-only via
  `ZImageTurboLoraLoader` on z_turbo), so the `lora_clip_strength`
  form field is now accepted but ignored. The legacy `flags` field is
  also accepted-and-ignored. A NOTE line is logged when either is
  non-default so the divergence is visible in the bridge log.

### Fixed
- **cm_bridge access log no longer spams `/v1/active-generation` 200 OK
  lines.** The `_SilencePollingAccessFilter` in
  `python/cm_bridge/api_server.py` was gating on `" 200 OK"`, but
  uvicorn formats the access record as `'%s - "%s" %d' % (addr,
  request_line, status)` and the reason phrase ("OK") is appended later
  by `AccessFormatter`. The substring was never present at filter time,
  so every record passed through unfiltered. Dropped the status gate;
  polling endpoints are now silenced regardless of status code. Real
  generation requests still log normally.

## [0.2.47] - 2026-05-16

### Fixed
- **Concept Magic Blender add-on now sees LoRAs on non-default install paths
  (W:\, E:\, etc.).** `cm_bridge` was hardcoded to scan `D:\models\loras`,
  so users who installed Comfy Labs outside `D:` and dropped `.safetensors`
  files into `<install>\models\loras\` got an empty LoRA dropdown in
  Blender even though ComfyUI itself loaded fine. Two changes:
  (1) `python/cm_bridge/api_server.py` now derives the LoRAs directory from
  `COMFY_LABS_LORAS_DIR` -> `MODELS_DIR\loras` -> legacy `D:\models\loras`,
  in that precedence order; (2) `src/main.js` explicitly sets
  `COMFY_LABS_LORAS_DIR` on the cm_bridge subprocess from the
  wizard-/Prefs-picked models folder, so the Prefs value always wins over
  any OS-level workaround a user may have set.
- **Existing installs need no action** -- the next app launch reads the
  already-saved `models_folder` from config and forwards it. Original
  incident: 2026-05-15 user report.

### Added
- **Preferences -> Models folder.** New path picker in the Preferences
  dialog that surfaces the wizard-picked `models_folder` so users can
  relocate models after install without re-running the wizard. Empty value
  resets to the wizard default. Changing the field shows an amber
  "Restart Comfy Labs after saving" hint, since cm_bridge, ComfyUI, and
  Streamlit all read the path at subprocess startup.
- **Inpaint Image auto-installs `streamlit-drawable-canvas` on first
  open.** Pre-0.2.46 venvs (which survive a ZIP update intact) used to
  show a misleading "needs Comfy Labs 0.2.46+" error when opening
  Inpaint Image, even though the user was already on >=0.2.46. The
  plugin now calls a new `WorkflowAPI.ensure_pip_package()` helper at
  the top of render() that pip-installs the missing package into the
  bundled runtime with a spinner, then reruns -- no app reinstall
  needed. Failed installs surface an actionable error with the manual
  `pip.exe` fallback command. The helper is plugin-facing API, so any
  future workflow that introduces a new dependency can opt-in with one
  line.

## [0.2.46] - 2026-05-15

### Added
- **Inpaint Image workflow plugin.** New plugin that runs Flux Fill Dev
  (same model as Extend Image -- shares the ~12 GB weights) on a
  user-painted brush mask. UI: 800px-wide canvas preview, freehand brush
  with adjustable size (5-150px), Clear mask button, prompt text area
  (optional -- empty works well for object removal), seed + Lock, and an
  Advanced expander with Guidance / Steps / Mask feather / Mask grow.
  Result panel mirrors Extend Image: Download PNG + Use as new source for
  iterative refinement (fix face, then hands, then background, etc.).
  Output naming: `<source>_inpaint_<seed>.png`.
- **`streamlit-drawable-canvas` >= 0.9.3** added to
  `python/requirements.txt`. New plugin depends on it for the brush
  canvas; existing users get it automatically on the next app bootstrap
  (or via `pip install -r requirements.txt` against the bundled runtime).
  Plugin imports the package behind a try/except so a stale venv shows a
  helpful "needs Comfy Labs 0.2.46+" error instead of crashing.
- **`min_app_version: "0.2.46"`** declared in the inpaint-image manifest
  and bundled catalog entry, so older installs that see the workflow in
  the catalog mirror won't be offered the install.

### Changed
- **Sidebar grouping for Flux Fill plugins.** Both Extend Image and Inpaint
  Image now share `category: Editing` with `display_order: 10` / `20`
  respectively, so they appear adjacent in the workflows sidebar (Extend
  directly above Inpaint) instead of being alphabetically interleaved with
  unrelated plugins. Cosmetic only -- no behavior change.

### Z Image base as opt-in pack (4 workflows)
- **Z Image base diffusion model (`z_image_bf16.safetensors`, ~12 GB) is now
  an opt-in model pack across Text to Image, Image to Image, Concept Magic,
  and Concept Magic LoRA.** Non-distilled sibling of Z Image Turbo; higher
  quality, slower (more sampling steps and real CFG). Shares the bundled
  `qwen_3_4b` text encoder and `ae.safetensors` VAE with Z Turbo, so the
  pack only adds the diffusion model (and, for the ControlNet workflows,
  the matching `Z-Image-Fun-Controlnet-Union-2.1.safetensors` -- the Turbo
  ControlNet does not work against the non-distilled weights).
- **Concept Magic + Concept Magic LoRA: Advanced settings expander on
  both Z Turbo and Z Base.** Surfaces Steps / CFG / Scheduler /
  ModelSampling shift with per-variant defaults (Turbo: 9 / 1 / beta /
  1.73; Base: 42 / 3 / normal / 3, matching the reference workflow
  `ConceptMagic-zbase-CannyControl-LoRA.json` under ComfyUI Easy
  Install). Variant-scoped widget keys so flipping the model picker
  doesn't bleed Turbo's overrides into Base or vice versa. Each control
  carries a tooltip explaining the per-variant useful range.

### Fixed
- **Concept Magic LoRA + Z Image base now actually applies the LoRA's
  style.** Two bugs in the initial Z Base wiring:
  (1) used `ZImageTurboLoraLoader` (which converts base-format LoRAs to
  Turbo's QKV-fused layout) instead of the standard `LoraLoader`, so the
  LoRA loaded but landed in the wrong layout for the non-distilled model;
  (2) `CLIPTextEncode` was wired to the raw `CLIPLoader` output rather
  than the LoRA's CLIP output, so the text-side adapter weights never
  reached the prompt encode -- the model took the LoRA but ignored its
  style. Both fixed: z_base path now uses standard `LoraLoader` with
  both MODEL + CLIP outputs wired through.
- **DetailDaemon works on Z Base now.** Unified the sampler tail for both
  variants on `SamplerCustomAdvanced`: z_turbo keeps `BasicGuider` (cfg
  implicit 1), z_base uses `CFGGuider` (explicit cfg + zeroed negative
  conditioning). DetailDaemon wraps the `KSamplerSelect` output, which
  both chains consume.
- **Workflow updates badge no longer fires for stale `<models>/.workflows/`
  installs in dev mode.** `workflow_manager.get_workflow_dir()` now
  prefers the bundled (`python/workflows/`) copy over the host-volume
  copy when `COMFY_LABS_DEV_MIRROR=1`, mirroring how `fetch_catalog`
  already prefers the repo in dev. Production behavior unchanged.

### Changed
- **Concept Magic LoRA: Control type default Depth -> Canny.** Matches
  Concept Magic's default. Affects the single-variation path; the
  multi-variation path still splits 50/50 canny+depth.
- **Concept Magic LoRA: LoRA strength default flips with the variant.**
  1.75 for Z Turbo (turbo distillation often wants stronger LoRA);
  1.0 for Z Base (reference workflow default; the LoRAs were trained
  against Z Image base, so 1.0 is the neutral strength).
- **Concept Magic LoRA: per-variant enhancer checkboxes.** Z Turbo gets
  "Bypass Seed Variance" as before; Z Base gets "Use Detail Daemon"
  (default off). SeedVarianceEnhancer is a Turbo-tuned conditioning hack
  and isn't applied on Z Base.

### Workflow plugin versions
- extend-image: 1.0.4 -> 1.0.5 (publish via `tools/publish_workflow.py
  extend-image --bump patch` -- manifest-only change for sidebar grouping)
- concept-magic: 1.0.2 -> 1.0.3 (z_base opt-in pack + advanced expander)
- concept-magic-lora: 1.0.3 -> 1.0.4 (z_base LoRA + CLIP wiring fix,
  CFGGuider sampler chain, advanced expander, Canny default)
- text-to-image: 1.0.2 -> 1.0.3 (z_base opt-in pack)
- image-to-image: 1.0.2 -> 1.0.3 (z_base opt-in pack)

### Tests
- `test-evidence/test_zbase_lora.py` -- 81-check Python battery covering
  every variant combo for Concept Magic / Concept Magic LoRA and a
  reference-workflow diff against
  `ConceptMagic-zbase-CannyControl-LoRA.json` (verifies UNETLoader,
  ControlNet, LoRA loader class, CLIP wiring, shift, steps, cfg,
  sampler, scheduler, denoise all match upstream).

## [0.2.45] - 2026-05-15

### Added
- **Extend Image: "Use as new source" button.** New primary-blue button next
  to Download PNG on the result panel. One click promotes the outpainted
  result to be the new source so the user can immediately extend again --
  no download / re-upload dance. Padding values, prompt mode, custom prompt,
  and Advanced settings all carry over (the common case is "extend by
  another 256px on the same side"). Seed regenerates unless Lock is set.
  This is the supported pattern for large extensions: Flux Fill Dev
  degrades when asked to invent a lot of pixels in one pass, so the
  recommended workflow is 256px-at-a-time iteration.

### Workflow plugin versions
- extend-image: 1.0.3 -> 1.0.4 (publish via `tools/publish_workflow.py
  extend-image --bump patch` to ship to existing users via the catalog
  mirror without an app update)

## [0.2.44] - 2026-05-14

### Fixed
- **Workflow updates badge no longer fires in dev.** The status-bar badge
  was sha-comparing bundled `python/workflows/<id>/manifest.yaml` against
  the public catalog mirror unconditionally, so every local workflow edit
  showed up as "N updates available" -- even though Streamlit's own
  catalog builder already short-circuits to the local source in dev via
  `COMFY_LABS_DEV_MIRROR`. `checkWorkflowUpdates` now matches that
  behavior: skip the check when `!app.isPackaged` and no
  `COMFY_LABS_BASE_URL` override is set. Set `COMFY_LABS_BASE_URL` to
  re-enable for staging-mirror testing.

### Added
- **Extend Image: Manual expansion mode.** Third option alongside
  Percentage and Aspect Ratio. Four pixel inputs (Top / Right / Bottom /
  Left, 0--2048 each, step 8) let users specify asymmetric outpainting
  -- e.g. extend only the top, or stretch wide on the right. Output
  dimension caption updates live. Defaults to 256px per side.
- **Extend Image: Advanced settings expander.** Three knobs that previously
  required code edits are now user-tunable:
  - **Guidance** (1.0--20.0, default per prompt mode): editable in all three
    prompt modes instead of locked to the preset value. Default flips with
    the mode (10 for No prompt / Auto-describe, 5 for Anti-hallucination)
    but the user can override.
  - **Steps** (15--40, default 20): more steps help with complex content
    (architecture, detailed textures) where the previous fixed 20 sometimes
    left visible artifacts at the seam.
  - **Edge feather** (10--80px, default 40): controls the blend width
    between source and outpainted area. Smaller for sharper edges, larger
    when the previous fixed 40 left visible seams or hard transitions.

### Workflow plugin versions
- extend-image: 1.0.2 -> 1.0.3 (publish via `tools/publish_workflow.py
  extend-image --bump patch` to ship to existing users via the catalog
  mirror without an app update)

## [0.2.43] - 2026-05-12

### Fixed
- **Image to Video RIFE source verified.** Switched the `postprocess_rife`
  model entry from a guessed `hzwer/Practical-RIFE / rife426.pkl` URL to
  the canonical `Comfy-Org/frame_interpolation /
  frame_interpolation/rife_v4.26.safetensors` (22.7 MB). Verified by
  downloading + running the post-process graph end-to-end against the
  new filename. The graph.py reference was updated to match.
- **All 11 Image to Video model URLs are now byte-size-verified** against
  HuggingFace's `X-Linked-Size` header. The WAN 4 (~24.7 GB), LTX 4
  (~42.2 GB), and post-process 3 (~3.9 GB) all resolve cleanly and match
  the manifest's declared `size_bytes`. Fresh users can install any pack
  via the workflow page's "Download" affordance with no dead links.

### Workflow plugin versions
- image-to-video: 1.0.0 -> 1.0.1

## [0.2.42] - 2026-05-12

### Added
- **Image to Video workflow plugin.** New plugin with two generators behind a
  pack picker -- LTX 2.3 (22B distilled, ~42 GB pack, faster, 25 fps native)
  and WAN 2.1 I2V 480p (~24.7 GB pack, slower, 16 fps native, better motion).
  Single-page state machine: upload an image, write a prompt, pick a length
  in seconds (1-5 LTX / 1-10 WAN), Generate. Resolution is model-native
  (~480p) with aspect taken from the input image. After generation, a
  second stage unlocks for RIFE 2x frame interpolation + SeedVR2 spatial
  upscale to 720p / 1080p / 1440p / 4K, always writing two MP4s (native fps
  + 30 fps timeline). 4K shows an OOM warning. All three packs (LTX / WAN /
  post-process) install on demand from inside the workflow page.
- **`api.find_output_videos()`** in WorkflowAPI -- scans `gifs`
  (VHS_VideoCombine) and `videos` (SaveVideo) keys in workflow outputs,
  mirroring `find_output_images`. Existing `api.download_image` already
  handles MP4 downloads via ComfyUI's `/view` endpoint.
- **Stock-only video nodes.** The plugin uses ComfyUI core's `LoadVideo` /
  `GetVideoComponents` / `CreateVideo` / `SaveVideo` (>=0.20) and core
  `FrameInterpolate`. SeedVR2 (upscale) is already bundled. No new
  custom_nodes pack needed for Image to Video.

### Workflow plugin versions
- image-to-video: NEW -> 1.0.0

## [0.2.41] - 2026-05-11

### Fixed
- **`extra_model_paths.yaml` now lists multiple paths per ComfyUI folder
  type.** `clip` resolves both `clip/` and `text_encoders/`; `diffusion_models`
  resolves `diffusion_models/` plus its `flux/` and `qwen/` subdirs. Lets
  Comfy Labs point at a stock ComfyUI-Easy-Install models tree (which keeps
  flux/qwen files flat and Mistral/Ministral CLIPs under `text_encoders/`)
  without manual junctions. v0.2.40 QA hit this when switching to an
  ComfyUI-Easy-Install path -- flux1 / flux2 / ernie i2i all failed with
  `value_not_in_list`.
- **Workflow graph builders drop the `flux/` and `qwen/` subdir prefixes
  from `diffusion_models` filenames.** Now `flux1-dev-fp8.safetensors` and
  `z_image_turbo_bf16.safetensors` resolve against either curated or flat
  layouts via the new YAML mapping above. Affects text-to-image,
  image-to-image, concept-magic, concept-magic-lora, make-pbr-tileable-textures.
- **Updates badge now attaches the BrowserView before navigating.** When the
  user was on Home, the "N workflow updates available" status-bar link
  changed the Streamlit URL but the BrowserView stayed detached -- click
  felt like a no-op. Now also calls `showStreamlit()` first.
- **Image to 3D Mesh shows an upfront warning for RGB inputs.** Trellis2's
  preprocess step indexes the alpha channel and raises an obscure
  `IndexError: index 3 is out of bounds for axis 2 with size 3` on images
  without transparency. The UI now detects this before queueing and points
  the user at the "Remove background before processing" checkbox.
- **Create Ortho Views writes atomically.** Uploaded image saves and the
  composite "sheet" output now write to a `.tmp` and rename, so a parallel
  reader cannot observe a 0-byte / half-encoded PNG mid-write.
- **Ctrl+Enter hint under every prompt textarea.** Streamlit's `st.text_area`
  only commits to session state on blur or Ctrl+Enter; typing alone leaves
  the Generate button reading stale state. Added a small caption to
  Text to Image, Image to Image, Concept Magic, Concept Magic LoRA, and
  Extend Image so users know how to apply their text.

### Workflow plugin versions
- text-to-image: 1.0.1 -> 1.0.2
- image-to-image: 1.0.1 -> 1.0.2
- concept-magic: 1.0.1 -> 1.0.2
- concept-magic-lora: 1.0.2 -> 1.0.3
- make-pbr-tileable-textures: 1.0.1 -> 1.0.2
- image-to-3d-mesh: 1.0.1 -> 1.0.2
- create-ortho-views: 1.0.1 -> 1.0.2
- extend-image: 1.0.1 -> 1.0.2

## [0.2.40] - 2026-05-11

### Added
- **CFG slider in Text to Image and Image to Image.** Sits directly
  under the prompt because that's what it controls. Per-variant
  defaults: 1.0 for Z Image Turbo / Flux 2 Dev / ERNIE Turbo / Flux 1
  Dev t2i (these are distilled / flow-matching models where higher
  CFG is usually counterproductive); 4.5 for Flux 1 Dev img2img
  (matches the upstream reference workflow).
- **Latent batch in Image to Image.** Produces N variations of the
  same source image in a single sampling pass via a `RepeatLatentBatch`
  node between VAEEncode and KSampler. Same per-variant caps as
  Text to Image (Z Turbo / ERNIE up to 4, Flux 1 fp8 up to 2,
  Flux 1 fp16 / Flux 2 capped at 1 to keep VRAM in check). Restores
  feature parity with Text to Image that v0.2.39 missed.

### Changed
- **Image to Image control order: prompt -> CFG -> denoise -> resolution
  -> batch + seed.** The two prompt-affecting sliders now live with
  the prompt; resolution and batching live with the run config.

## [0.2.39] - 2026-05-11

### Added
- **Image to Image workflow.** Re-imagine an uploaded image with a
  text prompt, picking from the same four diffusion-model variants
  as Text to Image (Z Image Turbo / Flux 1 Dev fp8|fp16 / Flux 2 Dev
  / ERNIE Turbo). Includes a **denoise slider** (0 = keep source,
  1 = ignore source entirely) defaulted per variant to match the
  upstream reference workflows (Z Turbo / Flux 2 / ERNIE 0.75-0.78,
  Flux 1 0.5). Same aspect-lock W/H inputs, queue batching, and
  rolling ETA as Text to Image. The optional model packs are
  **shared with Text to Image** -- installing a pack from either
  plugin satisfies the other (same file paths on disk).

  Verified end-to-end against the bundled Comfy Labs ComfyUI:
  16 / 16 tests pass (4 t2i variants + 4 i2i variants +
  remove-background + upscale-image + concept-magic +
  concept-magic-lora + extend-image + create-ortho-views +
  make-pbr-tileable-textures + image-to-3d-mesh).

## [0.2.38] - 2026-05-11

### Added
- **Reusable `api.render_model_install_ui(label, models, state_key, ...)`
  helper for any workflow that needs to install multiple model files.**
  Renders a "Files needed" expander plus two options: (1) scan an
  existing folder recursively and symlink any matches we find (no
  download, no copy -- handy when the user already has the files in
  a separate ComfyUI install) and (2) download from the source
  declared in the manifest. Used by Text to Image for each opt-in
  diffusion-model pack (Flux 1 fp8/fp16, Flux 2, ERNIE); future
  workflows with multi-file model bundles can call the same method
  in one line. Bumps Text to Image's `min_app_version` to 0.2.38.
- **Browse button next to the "Path to scan" input.** Opens
  Electron's native folder picker via a new `POST /pick-folder`
  MCP route, returns the chosen absolute path, and writes it into
  the text input -- no more typing or copy-paste-from-Explorer
  gymnastics. New `api.pick_folder(title, default_path)` Python
  helper any plugin can call. Returns empty string when the bridge
  isn't reachable (test harnesses, older shells) so callers can
  fall back to the typed path. A "Tip: Shift+Right-click a folder
  in Explorer -> Copy as path" caption stays under the input as a
  belt-and-suspenders option.

### Fixed
- **ComfyUI bumped to `1eeaf23f` (v0.20.1 + post-release commits) for
  ERNIE Image Turbo support.** PR #13369 ("Implement Ernie Image
  model") landed after our previous pin `b202f842`, so the bundled
  ComfyUI didn't have `comfy/text_encoders/ernie.py` -- when the
  text-to-image plugin's ERNIE graph loaded `ministral-3-3b.safetensors`
  via `CLIPLoader type=stable_diffusion`, ComfyUI fell back to the
  Llama2 architecture and crashed with `Error(s) in loading state_dict
  for Llama2`. The new pin also pulls in the matching pip dep updates
  (comfyui-frontend-package 1.43.18, comfyui-workflow-templates 0.9.73,
  comfy-aimdo 0.3.0, SQLAlchemy>=2.0). Torchaudio pinned to
  `2.8.0+cu128` to match the rest of the torch stack -- the newer
  ComfyUI moved `audio_vae` into `comfy/sd.py`'s import chain, so a
  drifted torchaudio (previously a silent WinError 127) now blocks
  ComfyUI startup. **Verified all 12 test cases against the bundled
  Comfy Labs ComfyUI on a private port: 4 text-to-image variants
  (z_turbo / flux1_fp8 / flux2 / ernie), remove-background,
  upscale-image, concept-magic, concept-magic-lora, extend-image,
  create-ortho-views, make-pbr-tileable-textures, image-to-3d-mesh
  all run end-to-end on the new pin.** Users with an existing
  install will get the new code via the existing ComfyUI directory
  on next Stop / Start ComfyUI from the toolbar.

- **Scan / link no longer leaves broken symlinks in place.** Two
  Windows-specific bugs were producing the same user-visible
  symptom ("Linked N of N" then immediately "still missing"):
  (1) past HF `snapshot_download` attempts could plant dangling
  links in `<MODELS_DIR>/<subdir>/<file>` pointing at blob hashes
  that no longer exist -- `os.path.exists` returns False for those
  but `lexists` is True, so the link was invisible to the "already
  exists" check yet still blocked subsequent `os.link` /
  `shutil.copy2` with `FileExistsError`. (2) Even after we cleaned
  up the broken link and called `os.link(source, dest)`, the source
  itself is typically a symlink in HF's cache layout
  (`snapshots/<commit>/<file> -> ../../blobs/<hash>`). On Windows
  `os.link` hardlinks the symlink itself, not the target -- the
  dest inherits the symlink's relative `../../blobs/<hash>` reparse
  path, which doesn't resolve from the new location, so it's
  broken from the moment it's created. `link_found_models` and
  `_hardlink_tree` now `os.path.realpath` the source before
  hardlinking and refuse the link (falling back to copy) if the
  result is somehow still a symlink. Existing broken links in the
  user's models dir are auto-cleaned on the next Scan.
- **cm_bridge access log no longer floods with `/v1/active-generation`,
  `/v1/process-status`, and `/v1/health` polling lines.** The Concept
  Magic Blender add-on hits these endpoints on a tight loop while
  open, generating dozens of `200 OK` lines per minute. A
  `logging.Filter` on `uvicorn.access` drops successful 200s for
  those specific paths only; real request activity and any non-200
  responses still log normally.

### Changed
- **Resolution preset buttons replaced with a modal aspect-lock
  radio.** Pick "Free" for independent W/H or one of 1:1, 3:2, 2:3,
  16:9, 9:16. While a ratio is locked, adjusting Width auto-recomputes
  Height (and vice versa) on the 16-pixel grid -- you can't accidentally
  drift off-ratio. Switching INTO a locked mode keeps the current W
  and snaps H to match, so your size stays in the same ballpark.
  Also fixes the v0.2.37 bug where the old preset buttons wrote to
  shadow session-state keys instead of the widgets' own keys, so they
  visually did nothing and every generation came out 1024x1024.
- **Dev-mirror catalog no longer pulls in previously-installed
  workflows from the user's data folder.** When running unpackaged
  (`npm start`) with `COMFY_LABS_DEV_MIRROR=1`, the catalog is built
  only from `python/workflows/` in the repo, not also from
  `<MODELS_DIR>/.workflows/`. Means a plugin removed from the repo
  (e.g. `text-to-concept-art` after the v0.2.36 rename) actually
  disappears from the dev UI instead of lingering from a past
  mirror install. Packaged builds and the offline-fallback path
  still enumerate both dirs.

## [0.2.37] - 2026-05-11

### Added
- **Dev-mirror mode for in-repo plugin iteration.** When Comfy Labs is
  launched from source (`npm start`, i.e. `!app.isPackaged`), Electron
  sets `COMFY_LABS_DEV_MIRROR=1` in every Python subprocess. The
  workflow manager then builds the catalog from `python/workflows/`
  on disk and skips the filedn / cw4me HTTP fetches entirely. Means
  plugin changes show up immediately in `npm start` without needing
  `tools/publish_workflow.py` first. Packaged installer builds get
  the public mirror as before -- nothing changes for end users.
- **`min_app_version:` manifest field with install-time + UI gate.**
  Plugin manifests can now declare the minimum Comfy Labs shell they
  need (e.g. `min_app_version: "0.2.36"` on Text to Image, which
  calls `api.find_output_images` + `workflow_manager.get_optional_models_for_workflow`
  -- both added in v0.2.36). When a user on an older Comfy Labs
  browses the catalog, the workflow card replaces the Install button
  with a red "Needs Comfy Labs >= X.Y.Z" badge and a tooltip telling
  them to update the app first; `install_workflow()` also refuses
  programmatically, so nothing can sneak through.

### Changed
- **Comfy Labs version is now exposed to Python via `COMFY_LABS_VERSION`**
  (already set by Electron, now consumed by `workflow_manager.app_version()`).
  Used for the `min_app_version` comparison; empty string outside Electron
  (test harnesses) bypasses the gate so scripts don't trip on missing metadata.

## [0.2.36] - 2026-05-11

### Added
- **Text to Image workflow with four diffusion-model variants.** Single
  workflow page hosts a radio picker: Z Image Turbo (bundled, fast,
  6 GB), Flux 1 Dev fp8 (~27 GB with shared text encoders), Flux 1
  Dev fp16 (~33 GB, marginal quality bump over fp8), Flux 2 Dev
  (~20 GB), and ERNIE Image Turbo (~21 GB). Z Turbo is always
  available; the other three packs are opt-in installs surfaced
  inline when the corresponding files aren't on disk. All sources
  are Comfy-Org Hugging Face repackages -- no auth, no manual flow.
- **Per-model VRAM-aware latent batch ceilings.** The latent-batch
  input is capped at 4 for Z Turbo / ERNIE, 2 for Flux 1 Dev fp8,
  and 1 for Flux 1 Dev fp16 / Flux 2 Dev so users on smaller cards
  can't OOM the bigger models.
- **Queue batching with live ETA.** Queue up to 1000 runs. Each run
  uses a fresh seed (start seed user-settable, 0 = random). A rolling
  per-image timing average is shown after the first run finishes, so
  the ETA gets sharper as the batch progresses. Per-model timing is
  remembered across runs.
- **Resolution presets + free-form W/H.** Five preset buttons (square,
  2:3, 3:2, 9:16, 16:9) feed two number inputs snapped to 16-pixel
  steps; min 256, max 2048.
- **`optional_models:` field in workflow manifests.** Workflows can
  now declare alternate model bundles that are not required for the
  plugin to function; they don't count against `check_workflow_ready`
  and the plugin's own UI decides when to surface a download
  affordance. First used by Text to Image for the Flux / ERNIE packs.

### Changed
- **Renamed `text-to-concept-art` to `text-to-image`** (plugin id +
  display name). The old plugin shipped only Z Image Turbo on a white
  background with automatic background removal; the new plugin is
  general-purpose, drops the RMBG step and the negative-prompt field,
  and adds the model picker described above. Existing users will see
  the old plugin disappear from the catalog after upgrading Comfy
  Labs and can install the new one from Settings.

### Removed
- **Background-removal step in the text-to-image flow.** Use the
  standalone Remove Background workflow if you still need
  transparent PNGs for 3D modelling.

## [0.2.35] - 2026-05-10

### Changed
- **Compact workflow buttons in the sidebar.** Tightened vertical
  padding and line-height on Streamlit's default buttons (scoped to the
  sidebar only) so more workflows fit on screen as the catalog grows.
  No layout change, just less wasted space inside each button.

## [0.2.34] - 2026-05-10

### Added
- **Manual-download fallback for gated upstream models (infrastructure
  only -- no model is flagged gated yet).** Workflow manifests can now
  carry `source: manual` on a model entry, alongside a `sha256` and a
  `manual_download:` block with `url`, `reason`, and optional
  `instructions`. When the first-run wizard or the Settings "Download
  models" button hits one of these, Comfy Labs renders a guided modal
  instead of attempting an HTTP fetch: it shows the exact destination
  path (so you can paste it into your browser's Save dialog), an "Open
  download page" button that hands you off to the upstream license page
  in your default browser (where you're already signed in), and an
  "I've downloaded it -- verify" button that SHA256-checks the file you
  placed. On hash mismatch the modal explains the common failure modes
  (wrong file, interrupted download, LFS pointer text instead of
  weights) and asks you to redownload. A "Skip workflow" button drops
  any workflow depending on the gated file from the current run rather
  than crashing later. Verified files get a small `.sha256ok` sidecar
  marker so subsequent launches don't re-hash multi-GB weights on every
  rerun.

### Changed
- **Catalog mirror policy clarified: gated artifacts are never mirrored
  to filedn / cw4.me.** Redistributable wheels, LoRAs, and public model
  weights continue to be mirrored as before; gated weights (HF
  license-gated, CivitAI throttled, etc.) are intentionally excluded
  and reach users only via the manual-download flow above.

## [0.2.33] - 2026-05-09

### Changed
- **User Manual refresh: tightened toward end-user perspective only.**
  Removed the "Dev repository" section in Appendix A (the
  `D:\GitHub\comfy-lab\` source-tree layout) since the manual is for
  people running the installed app, not contributors. Dev-only path
  references (`<repo>/public/debug_logs/app.log`,
  `python/cm_bridge/CM_BRIDGE_API_v1.md` "in the repo") replaced with
  their installed-app equivalents (`%APPDATA%\Comfy Labs\app.log`,
  `<app folder>\resources\python\cm_bridge\CM_BRIDGE_API_v1.md`).
  Desktop-shortcut tip now points at Windows' native "Send to ->
  Desktop (create shortcut)" instead of the in-repo
  `create-shortcut.ps1` helper which doesn't ship in the install ZIP.

## [0.2.32] - 2026-05-09

### Changed
- **`make-pbr-tileable-textures` migrated to the per-plugin `graph.py`
  pattern -- the last legacy plugin done.** All 8 originally-legacy
  workflow plugins now own their graph builders in
  `python/workflows/<id>/graph.py` and ship updates via the catalog
  mirror without an app rebuild. PBR was the largest migration:
  ~810 lines consolidating the five pipeline entry points
  (generate_diffuse, apply_lighting_correction, outpaint_texture,
  convert_to_pbr, upscale_to_4k) and their graph builders, previously
  spread across `comfy_lab.py`, `txt_to_texture.py`, and `zoom_out_2x.py`.

  Pure-PIL utilities (make_tiled_mirror, make_tileable_pil,
  smart_crop_to_square, apply_adjustments, make_histogram,
  has_non_default_adjustments, make_tile_preview) intentionally remain
  as `api.*` calls -- they're shared helpers, not workflow-specific
  graph builders, and don't benefit from per-workflow shipping.

  Manifest: `make-pbr-tileable-textures` 1.0.0 -> 1.0.1.

## [0.2.31] - 2026-05-09

### Added
- **`GET /v1/loras` endpoint on cm_bridge -- LoRA picker support for the
  Concept Magic add-on.** Returns a JSON array of
  `{filename, display_name, trigger_word}` for every `.safetensors` file
  actually installed in `D:\models\loras\`. The add-on uses this to
  populate a new LoRA-picker dropdown (one less thing the user has to
  type by hand).

  Metadata source priority: (1) locally cached `index.json` if the
  lora_pack downloader ever writes one; (2) a one-shot remote fetch of
  `https://filedn.com/.../comfy-labs/LoRAs/index.json` (5s timeout, then
  cached for the lifetime of the cm_bridge process); (3) a filename-stem
  heuristic that strips `_zimage_*.safetensors` to recover the trigger.
  In practice (2026-05-09) the remote index has empty `trigger` fields
  for all four shipped LoRAs, so the heuristic is what actually
  populates `trigger_word` -- this is by design (the contract guarantees
  a non-empty trigger). Results are cached in-process and invalidated
  by the loras-dir mtime, so the dropdown survives a restart-free LoRA
  swap.

  Also registered as `/loras` (un-prefixed alias) alongside the other
  Docker-era compat aliases.

### Changed
- **7 of 8 legacy workflow plugins migrated to the per-plugin
  `graph.py` pattern.** Until now most of the workflow library lived in
  the shared `python/comfy_lab.py`, which meant a graph or parameter
  tweak in (for example) `concept-magic` could only reach existing
  users by shipping a full Comfy Labs `.exe` rebuild. After this push
  each migrated plugin owns its graph builder(s) inside
  `python/workflows/<id>/graph.py`, so a plugin patch + manifest bump
  + `tools/publish_workflow.py <id> --bump patch` is enough -- the
  catalog mirror delivers the new version on the user's next workflow
  update check.

  Migrated this round (with manifest bumps):
    - `remove-background` 1.0.0 -> 1.0.1
    - `image-to-3d-mesh` 1.0.0 -> 1.0.1 (full Trellis2 pipeline incl.
      phase-based status callbacks moves into the plugin)
    - `upscale-image` 1.0.0 -> 1.0.1 (carries the v0.2.30 SeedVR2
      shortest-edge axis fix; the fix can now reach existing users via
      catalog mirror without an app update)
    - `create-ortho-views` 1.0.0 -> 1.0.1
    - `concept-magic` 1.0.0 -> 1.0.1
    - `concept-magic-lora` 1.0.1 -> 1.0.2
    - `extend-image` 1.0.0 -> 1.0.1 (both the Flux Fill outpaint and
      QwenVL caption graphs)

  `make-pbr-tileable-textures` is intentionally NOT in this round --
  it's the largest plugin (10 api calls, plus deps in
  `python/zoom_out_2x.py` and `python/txt_to_texture.py`). Migrating it
  is queued as a focused follow-up so the rush doesn't ship a
  regression to the most complex workflow in the app.

  The legacy `comfy_lab.<func>` and `api.<func>` shims are left in
  place. They're no longer called by the migrated plugins but are
  still used by older non-plugin code paths in `Workflows.py`, and a
  flag-day removal is unrelated to delivering this win.

## [0.2.30] - 2026-05-09

### Fixed
- **Upscale Image: scale factor was producing the wrong output size on
  non-square sources.** A 1920x1080 input at `Scale factor = 2.00`
  predicted "Output: 3840x2160" in the UI but actually produced
  6826x3840 -- a 3.55x scale, not 2x. The cm_bridge `/v1/upscale`
  endpoint had the same bug, so Concept Magic add-on upscales were also
  off. Cause: SeedVR2's `resolution` input is the SHORTEST-edge target
  of the output, not the longest, but the code was passing
  `max(w, h) * scale`. After the fix the multiplier mode lands exactly
  on the predicted dimensions and the "Max dimension (longest edge)"
  mode honours its label. The v0.2.30 contract update above ("output
  up to 4096px" for 2048 input) is now actually true; before this fix
  16:9 inputs were running 1.78x past the documented ceiling.

### Changed
- **`/v1/upscale` input limit raised from 1500px to 2048px on the longest edge** (output up to 4096px). This produces a substantially larger result than the hosted Good AI Upscaler tiers (which cap at 2.075 MP input / 8.3 MP output) — intentionally so, since local SeedVR2 doesn't bill credits and "much larger than the hosted ones" is the intended differentiator. On a 5090 a 2048-edge input finishes in roughly 2–3 minutes; slower GPUs approach the 1800s submit_and_wait ceiling but should still complete. Inputs beyond 2048 still get a 400 with the new limit in the message.
- **Streamlit sidebar: ComfyUI Connected/Offline status restored as the
  colored box** (green/red) it was before v0.2.29's compaction. The
  Queue + connections detail stays as the single compact line below it,
  so total height is still small but the at-a-glance ComfyUI state is
  back to its original visual.

### Added
- **Troubleshooting: dedicated section for "A workflow fails with a
  generic error (likely out of VRAM)"** in `docs/Comfy-Labs-User-Manual.md`.
  Covers how to confirm OOM by grepping `View Log` for the known CUDA
  markers, a five-step fix checklist (close other GPU apps, reduce
  output size, drop unused LoRAs, restart ComfyUI, verify the workflow
  fits the card), and an honest note that the app coalesces OOM into
  the generic "Generation failed" banner today -- so the log is the
  source of truth until that surfaces in-app.

## [0.2.29] - 2026-05-09

### Added
- **`POST /v1/upscale` endpoint on cm_bridge — local one-click 2x SeedVR2
  upscale for the Concept Magic add-on, no credits / no network round
  trip.** Multipart `image` field (PNG/JPG), no other inputs; returns raw
  PNG bytes (same shape as `/v1/generate`). All SeedVR2 parameters are
  pinned server-side: scale=2.0, color_correction=`lab`, seed=42, tiled
  VAE on. Inputs whose longest edge exceeds 1500 px are rejected with
  HTTP 400 to keep the output under 3000 px (SeedVR2 tiled-VAE comfort
  zone). Aliased at the un-prefixed `/upscale` for parity with the legacy
  Concept Magic Docker contract.

### Implementation notes
- `python/cm_bridge/upscale_seedvr2.py` (new) holds the workflow builder
  and submit/poll/download helpers, vendored from
  `comfy_lab._build_seedvr2_workflow` and `comfy_lab.seedvr2_upscale`
  (around lines 2057-2216). Copied rather than imported because the
  Streamlit module pulls in `from logger import log` and the non-docker
  `comfyui_api_helper`, neither of which the bridge package wants as a
  dependency (see the isolation note in `concept_magic_docker.py`).
- Timeout model lifted from the Streamlit side:
  `min(1800, max(600, output_pixels // 6000))`, with the same
  `SEEDVR2_TIMEOUT_S` env override (clamped 60..7200).
- Warmup intentionally does NOT touch SeedVR2. The model is large enough
  (~6 GB DiT + VAE) that loading it on every cm_bridge start would push
  VRAM pressure on smaller cards even when the user is only doing
  generation. First upscale request is therefore cold (~2-3 min);
  subsequent ones are fast.
- The handler is wrapped in `_track_generation()` so the status-bar
  "Working" indicator from v0.2.28 lights up during upscales too.

### Changed
- **Streamlit sidebar Queue / connection status compacted to a single
  inline line.** Was two padded info boxes (~120 px tall when ComfyUI
  was connected and idle, which is most of the time on a single-user
  desktop); now one ~20 px line with a small colored dot encoding
  status (green idle, amber busy, red offline). All existing info is
  preserved -- ComfyUI online state, queue running+pending count, the
  WebSocket "connected" count, and the offline "Click Restart ComfyUI
  header above" hint. Auto-refresh cadence (3s) is unchanged.

## [0.2.28] - 2026-05-09

### Added
- **Status bar "Working" indicator while cm_bridge is serving a Blender
  generation request.** Until now the cm_bridge dot at the bottom of the
  Comfy Labs window stayed a static green while a generation could be
  taking 10-30s warm or up to 240s on a cold ComfyUI spawn -- no signal
  that the bridge was actually busy. The dot now stays green for "service
  up" and a small blue spinner + "Working" label appears next to it
  whenever a `/generate*` request is in flight; both vanish the moment
  all in-flight requests complete. Indicator works for `/v1/generate`,
  `/v1/generate/lora`, and `/v1/generate/base64` (and the un-prefixed
  legacy aliases used by shipped Concept Magic add-on builds).

### Implementation notes
- `python/cm_bridge/api_server.py` tracks an `_active_generations` int
  guarded by a `threading.Lock`, exposed via a new
  `GET /v1/active-generation` endpoint (also aliased at
  `/active-generation`). Single-worker uvicorn so a plain int counter is
  sufficient; the three generate handlers wrap their bodies in a
  `_track_generation()` context manager so the counter stays correct on
  exception paths.
- The blocking ComfyUI call inside each generate handler is offloaded
  via `await asyncio.to_thread(...)`. Without this, calling sync
  `generate()` from `async def` froze the event loop for the full
  10-30 s generation, so the supervisor's polls timed out and the
  spinner never saw a non-zero count. (Caught while testing the first
  end-to-end Blender generation against v0.2.28.)
- The Electron supervisor polls `/v1/active-generation` at 1Hz only while
  cm_bridge's lifecycle state is `running`; the timer starts on the
  `stopped/starting -> running` transition and is cleared on any other
  transition. Failures (timeout, connection refused) fall back to
  `busy=false` so a crash mid-generation can't leave a stale spinner.
- New IPC channel `subprocess:activity` (kept orthogonal to the existing
  `subprocess:state` so the lifecycle contract isn't tangled).
  `StatusBar.jsx` subscribes alongside the existing state subscription
  and renders the spinner using Tailwind's `animate-spin` on the same
  primary-blue (`#0D99FF`) used elsewhere in the status bar -- distinct
  from the green/yellow/red lifecycle palette so the user doesn't
  confuse a busy bridge with a starting or crashed one.

## [0.2.27] - 2026-05-09

### Fixed
- **cm_bridge: every Concept Magic generation failed on Windows with
  `prompt_outputs_failed_validation`.** The vendored
  `python/cm_bridge/concept_magic_docker.py` workflow builders had four
  hardcoded forward-slash subpaths -- `qwen/z_image_turbo_bf16.safetensors`
  and `qwen/qwen_3_4b.safetensors` in both `generate()` and
  `generate_lora()` -- submitted verbatim to ComfyUI's `/prompt` endpoint.
  ComfyUI on Windows registers those models with backslash separators
  and validates with string equality, so every request bounced with
  `Value not in list: ...`, cm_bridge returned HTTP 500
  `{"error":"Generation failed"}`, and the add-on surfaced
  "Z Turbo server error: Generation failed". Replaced with literal
  backslashes (Comfy Labs is Windows-only -- no `os.sep` /  `pathlib`
  abstraction needed). The other workflow modules
  (`comfy_lab.py`, `txt_to_texture.py`, `img_to_texture.py`,
  `workflows/text-to-concept-art/graph.py`) already route every model
  path through a `_model_path()` helper that does the same normalization,
  so only the cm_bridge file was affected.

### Notes
- cm_bridge runs without `--reload`, so this fix only takes effect after
  Comfy Labs is restarted (or the cm_bridge Prefs toggle is flipped
  off and back on, which respawns the subprocess).

## [0.2.26] - 2026-05-09

### Fixed
- **cm_bridge: shipped Concept Magic Blender add-on builds (1.2.x, 1.3.x)
  could not reach generation.** PRD section 7 had moved every endpoint
  to `/v1/...` on the assumption that the add-on would ship retargeted
  at the same time, but no such add-on release exists -- every shipped
  copy still calls `http://127.0.0.1:8000/health`, `/generate`,
  `/generate/lora` (un-prefixed). Result: with cm_bridge enabled in
  Prefs, the add-on's first health probe got 404 and surfaced
  "Cannot reach Z Turbo server" before any image upload was attempted.
  Restored the un-prefixed paths as silent compat aliases that share
  the same handler functions as the canonical `/v1/...` routes (no
  wrapper, no drift). New clients should still target `/v1/...`
  directly. Aliases will be removed in a future release once the
  add-on side ships a `/v1`-aware build.

### Notes
- cm_bridge stays opt-in via Prefs (`cm_bridge_enabled`, default off);
  no Electron-side changes. The Blender add-on is responsible for
  telling the user to (1) launch Comfy Labs and (2) enable the
  cm_bridge Prefs toggle when its connection probe fails.
- See `python/cm_bridge/CM_BRIDGE_API_v1.md` and `PRD.md` section 7
  for the updated contract documentation.

## [0.2.25] - 2026-05-07

### Changed
- **App self-update mechanism is now directory-listing-driven.** The
  publisher no longer maintains `version.json` -- the source of truth
  is the set of `ComfyLabs-vX.Y.Z.zip` filenames on the mirror itself.
  On startup the updater fetches the mirror's directory listing,
  scrapes filenames matching the strict semver shape, sorts by version,
  and uses the highest as the candidate update. Pre-release tags
  (e.g. `-rc1`) are deliberately skipped. Release ritual reduces to:
  `cp out/ComfyLabs-vX.Y.Z.zip C:\WebDAV\Comfy-Labs\` and let WebDAV
  sync. RELEASE-NOTES.md is still picked up if present (conventional
  filename next to the ZIPs).

### Migration note
- Existing v0.2.24 and earlier installs use the old `version.json`
  protocol. To bridge them across this transition once, leave a final
  `version.json` on the mirror pointing at v0.2.25 (or whatever
  version first ships with this code) so they receive the update
  notification one last time. After they upgrade to v0.2.25+, the new
  directory-scrape path takes over and `version.json` can be deleted.

## [0.2.24] - 2026-05-07

### Fixed
- **v0.2.23 main process crashed at startup** with
  `ReferenceError: Cannot access 'k' before initialization`. The
  Phase 1e MCP route registration line for `/workflow-installed`
  was placed before the `const MCP_ROUTES = {}` declaration in
  `src/main.js` source order. CommonJS executes statements top-to-
  bottom, so the assignment hit a temporal dead zone and threw
  unrecoverably at module load. Symptoms: Streamlit subprocess
  never spawned (so "Open Workflows" stayed disabled), version IPC
  never registered (so `v0.2.23` never showed in the StatusBar or
  About dialog), MCP bridge never started. Fix: move the
  `MCP_ROUTES['POST /workflow-installed'] = mcpToolWorkflowInstalled`
  line to the same block as the other MCP route assignments, which
  is positioned after the const declaration.

## [0.2.23] - 2026-05-07

### Added
- **Plugin-architecture shell.** `Workflows.py` no longer hardcodes the
  list of workflows. The sidebar nav, workflow descriptions, and step
  routing are now built dynamically from
  `workflow_manager.get_available_workflows()` -- which walks installed
  manifests merged with the catalog. Adding a brand-new workflow is now a
  pure catalog/install operation: the user installs from Settings, and
  the new entry appears in the nav after `st.rerun()` with no app code
  changes. Deletes ~80 lines of hardcoded `WORKFLOWS` dict +
  `_WORKFLOW_NAME_TO_ID` map + `_DOCKER_EXCLUDED` set + `_STEP1_ONLY`
  set.
- **Manifest schema: `display_order` (int, default 100) +
  `category` (str, optional).** Workflows declare their own nav position.
  Lower `display_order` sorts earlier; nav is grouped by `category` then
  ordered by `display_order` then by name. `tools/publish_workflow.py`
  reads these from the manifest and writes them to per-mirror
  `catalog.json` so the order is correct even before install.
- **Low-level `WorkflowAPI` primitives** for plugins that build their
  own ComfyUI graphs:
  - `api.run_workflow(graph_dict, label, timeout)` -- submit + poll
  - `api.upload_image(local_path)` -- upload to ComfyUI input dir
  - `api.find_output_image(result, *node_ids)` -- pull file_info from a result
  - `api.download_image(file_info, dest_path)` -- fetch output to disk
  - `api.run_and_save(graph, output_node_ids, dest_path, label, timeout)` -- combo helper
  - `api.load_sibling("graph")` -- import a sibling module that ships
    alongside `ui.py`, with mtime-based cache invalidation
- **Pilot migration: `text-to-concept-art` -> v1.1.0.** Graph-builder
  code lives in `python/workflows/text-to-concept-art/graph.py`
  (extracted from `comfy_lab.generate_concept_art`). `ui.py` imports
  via `api.load_sibling("graph")` and runs via `api.run_and_save`.
  The Resolution dropdown (1024x1024 / 1280x720 / 768x1280) now drives
  the `EmptySD3LatentImage` node's width/height in the graph -- the
  feature wires through end-to-end, no `comfy_lab.py` change needed.
  Manifest gets `display_order: 10` (first in nav) and
  `category: Generation`.
- **Brand-new workflows are detected and counted by the StatusBar
  badge.** Phase E previously only counted workflows installed but
  out-of-date. The check now also counts catalog entries with no local
  manifest at all (truly new workflows). The badge text adapts:
  "N workflow update(s) available" / "N new workflow(s) available" /
  "N updates + M new available". Click still routes to Settings.
- **Restart-required Electron dialog after workflow install/update.**
  Streamlit's Settings page POSTs to a new `/workflow-installed` MCP
  bridge route; the Electron main process forwards to the renderer's
  custom `Dialog` (via the new `WorkflowInstallRestartBridge`
  component). Different copy for "Workflow installed" (new) vs
  "Workflow updated". User picks Restart now or Later. Mirrors the
  existing onboarding-complete restart pattern.

### Changed
- **`comfy_lab.py` no longer grows for every new workflow.** Existing
  high-level methods (`api.generate_concept_art` etc) remain for
  backward compat with the 8 not-yet-migrated workflows. Future
  workflows go through the plugin path. The eventual cleanup release
  will drop those once every workflow has been migrated.

### Build
- ZIP size unchanged (~200 MB). Plugin-architecture is a code change,
  not a payload change.

## [0.2.22] - 2026-05-06

### Added
- **Per-workflow incremental updates.** Editing one file in a workflow
  plugin (e.g. `workflow.json`) now means only that file is uploaded by
  the publisher and only that file is downloaded by users. Each
  workflow `manifest.yaml` carries a `files:` block listing every
  shippable file with its sha256 + size; `install_workflow()` fetches
  the remote manifest, diffs hashes against local copies, and pulls
  only differing or missing files. Hard-fails on any sha mismatch
  (no silent fallback). Local corruption is detected and repaired on
  the next install.
- **`tools/publish_workflow.py`** -- new author-side tool. Walks
  `python/workflows/<id>/`, hashes every file, writes the `files:`
  block back into `manifest.yaml` (preserving comments via
  ruamel.yaml), bumps version (`--bump major|minor|patch`), uploads
  changed files only to both mirrors (filedn primary + cw4me backup),
  and updates per-mirror `catalog.json` with the new
  `manifest_sha256`. Refuses to publish content changes without a
  version bump.
- **Decoupled LoRA index.** A workflow's `lora_pack` model entry now
  binds to an `index.json` on the mirror via `index_url:` instead of
  scraping a directory listing. Adding or updating a LoRA is now a
  pure content publish that requires NO workflow republish or app
  release. `tools/publish_lora.py` hashes a `.safetensors`, updates
  `index.json` on both mirrors, and uploads the new file (with
  `--replaces` cleanup of the previous filename).
- **`tools/migrate_loras.py`** -- one-shot script that built the
  initial `index.json` from the current LoRAs at
  `P:\Public Folder\comfy-labs\LoRAs\` (4 entries, sha256-hashed) and
  pushed it to both mirrors.
- **Per-workflow content-update badge in the StatusBar.** On launch,
  the Electron main process fetches `catalog.json` from the primary
  mirror (with backup fallback), compares each workflow's
  `manifest_sha256` against the local manifest's sha256, and emits an
  IPC event with the count. The renderer's StatusBar shows
  "N workflow update(s) available" as a clickable link to Settings.
  Independent of the app self-update channel.
- **Smoke tests** -- `tools/test_workflow_install.py` (6 tests:
  catalog fetch, fresh install, idempotent re-install, corruption
  recovery, manifest sha mismatch hard-fail, backward compat) and
  `tools/test_lora_pack.py` (7 tests: index fetch, install state,
  download, idempotent re-install, corruption recovery, sha mismatch
  hard-fail). Both run against a temp http.server serving the real
  local mirror.

### Changed
- **`install_workflow()` now uses versioned URLs**
  (`<base>/<id>/<version>/<file>`), matching the existing mirror
  layout. Falls back to the unversioned path on 404 for compatibility
  with older mirror layouts. Previous code fetched the unversioned
  path despite the mirror being version-folder organised, which made
  the remote install path effectively non-functional (bundled fallback
  was the only working source).
- **Catalog fetches now have a 5-minute soft TTL** so launch-time
  refresh and Settings-page navigation share state without redundant
  HTTP requests.
- **Per-workflow `manifest.yaml` `models:` block is now the single
  source of truth.** `python/models.yaml` (the legacy global registry)
  is removed -- duplicating the same model declarations across both
  files was authoring friction with no upside, since model dedup
  already happens at the filesystem layer (shared `subdir/filename`).
  `model_manager` helpers (`check_workflow_ready`, `scan_for_models`,
  `link_found_models`, `calculate_workflow_download_size`) now take
  models lists directly; new helpers in `workflow_manager`
  (`get_models_for_workflow_ids/names`, `get_workflow_vram_map`,
  `get_all_models_from_manifests`) build those lists by walking the
  manifests. `wizard.py`, `01_Settings.py`, and `main.js`'s
  `checkWorkflowReadiness` Python probe migrated.
- **catalog.json carries `manifest_sha256` per workflow** (additive
  field; v1 catalogs are still tolerated). The client verifies the
  manifest sha256 after fetching but before applying the per-file
  diff; mismatch hard-fails the install. Backup mirror catalog is now
  a byte-for-byte mirror of primary (with `base_url` swapped) instead
  of being independently sparse.

### Fixed
- **`extend-image` and `upscale-image` had drifted**: bundled `ui.py`
  in `python/workflows/<id>/` did not match the mirror's published
  copy. Migration aligned them.
- **cw4me backup mirror was missing `workflows/` and `catalog.json`
  entirely.** It now mirrors primary fully (catalog + 9 workflow
  trees).

### Migration notes
- All 9 bundled `manifest.yaml` files now carry a `files:` block.
  Comment blocks preserved on round-trip (ruamel.yaml).
- `concept-magic-lora` republished at v1.0.1 with the new `lora_pack`
  binding. Older clients (v0.2.21 and earlier) continue to use the
  legacy `url_directory` scrape path, which is kept in `model_manager`
  for backward compat until those clients age out.

## [0.2.21] - 2026-05-05

### Fixed
- **Fresh installs no longer fail at `clone-comfyui` with `[WinError 2]
  The system cannot find the file specified` on machines without git
  on PATH.** Beta tester reported the failure on a fresh 5080 setup
  after `install-flashattn` succeeded -- the bootstrap had already
  burned ~30 min of downloads before tripping. Root cause: bootstrap
  shelled out to the literal string `"git"`, which Windows resolves
  via PATH; on a non-developer install, git isn't there.

### Added
- **Bundled MinGit 2.54.0** (`git-runtime/git-runtime.tar.gz`,
  ~40 MB packed). Same vendoring pattern as the bundled CPython 3.12
  runtime. On first wizard run, `ensureGitRuntime()` in `src/main.js`
  extracts the tarball into `<install_dir>\git-runtime\` via Windows'
  built-in `tar.exe`, and the spawned bootstrap subprocess receives
  `COMFY_LABS_GIT_EXE` pointing at the extracted `git.exe`.
- **`bootstrap.py` resolves git lazily** via the new `git_exe()`
  helper -- prefers `COMFY_LABS_GIT_EXE` (set by Electron), falls back
  to `shutil.which("git")` for developers running the script directly,
  raises a clear actionable error if neither resolves. All five
  hardcoded `["git", ...]` invocations (clone-comfyui, fetch,
  unshallow, custom-nodes clone, pinned checkout) now use the resolved
  path.

### Build
- `forge.config.cjs` ships `git-runtime/` as a fifth `extraResource`
  tree. `git-runtime/SOURCE.md` documents provenance + bump procedure.
  ZIP grows by ~40 MB; net cost 215 MB -> 255 MB.

## [0.2.20] - 2026-05-04

### Changed
- **Install location wizard no longer hard-blocks C:.** Users with a
  single-drive setup (or plenty of free space on C:) can now pick a
  C:\ path for the install. The validator only checks for a valid
  drive-letter path. The non-C: suggestion algorithm
  (`wizard:suggest-install-dir`) is unchanged -- when a roomier
  non-system drive exists with 25+ GB free, the wizard still proposes
  it. Step copy was softened from "Pick a drive other than C:" to
  "A drive other than C: is recommended ... but C: works if you have
  25+ GB free there."

## [0.2.19] - 2026-05-04

### Fixed
- **Concept Magic LoRA workflow no longer fails with "No LoRAs found
  in `D:\ComfyUI-Easy-Install\...`" on installs that aren't on D:.**
  The Python LoRA registry in `python/workflow_api.py`
  (`get_lora_registry`, `scan_loras`) and `python/Workflows.py`
  (`LORA_DIR`) had a hardcoded Docker-era fallback path that fired
  whenever the `LORA_DIR` env var was unset -- which is always, since
  the Electron main process passes `MODELS_DIR` instead. Both spots
  now derive the loras dir as `<MODELS_DIR>/loras`. `LORA_DIR` is
  still honored as an explicit override. No hardcoded fallback (fail
  loud, never fake). Closes the v0.2.12 QA bug.

## [0.2.18] - 2026-05-04

### Fixed
- **Extend Image -> "Generate description" (Auto-describe mode) no
  longer crashes with `ModuleNotFoundError: No module named 'llama_cpp'`.**
  The captioning workflow used the `AILab_QwenVL_GGUF` node, which
  requires a vision-capable `llama-cpp-python` build (e.g. JamePeng's
  fork exposing `Qwen3VLChatHandler`). PyPI's wheel does not include
  vision handlers, and the bootstrap never installed one -- so the
  node failed at load time on every fresh install. Switched to
  `AILab_QwenVL` (HF transformers backend) using
  `Qwen/Qwen3-VL-2B-Instruct` with runtime 4-bit quantization. The
  transformers stack is already installed by the bootstrap; no extra
  wheel hosting required.
- **Auto-describe caption now actually appears in the text area.**
  Even after the GGUF -> HF swap, captions were generated correctly
  server-side but never populated the "Description (editable)" field.
  Root cause was a Streamlit 1.x widget-state pitfall: setting
  `st.session_state[widget_key] = caption` after the widget had been
  instantiated is silently ignored, and `st.session_state.pop(...)`
  doesn't reset Streamlit's internal widget state either. Fixed by
  versioning the text_area's `key` -- every Generate click bumps the
  version, forcing Streamlit to instantiate a fresh widget that
  honors the `value=` parameter.
- **Workflow plugin edits now hot-reload without restarting Comfy
  Labs.** `python/workflow_manager.py:load_workflow_ui()` cached each
  `ui.py` module forever; Streamlit's auto-rerun reloads the main
  script but not modules we `exec_module` ourselves. Cache is now
  keyed on `(ui.py mtime, module)` and re-executes on change.

### Changed
- **Model registry: `qwenvl_gguf` -> `qwenvl_hf_2b` (Extend Image).**
  Removed the 2.8 GB `Qwen3VL-4B-Instruct-Q4_K_M.gguf` download and
  added the HF `Qwen3-VL-2B-Instruct` repo (~4.5 GB). Net +1.7 GB on
  the Extend Image footprint; fits within the existing 16 GB VRAM
  budget after bitsandbytes 4-bit quantization (~1.5 GB peak).
- **Toolbar "Restart ComfyUI" button replaced with a Start/Stop
  toggle.** Shows "Start ComfyUI" (play icon) when the backend is
  stopped or crashed; "Stop ComfyUI" (square icon) when running;
  spinner + disabled while starting. Stop prompts for confirmation
  (frees VRAM, cancels in-flight generations); Start fires
  immediately. The duplicate "Stop ComfyUI" link in the StatusBar
  footer was removed -- the toolbar button is now the single source
  of truth. The crash-recovery path in `CrashPane` still uses the
  underlying `restartSubprocess` IPC.

### Beta-user upgrade notes
- Replace the app with the v0.2.18 ZIP. On the next launch, the
  wizard's model-check pass will download `Qwen3-VL-2B-Instruct`
  (~4.5 GB) into `<models>/LLM/Qwen-VL/Qwen3-VL-2B-Instruct/`.
- The old GGUF file at
  `<models>/LLM/GGUF/Qwen/Qwen3-VL-4B-Instruct-GGUF/Qwen3VL-4B-Instruct-Q4_K_M.gguf`
  is now orphaned. Safe to delete to reclaim 2.8 GB; harmless to keep.

## [0.2.17] - 2026-05-03

### Fixed
- **Streamlit "Start ComfyUI" button no longer fails with WinError
  10061 in packaged builds.** The button called the MCP HTTP bridge
  on port 9876, which is intentionally disabled in packaged builds.
  Removed the in-page button and pointed users at the existing
  toolbar Restart ComfyUI action, which uses the IPC path that has
  always worked in packaged builds.
- **Sidebar ComfyUI connection / queue status no longer goes stale
  until you click something.** Streamlit can't be pushed events from
  Electron, so the sidebar status sat frozen across IPC-driven state
  changes. Wrapped the status block in `@st.fragment(run_every="3s")`
  so it polls `is_running()` on its own without re-running the whole
  page.

### Added
- **`Force-Quit-ComfyUI.bat` / `Force-Quit-ComfyUI.ps1`** -- handles
  the case where Comfy Labs was force-killed and ComfyUI is left
  orphaned holding port 8188 + the GPU. Detects via install_dir +
  port 8188 owner, prompts Y/N, then `taskkill /T /F`. Bundled via
  `forge.config.cjs` postPackage alongside the uninstaller.

### Beta-user upgrade notes
- See `UPGRADING_COMFY-LABS.md` -- in-place upgrade by unzipping next
  to the old folder; install_dir + venv + models + config all stay
  untouched.

## [0.2.16] - 2026-05-02

### Changed
- **Bootstrap now caches downloaded wheels under
  `%LOCALAPPDATA%\Comfy Labs\pip-cache\`.** v0.2.15 and earlier passed
  `--no-cache-dir` to every pip invocation, a Docker-era defensive
  habit that turned every partial-install retry into a full
  redownload. During v0.2.15 QA, a flaky network mid-install forced a
  restart and pip then redownloaded 3.4 GB of PyTorch wheels from
  scratch -- the cache hit now completes the same step in seconds.
  The cache is intentionally separate from the user's global
  `%LOCALAPPDATA%\pip\Cache\` so a "wipe" action can never touch
  wheels owned by other Python installs on the machine. `%LOCALAPPDATA%`
  (Local, not Roaming) is also where pip's own default cache lives,
  so a 4 GB cache never rides a corporate roaming-profile sync.

### Added
- **"Wipe wheel cache now (~X GB)" button in Preferences.** The cache
  is never wiped automatically -- end users keep it forever (it pays
  back on every reinstall and is only ~4 GB after a full install).
  Click the button when you want the space back; it shows the live
  size next to the button label and reports the freed bytes inline.
- **7th item in the standalone uninstaller: "Pip wheel cache".**
  Selected by default, sized live like the other items, only enabled
  when the cache exists.

### Fixed
- **`LICENSES.md` now ships next to `Comfy Labs.exe` in the portable
  ZIP.** Previously only the uninstaller scripts rode along; the
  third-party attribution file lived only in the source tree. The
  `postPackage` hook now copies all three top-level files
  (`Uninstall-Comfy-Labs.bat`, `Uninstall-Comfy-Labs.ps1`,
  `LICENSES.md`) into the artifact root.

## [0.2.15] - 2026-05-02

### Removed
- **Dropped the `Nvidia_RTX_Nodes_ComfyUI` custom node and its
  `nvidia-vfx` dependency.** The `nvidia-vfx` PyPI distribution is a
  `wheel-stub` sdist whose pyproject build hook silently downloads a
  ~490 MB prebuilt wheel during pip's "Preparing metadata" step, with
  zero output. On slow connections this could hang an install for
  many minutes (a single QA run on a degraded link sat at zero
  throughput for 30 minutes before being cancelled). The only
  feature that depended on it was the Upscale Image workflow's
  "Fast (Tensor)" mode; the same workflow's "Detail (AI)" mode
  (SeedVR2) covers the same job and is now the only upscaler. Net
  effect on a fresh install: ~500 MB smaller, no opaque silent-stall
  failure mode, one fewer upscaler choice.

### Changed
- **Wizard heartbeat phrasing and cadence improved.** The previous
  v0.2.14 ladder fired at 30 s / 1 m / 2 m / 5 m / 10 m / +5 m, and
  used the word "idle" -- both choices read as "the install is
  hung" to a user watching the log pane. Heartbeats now fire **once
  per minute, every minute** (1 m, 2 m, 3 m, ...) and use the word
  "silent" instead of "idle" to describe the output channel rather
  than the process state. The wheel-stub-specific hint message
  ("possibly a 490 MB nvidia-vfx download") was generalized to
  reference PyTorch wheels and transformers, since those are now
  the largest legitimate silent-phase causes after `nvidia-vfx` was
  dropped. Heartbeats still never kill on silence; the Stop install
  button remains the only kill path.

## [0.2.14] - 2026-05-01

### Fixed
- **Bootstrap no longer aborts the entire install when one custom node
  fails to build.** Previously, a single broken sdist (missing C
  compiler, deleted upstream repo, etc.) inside `install-custom-nodes`
  would raise out of the step and the main loop would skip every
  remaining bootstrap step, leaving a half-installed Comfy Labs. The
  failing node is now skipped (a `step-skip-node` event is emitted with
  the failure reason), the rest of the nodes install normally, and the
  bootstrap continues through `install-app-deps` and
  `install-pinned-ml-deps` to completion. The skipped node names are
  surfaced at the end of the step and in the `summary` of the final
  `done` event.

### Changed
- **Wizard progress pane now emits an idle heartbeat during long quiet
  phases.** Some pip backends (notably the `wheel-stub` sdist used by
  `nvidia-vfx`, which silently downloads a ~490 MB prebuilt wheel
  during pip's "Preparing metadata" step) produce zero output for many
  minutes, making the wizard look frozen. The bootstrap now emits a
  "still working (idle ...)" heartbeat at 30 s, 1 m, 2 m, 5 m, 10 m
  and every 5 m thereafter while a subprocess is silent, so users can
  see the install is alive. The Stop install button is still the only
  thing that kills a running bootstrap; we explicitly do **not**
  kill-on-idle, because that would false-positive on exactly the
  silent-binary-download cases the heartbeat is meant to make legible.

## [0.2.13] - 2026-05-01

### Added
- **Comfy Labs now bundles its own Python runtime.** A relocatable
  CPython 3.12.13 distribution from `astral-sh/python-build-standalone`
  ships inside the portable ZIP (~46 MB) and is extracted into
  `<install_dir>\python-runtime\` on first run. Users no longer need
  to install Python separately, and Comfy Labs cannot conflict with
  any other Python installation on the machine -- the bundled
  interpreter lives entirely inside the install dir, never touches
  PATH, and is never registered with the OS.

### Changed
- **First-run wizard no longer requires system Python.** The
  bootstrap step's `create-venv` is now built on top of the bundled
  interpreter, not whatever `python` happened to be on the user's
  PATH. The wizard's bootstrap progress shows a new `extract-python`
  step (~5 seconds) before the existing GPU detect / venv create
  / PyTorch install steps run.
- **Tester onboarding is now "extract ZIP, double-click .exe."** The
  README's earlier "install Python 3.12 first" caveat is gone.

## [0.2.12] - 2026-05-01

### Fixed
- **Restored ability to build a packaged ZIP.** v0.2.11's
  rewritten Uninstall button introduced a single-quoted JSX
  string containing an unescaped apostrophe ("wasn't"), which
  closed the string early and broke the production Vite build
  (`Expected ")" but found "t"`). Switched the offending string
  to a template literal. Dev mode (`npm start`) was unaffected
  because the dev server wasn't restarted between the edit and
  the v0.2.11 push -- only `npm run make` surfaced it. **No v0.2.11
  ZIP was ever produced; ship v0.2.12 instead.**

## [0.2.11] - 2026-05-01

### Added
- **Standalone uninstaller now ships with the portable build.** The
  packaged `.zip` includes `Uninstall-Comfy-Labs.bat` (and its
  PowerShell partner) next to `Comfy Labs.exe`. Double-click the
  `.bat` to wipe everything: the install dir, app config + logs,
  the Electron app folder itself, and -- with explicit prompts --
  the models folder and HuggingFace cache. Useful when the app
  won't open or you want to remove it without launching first.

### Changed
- **In-app "Uninstall Comfy Labs" button now points at the
  standalone uninstaller instead of running its own removal logic.**
  Click opens Explorer with `Uninstall-Comfy-Labs.bat` selected and
  shows a small dialog with the path. Close Comfy Labs and run the
  `.bat` to actually uninstall. Trims the in-app code path,
  removes the long-running progress state, and gives users a single
  uninstall mechanism that works whether the app is open or broken.

## [0.2.10] - 2026-05-01

### Fixed
- **A FlashAttention wheel ABI mismatch no longer wipes out the
  whole 30-minute install.** The prebuilt wheel is pinned to a
  specific Python ABI tag (currently cp312); on a system Python
  with a different minor version (e.g. 3.10), pip rejected the
  wheel and the bootstrap loop's break-on-error tore down all
  prior progress (PyTorch, ComfyUI, every custom node). The
  install-flashattn step now treats this as a non-fatal skip and
  emits a clear reason; everything else still installs. FA is a
  performance optimisation, so workflows still run -- just
  without its speedup until a matching wheel is hosted.

### Changed
- **Install wizard: changing the install drive now updates the
  default models-folder path.** Previously the models folder
  defaulted to whatever drive the wizard auto-suggested first
  (usually the one with the most free space). If the user then
  picked a different install drive, the models folder stayed
  pointed at the old one. The default now tracks the install dir
  unless the user has explicitly typed or picked a folder, in
  which case their choice is preserved.

## [0.2.9] - 2026-05-01

### Changed
- **"Open Workflows" button now shows a busy indicator while
  attaching the Streamlit view.** Previously the button gave no
  visual feedback during the brief IPC + BrowserView attach window,
  so users kept clicking it. Click now swaps the label to
  "Opening Workflows..." with a spinning indicator and disables
  the button until the welcome screen unmounts. Rapid double-clicks
  are also guarded so only one open is ever in-flight.

## [0.2.8] - 2026-05-01

### Fixed
- **"Start ComfyUI" button on a fresh install no longer crashes
  with `FileNotFoundError`.** When ComfyUI was offline and the user
  clicked **Start ComfyUI** on the Workflows page, the Streamlit
  process tried to spawn ComfyUI itself via a legacy code path that
  assumed the old `D:\ComfyUI-Easy-Install\...` directory layout
  (embedded Python under `<dir>\python_embeded\`, `main.py` under
  `<dir>\ComfyUI\`). On any Comfy Labs install that path doesn't
  exist, so `subprocess.Popen` failed immediately. The button now
  delegates to the Electron supervisor (the same way the Concept
  Magic bridge already does), which uses the runtime venv and the
  correct paths.
- **Internal: `/subprocess/start` MCP route is now fire-and-forget.**
  It used to await ComfyUI's full health probe (up to 240 s), which
  blocked the HTTP response and timed out the Python-side caller's
  default 5 s `urlopen` window. Now the route returns immediately
  after issuing the spawn; the caller polls the service's own
  health endpoint to confirm readiness. Spawn failures still
  surface via the subprocess state channel + Support Log. Same fix
  also unblocks the Concept Magic bridge's lazy-spawn path.

## [0.2.7] - 2026-05-01

### Changed
- **Post-download restart prompt now uses the in-app dialog
  framework.** When all models finish downloading, the "Models
  installed -- Restart now / Later" prompt was a native Windows
  dialog (jarring, didn't match the rest of the UI). It is now
  rendered via the same DialogProvider that handles uninstall
  confirms, error toasts, etc. -- consistent dark theme and button
  styling, and the Streamlit BrowserView collapses behind it like
  every other in-app dialog.

## [0.2.6] - 2026-05-01

### Fixed
- **Hide the cloud "Deploy" button on the first-run setup wizard.**
  The wizard pages (System Check, Choose Your Workflows, Locate
  Models, Download) were rendering Streamlit's default Deploy button
  in the top-right, which is meaningless for a packaged desktop app.
  Workflows.py and Settings already suppressed it; wizard.py did
  not. Hides Deploy only -- the 3-dot hamburger and running-status
  animation are preserved.

### Changed
- **User manual refreshed.** Image paths updated to the new
  `Comfy-Labs-User-Manual_images/` folder; trimmed sections that
  duplicated developer-facing reference material.

## [0.2.5] - 2026-04-30

FlashAttention wheel published; Trellis2 / Image-to-3D-Mesh ship-blocker
fully closed.

### Fixed
- **Image-to-3D-Mesh now works on a fresh install.** Bootstrap's
  install-flashattn step previously skipped with a "wheel not yet on
  mirror" warning because the URL pointed at a stale 2.7.0 filename
  that 404'd. Trellis2 then crashed at runtime with `TypeError:
  'NoneType' object is not callable` on its first attention call. The
  matching cu128 + torch 2.8.0 + cp312 build is now hosted at both
  catalog mirrors as `flash_attn-2.8.3-cp312-cp312-win_amd64.whl` and
  `bootstrap.py` points at it. Verified end-to-end: install-flashattn
  step completes in ~10 s and `flash_attn_qkvpacked_func` is callable.

## [0.2.4] - 2026-04-30

Image-to-3D-Mesh import + execution path unblocked. Two structural
fixes for ComfyUI custom nodes that ship their own bundled wheels and
read models via direct paths.

### Added
- **Bundled-wheel installer for custom nodes.** The bootstrap's
  install-custom-nodes step now walks each cloned node's
  `wheels/Windows/Torch{X}{Y}{Z}/` tree (and any nested CUDA-X.Y
  subdirs), picks wheels matching the installed torch + cp312 ABI, and
  pip-installs them after the node's requirements.txt. Generic --
  catches Trellis2's bundled cumesh / flex_gemm / nvdiffrast /
  nvdiffrec_render / o_voxel today, and any future node that ships a
  similar tree gets the same treatment.
- **Auto-junction for namespace-style model subdirs.** On every ComfyUI
  spawn, `<comfyui>/models/<subdir>` is now mirrored from the user's
  external models folder via Windows directory junctions for any
  namespace-style entry (`facebook`, `microsoft`, `qwen`, etc.). Some
  custom nodes (Trellis2 again) read from `folder_paths.models_dir`
  directly and bypass extra_model_paths.yaml's named-key resolver --
  the junction makes those direct lookups resolve to the same files.
  Idempotent; replaces empty residue dirs but respects real non-empty
  ones; doesn't need admin / Developer Mode on Windows.

### Fixed
- ComfyUI-Trellis2 used to fail at import with a chain of missing
  dependencies (`triton` -> `cumesh` -> further). The bundled wheels
  installer above resolves the chain and Trellis2 now loads its 47
  node mappings cleanly.
- Image-to-3D-Mesh used to fail with "Facebook Dinov3 model not found
  in models/facebook/dinov3-vitl16-pretrain-lvd1689m folder" even
  though the file was on disk. Auto-junction makes that path resolve.

## [0.2.3] - 2026-04-30

Three diagnostic / robustness fixes after a fresh-machine portable
build QA pass.

### Added
- **Port-conflict pre-check before spawning ComfyUI.** If something
  else is already bound to `:8188` (a stale ComfyUI, a separate
  ComfyUI-Easy-Install on the same box, etc.), the supervisor now
  fails loud with "Port 8188 is already in use. Another process ... is
  holding it. Stop that process and try again." in the subprocess
  status. Previously the spawn failed silently with `OSError 10048`
  buried in stderr and the user only saw a vague "ComfyUI: Offline".
- **Subprocess stderr tail captured on crash.** The last 40 stderr
  lines per subprocess are now retained and surfaced in
  `subprocess/status.lastError[key].stderr`. Renderer / Streamlit can
  use this to show recent stderr inline next to a crashed-state pill
  without forcing the user into the full log viewer.

### Fixed
- ComfyUI-Trellis2 import on Windows. The custom node imports
  `triton` unconditionally but its requirements.txt forgets to declare
  it. Bootstrap's install-pinned-ml-deps step now installs
  `triton-windows==3.6.0.post26` so Trellis2 (and any other node that
  expects triton) can load.

## [0.2.2] - 2026-04-30

Uninstall UX polish.

### Added
- **Uninstall progress feedback.** The Uninstall button now shows a
  spinner + "Uninstalling -- this can take a minute..." with a sub-line
  describing what's running (stopping subprocesses, killing orphaned
  pythons, removing the install folder). Replaces the silent ~1 minute
  wait that made the app look frozen.
- **`finish_comfy-labs_uninstall.txt` written to Desktop** at the end of
  uninstall, listing exactly which two folders need manual deletion
  (the `%APPDATA%\Comfy Labs` config + the program folder containing
  `Comfy Labs.exe`) and why each can't be self-deleted. The file opens
  automatically in Notepad so it's hard to miss. Any preserved custom
  models / output folders are listed as an optional cleanup step.

### Changed
- **Uninstall complete dialog** now points at the desktop instructions
  file instead of restating the manual steps inline. Frees the dialog
  from re-listing paths the user can already see in Notepad.

## [0.2.1] - 2026-04-30

Iteration on the v0.2.0 onboarding redesign. Adds a Prefs toggle for the
Concept Magic bridge, fixes two HF-cache models that were 5-18x bloated,
disables sidebar buttons for workflows whose models aren't on disk, and
ships seven new MCP tools so future tests can drive the Streamlit UI
without OS-level mouse-event fallbacks.

### Added
- **Concept Magic bridge toggle in Prefs.** Default OFF. Users who don't
  use the Blender add-on don't pay for an idle uvicorn process. Toggling
  spawns or stops cm_bridge live (no app restart needed).
- **`/v1/generate*` auto-spawns ComfyUI.** Each generation request calls
  the equivalent of `/v1/ensure-comfyui-running` first and polls for up
  to 240 s. The Blender add-on no longer has to handshake explicitly.
- **Workflow sidebar disables buttons** for workflows that are missing
  models or GPU-incompatible. Tooltip points the user at the Settings
  page Download Models button instead of letting them click into a page
  that will fail at generation time.
- **Seven new MCP tools for driving the Streamlit BrowserView**:
  `comfy_browser_screenshot`, `_click`, `_type`, `_key`, `_eval`, `_dom`,
  `_info`. The renderer-side tools only see the Electron chrome; these
  reach into the BrowserView's separate webContents. Without these, every
  workflow / Settings page test had to fall back to PowerShell SetCursorPos
  + System.Drawing screenshots, which burned hours during the v0.2.0 test.

### Changed
- **`mvadapter` registry entry** now pins to `mvadapter_i2mv_sdxl.safetensors`
  + configs only via `allow_patterns`. Previously snapshot_download pulled
  21 GB (i2mv_sdxl + beta + 3 SD-2.1 variants + cache cruft); now ~3.6 GB.
- **`sdxl_vae_fp16` registry entry** pins to `diffusion_pytorch_model.safetensors`
  + `config.json` only. Previously pulled 1.34 GB of redundant copies
  (.bin + fp32 + 2 alias safetensors); now 335 MB.
- **`link_found_models` filters by `allow_patterns`** when copying HF repo
  dirs from a discovered ComfyUI install. Always strips `*.incomplete`,
  `*.metadata`, `.cache/`, `.locks/` regardless of patterns -- so the
  scan path mirrors what a clean fresh download would have produced.

### Fixed
- Audit + cleanup of the running test install recovered 24.87 GB:
  HF download partials, blob+snapshot duplicates (Windows symlink
  fallback), an unused `mvadapter_i2mv_sdxl_beta` variant, leftover
  SD-2.1 mvadapter weights from a discovered ComfyUI install, and
  three redundant SDXL VAE alias copies. Workflow capability unchanged.

## [0.2.0] - 2026-04-30

Onboarding redesign. Workflows now know whether their model weights
are actually present, the wizard can pick up an existing ComfyUI
install, and Concept Magic stays gated until first-run setup is done.

### Added
- **New wizard step 4 -- "Already have ComfyUI models?"** Browse to or
  paste the path to an existing models folder. The Streamlit wizard
  later scans it and links any matching files into your models folder
  via hardlinks (cross-volume falls back to copy).
- **Per-file scan + link** in the existing-models flow finds matching
  model files by filename + size validation, and records provenance in
  `.comfy_labs_manifest.json` so uninstall does not delete files that
  belong to your other ComfyUI install.
- **Native restart prompt** after the Streamlit wizard finishes
  downloading models. Streamlit POSTs `/onboarding-complete` to
  Electron's MCP bridge; Electron shows a "Restart now / Later" dialog.
- **Workflow status badges** on the Settings page: Ready, Models
  missing, Unavailable (GPU). "Ready" requires both the plugin code
  and every required model file. Per-workflow "Download models" button
  appears when only weights are missing.
- **Smart Streamlit landing.** The BrowserView opens the Settings page
  on first launch (so you do not see a Workflows page that does not
  work yet), and the Workflows home once your selected workflows are
  ready.

### Changed
- **Concept Magic bridge gated** on `first_run_complete`. The bridge
  no longer spawns until you have completed onboarding (selected
  workflows + downloaded models + restarted). Saves spurious crash
  pane when ComfyUI is not yet warm.
- **`config.json` schema bumped to v2.** Adds `discover_path` and
  `first_run_complete`. v1 configs continue to work; the old install
  still validates and the bridge stays off until you finish setup.
- **Hardlinks instead of symlinks** for linked-from-existing models.
  No Windows admin / Developer Mode required. Cross-volume linking
  falls back to copy automatically.

### Fixed
- **`install-pinned-ml-deps` step** now runs during bootstrap. Without
  it, `huggingface-hub` ended up at 1.x via streamlit's transitive
  deps, breaking transformers (which requires <1.0) and stopping
  ComfyUI from launching.
- **`make-pbr-tileable-textures` no longer flagged "Models missing".**
  Workflows with no model dependencies now correctly report Ready.
- **`depth_anything_v2` correctly attributed to Concept Magic LoRA**
  in the central model registry (it was already in the workflow's
  manifest but missing from `models.yaml`'s reverse index).
- **cm_bridge no longer false-crashes** during ComfyUI cold-start. Its
  health timeout now matches ComfyUI's 240 s window so the bridge
  is not killed while it is legitimately waiting on warmup.
- **Reconfigure path skips bootstrap** when the runtime venv already
  exists (`<install_dir>\runtime\pyvenv.cfg`). Saves a 25-45 minute
  reinstall when only paths needed fixing.

## [0.1.4] - 2026-04-29

Bug fix release: stop two writers from racing over `config.json`.

### Fixed
- **`config.json` no longer loses `install_dir` / `models_folder`.** Window
  position is now stored in its own `window-state.json` instead of sharing
  `config.json` with the wizard. Previously, electron-store's debounced
  windowBounds save would clobber the wizard's keys on the next window
  move, and uninstall/relaunch would surface a misleading "wizard never
  finished?" warning. Existing `config.json` files are migrated once on
  launch -- `windowBounds` is lifted out into `window-state.json`.

### Changed
- **Uninstall complete dialog** now lists exactly which folders still need
  manual deletion (`%APPDATA%\Comfy Labs` plus the `.exe` folder).
- **Renderer diagnostic harness.** A blank gray render now shows the
  underlying error inline instead of leaving the user staring at nothing.

## [0.1.3] - 2026-04-29

Closed-beta UX pass: install drive picker, in-app uninstall, toolbar
chrome relocation, and Help/Prefs/Support Log dialogs that work over
the Streamlit BrowserView.

### Added
- **Install location wizard step.** Pick a non-C: drive at first run for
  the heavy install (~21 GB Python runtime + ComfyUI + custom nodes).
  C: keeps only `config.json`, `app.log`, and Electron window state
  (under 10 MB). Suggests the largest non-C: NTFS drive with at least
  30 GB free; auto-creates a `Comfy-Labs` subfolder so cancel/uninstall
  cleanup is safe-by-construction.
- **Stop install button.** While bootstrap is running, the wizard's Back
  button becomes a red Stop install. Click kills the python/pip/git
  process tree and removes the partial install.
- **Uninstall Comfy Labs.** Outlined red button on the welcome screen.
  Confirms via the dialog system, then deletes the install folder +
  `%APPDATA%\Comfy Labs`. Custom models/output folders outside the
  install folder are preserved. The `.exe` folder is left for manual
  removal after the app closes.
- **Toolbar buttons.** Support Log (zips logs + system info to Desktop),
  Restart ComfyUI (with confirm + spinner while restarting), Output
  (opens the output folder in Explorer), Prefs (output folder +
  retention).
- **User Manual link** in the Help / About modal.
- **External-ComfyUI detection.** A 5-second health poll keeps the
  status-bar dot green even when Streamlit spawns ComfyUI directly.

### Changed
- **Wizard streamlined.** Dropped the vestigial "Pick workflows" step
  (the saved selection was never read). DoneStep now explains workflows
  ship bundled and model weights download on first use.
- **Welcome screen.** Logo halved (280 → 140 px) for better balance with
  the new actions.
- **Streamlit chrome relocated.** Sidebar bottom buttons (Shutdown /
  Export Support Log / View Logs / Help & Documentation) removed. Top
  cloud-Deploy button + 3-dot menu hidden via CSS (running-man indicator
  preserved). Sidebar collapse arrow hidden so page nav sits flush with
  the top.
- **Streamlit pages renamed.** Main page is now `Workflows` (was
  `app`); the workflow catalog/install panel is now `Settings` (was
  `Workflows`). Browser title for the main page is `Comfy-Labs
  Workflows`.
- **Open Workflows always lands on Workflows.** Home -> Open Workflows
  no longer dumps the user back on the previously-viewed Streamlit page.
- **Help / Prefs / Support Log / View Log all overlay correctly.** When
  a modal opens while in Streamlit mode, the BrowserView collapses so
  the dialog is visible. AboutModal and PrefsDialog now use the same
  blurred backdrop as LogViewer.
- **Help button** moved to the far right of the toolbar.
- **About modal** copyright line replaced with "Comfy-Labs created by
  Chipp Walters 2026".
- **Status bar** version text brightened to match the subprocess dot
  labels.

### Fixed
- **Wizard never started Streamlit on completion.** The wizard saved
  `models_folder` / `output_folder` paths but didn't create the
  directories, which made `validateConfig` flag them missing and skip
  autostart. The save handler now `mkdir -p`'s both before writing
  config.
- **`st.toast` icon errors** in the Settings page (`icon="OK"` and
  `icon="X"` are not valid Streamlit emoji shortcodes -- replaced with
  ✅ / ❌).
- **Duplicate widget keys** in the Settings page when a workflow appears
  in multiple tabs (All / Installed / Updates Available). Keys now
  include the tab prefix.
- **Support Log success path.** The toolbar handler was reading
  `data.zipPath` while the IPC returns `data.path`, so the success
  dialog showed "Export failed" even though the ZIP wrote to Desktop.

## [0.1.0] - 2026-04-29

First Phase 1 cut. Wraps the existing Streamlit + ComfyUI + cm_bridge
product as a native Electron `.exe`, replacing the Docker delivery
vehicle. PRD locked in 13 architecture decisions; `OPEN_QUESTIONS.md`
documents the decisions made on the user's behalf where the brief
didn't lock the answer.

### Added (Phase 1 -- in scope)
- **Catalog publishing.** 9 workflows live at
  `https://filedn.com/lLMW4jXsJqxXkRYjd1UCoKL/comfy-labs/catalog.json`.
  Per-workflow folders at `workflows/<id>/1.0.0/` with manifest +
  ui.py + thumbnail + CHANGELOG. Hardware mins (`gpu_min_sm`,
  `vram_gb`, `ram_gb`, `download_size_gb`) annotated from a research
  pass over the Docker product.
- **Subprocess supervision in `src/main.js`.** Streamlit (8501) +
  cm_bridge (8000) always-on; ComfyUI (8188) lazy-spawned by request.
  Health-poll on spawn, single-instance lock, full shutdown on close
  (SIGTERM-equivalent + 3 s grace + force-kill via taskkill /T).
  Subprocess stdout/stderr funnels into `app.log` line-by-line with
  `[PY-<key>]` / `[PY-<key> ERR]` prefixes. Status indicator dots
  (Streamlit / cm_bridge / ComfyUI) in the status bar; Stop ComfyUI
  button when running.
- **Python bootstrap.** `python-bootstrap/bootstrap.py` provisions a
  fresh runtime: venv + PyTorch 2.8.0 + cu128 + FlashAttention prebuilt
  wheel + ComfyUI source at the pinned commit + 26 pinned custom nodes
  + Comfy Labs Streamlit/cm_bridge app deps. Outputs JSON-per-line
  progress events; spawned by Phase 4's wizard.
- **Catalog backup-mirror fallback.** `python/workflow_manager.py`
  tries the primary mirror (filedn.com) then the backup
  (web.cw4.me) before falling back to a local catalog of bundled
  workflows.
- **Streamlit Workflows panel** (`python/pages/01_Workflows.py`) with
  All / Installed / Updates Available tabs. Per-workflow card shows
  hardware-requirement badges (greyed out when audited GPU is below
  spec) + status badge + Install/Update/Uninstall action button. Top
  bar has Check-for-Updates (force-refresh) + Import-Workflow (manual
  .zip import with the unsigned-source warning required by PRD).
- **`cm_bridge` versioned `/v1/...` API.** All existing endpoints moved
  under `/v1/` (legacy un-prefixed paths dropped per PRD). Two new
  endpoints: `GET /v1/process-status` (port-pings the three subprocesses)
  and `POST /v1/ensure-comfyui-running` (idempotent ComfyUI lazy-spawn
  trigger; calls back to Electron's MCP supervisor over loopback).
  Documented in `python/cm_bridge/CM_BRIDGE_API_v1.md`.
- **5-step install wizard** (`src/components/InstallWizard.jsx`).
  Welcome + hardware audit (nvidia-smi, RAM, disk free, OS, driver)
  with floor check (4 GB VRAM / 8 GB RAM / 25 GB disk), models folder
  picker (native dialog), workflow picker (live catalog with
  hardware-incompatibility greying), bootstrap step that streams
  Phase 2's JSON-per-line events into a colored log pane, done step
  that writes config.json under userData and dismisses.
- **Native Electron crash pane.** When any subprocess transitions to
  `crashed`, an Electron-rendered modal surfaces the captured stderr
  + exit code with Copy details / Send Error Report / Restart /
  Dismiss buttons. The pane works even when Streamlit is the down
  process.
- **Zip Error Report.** Bundles app.log + subprocesses.json +
  app-info.json + installed-workflows.json into
  `~/Desktop/comfy-labs-report-<ts>.zip` via PowerShell's
  Compress-Archive. NO model files, NO user images, NO upload
  endpoint.
- **Output retention with Recycle Bin.** `shell.trashItem` sweeps
  files older than the retention setting (default 24h, presets:
  never / 24h / 7d / 30d). Runs on app startup + once per 24h for
  long-running sessions. Skips empty subdirs as it walks.
- **Status bar output-folder region** that opens Windows Explorer on
  click (PRD section 9 spec).
- **Post-update migration** (PRD section 6). On every launch,
  validates that `config.json`'s `models_folder` still exists; if
  not, surfaces the wizard in "Reconfigure setup" mode with a yellow
  warning banner listing the validation failures. Subprocess
  autostart is skipped during reconfigure so the user doesn't see
  spurious crash dots.
- **Manual workflow import** (Phase 3 Workflows panel). Accepts a
  .zip with manifest.yaml + ui.py; verifies the manifest, copies to
  `%LOCALAPPDATA%/Comfy Labs/workflows/<id>/`, surfaces the
  unsigned-source warning before install.
- **MCP test bridge.** Inline HTTP server in `src/main.js` exposes 19
  routes (subprocess status / start / stop, screenshot, click, type,
  eval, DOM, logs, store) for autonomous Claude-driven testing. Off
  in packaged builds unless `COMFY_LABS_MCP_PORT` is set.

### Changed
- Toolbar trimmed (PRD: workflow-driven, not document-driven).
  Dropped New / Open / Save; kept Updates + Help.
- StatusBar fully rewritten -- removed template placeholders
  (viewMode, projectName, themeName); added 3-dot subprocess state +
  output folder click + Stop ComfyUI button.
- `src/utils/logger.js` no longer calls `clearLog()` from the
  renderer constructor (was racing with the main-process startup
  sequence and discarding the early subprocess spawn logs).

### Out of scope for v0.1.0 (Phase 2 backlog)
- LAN mirror / USB-stick install tooling (catalog URL is already
  user-configurable; convenience tooling waits).
- Sticky-stop toggle (manual stop currently auto-respawns on next
  request).
- Code signing / EV cert (SmartScreen warnings will appear; closed
  beta accepts this).
- Comprehensive end-user documentation.
- Auto-update for workflow versions (user-confirmed only in v0.1.0).

### Decommission criteria
The `_reference/` directory and the legacy
`D:\GitHub\comfyui\comfy-labs-docker\` get deleted only after:
1. Comfy Labs builds, signs, and installs cleanly on a fresh Windows 11
   machine.
2. Subprocess supervision is stable across crash + restart cycles.
3. Python bootstrap runs end-to-end on a fresh machine.
4. All 9 workflows execute successfully against the Electron-spawned
   backend.
5. Concept Magic Blender add-on integration verified against `/v1/...`.
6. At least one beta user has completed an install + session.

## [0.0.4] - 2026-04-28

### Fixed
- Window title bar now reads "Comfy Labs" instead of the unrendered `{{APP_NAME_CAPS}}` template placeholder.
- About modal: replaced template placeholders (`{{APP_NAME_CAPS}}`, `{{APP_DESCRIPTION}}`, `{{AUTHOR_NAME}}`, hardcoded "Version 1.0.0", generic "Fast / Secure / Modern" highlights) with real Comfy Labs content. Version is read live from `package.json`.

### Added
- Native Windows title bar now renders in dark mode (`nativeTheme.themeSource = 'dark'`) so it matches the rest of the dark UI.
- `create-shortcut.ps1` and `TASKBAR_SHORTCUT.md` updated for Comfy Labs (custom icon, correct project paths, dropped `MD DOCS` placeholder text).

## [0.0.3] - 2026-04-28

### Added
- Auto-update notification: app checks `https://www.widgetgadget.com/cw1/Comfy-Labs/version.json` 30 seconds after startup (or on demand via the Updates button) and surfaces a banner when a newer version is published.
- "Download" and "Release notes" buttons in the banner open the corresponding URL in the user's default browser; user installs by unzipping the downloaded archive (no installer).
- Comfy Labs logo on the start screen.
- Windows-portable `.zip` distribution: app installs by unzipping anywhere; no admin rights, no installer.
- `forge.config.cjs` `postPackage` hook runs `npm install --omit=dev` inside the packaged `resources/app` so externalised production deps are present at runtime (sibling pattern to Mocha-Trace).
- Custom application icon baked into the `.exe`.

### Changed
- Trimmed `forge.config.cjs` to a single `maker-zip` (Windows only). Dropped Squirrel, deb, rpm makers and the `auto-unpack-natives` plugin (incompatible with `asar: false`).
- Replaced `{{APP_NAME_CAPS}}` placeholder in `src/main.js` with `app.getName()` so packaged-build log path resolves correctly.

## [0.0.2] - 2026-04-28

### Added
- Initial repo bootstrapped from `cw_electron_template` (Electron + React + Vite + Forge shell, dialog system, logger, status bar, toolbar, log viewer).
- `python/` workspace migrated from `comfy-labs-docker/app/` with full git history preserved (Streamlit app, 9 workflow plugins, ComfyUI helpers, FastAPI Concept Magic bridge, model registry).
- User manual, manual screenshots, and `models.yaml` brought across into the new layout.
- `HANDOFF.md` describing project state, predecessor history, and the path to Phase 1.
- `CLAUDE.md` rules covering Streamlit BrowserView host, subprocess logger funneling, workflow plugin discovery, and `_reference/` policy.

### Changed
- Delivery vehicle: Docker container -> native Windows Electron `.exe` (predecessor `comfy-labs-docker` v0.8.1 to v0.9.4 deprecated).
