diff --git a/.changelog/NEXT.md b/.changelog/NEXT.md
index 37cf37b1d4..2cd2be7a9d 100644
--- a/.changelog/NEXT.md
+++ b/.changelog/NEXT.md
@@ -14,6 +14,7 @@ TBD
- **Brain → Links: edit a link's URL**: the per-link edit form now exposes the URL alongside the title (each with a visible label so they're unambiguous even when the title defaults to the URL). Changing the URL re-derives the GitHub repo metadata, guards against duplicate URLs (409), and resets stale clone state so a cloned repo's local path can't point at the wrong target.
- **Base style image (style probe)** on a universe: generate a canonical image from the universe's embrace/avoid influences as the positive/negative prompt, with no character or subject — to preview the world's base visual emphasis. `styleNotes` is intentionally NOT mixed into the probe prompt because it never reaches the image model on any downstream prompt (canon refs, variations, sheets, comic pages) — including it would misrepresent what those renders will look like. Triggerable from both the Universe Builder (under the style/influences editor) and the Story Builder's Universe Aesthetic step; the result persists on the universe (`styleImageRefs`) so both surfaces share it. Includes a "Regenerate" affordance to re-roll the probe once an image already exists (new filenames append to `styleImageRefs[]` so prior renders survive as walk-back fallbacks).
- **Reader Map** on a series arc (`series.arc.readerMap`): a distinct audience-experience roadmap — hooks, payoffs, emotional beats, and cliffhangers across the arc — built on top of the Vonnegut story shape, separate from the protagonist arc. Generated and refined via the new Story Builder reader-map step (also preserved by arc regeneration).
+- **Image + Video Gen: inline model download status**. The model picker on both pages now shows whether the selected model's weights are already in the local HuggingFace cache. Available models get a green "Available · 7.8 GB" badge; missing ones get a "Download (~est)" button that pre-fetches the repo over SSE with a live progress bar (so users learn about a multi-GB pull before hitting Render instead of being surprised by a silent lazy download mid-generation). Hitting Render without pre-downloading still works — the existing lazy-download path is the fallback. The video form also surfaces the active text encoder (a separate ~7–25 GB Gemma pull) with its own button. New endpoints: `GET /api/image-gen/models/status`, `GET /api/image-gen/models/:id/download` (SSE), `GET /api/video-gen/models/status`, `GET /api/video-gen/models/:id/download` (SSE), `GET /api/video-gen/text-encoder/download` (SSE). Cache detection lives in `server/lib/hfCache.js` (`inspectModelCache`); download orchestration in `server/lib/hfDownload.js` and the new `scripts/hf_download_repo.py` helper (runs in the FLUX.2 venv, falls back to the mflux pythonPath). Mflux legacy `dev` / `schnell` map to their canonical `black-forest-labs/FLUX.1-*` repos so they get the same badge as every other entry.
- Local LLMs: when an Ollama model pull fails with `412: requires a newer version of Ollama`, PortOS now auto-upgrades Ollama in place and retries the install — no confirm click. On macOS with `/Applications/Ollama.app` present, this downloads the latest `Ollama-darwin.zip` directly from GitHub releases, force-kills the running Ollama, replaces the `.app` bundle, strips quarantine, relaunches, and polls `/api/version` until the new binary is serving. (The old brew-only path silently left the old binary running, because `/usr/local/bin/ollama` symlinks into the `.app` bundle — even a successful `brew upgrade` doesn't change which binary is on disk inside the `.app`.) On Linux it still re-runs the official Ollama install script; for headless macOS installs (brew formula only, no `.app`), it still runs `brew upgrade ollama`. The UI now shows a prominent yellow warning banner with live step-by-step progress for the whole upgrade flow, instead of a quiet inline confirm row. Failed `brew` runs now also surface the actual stderr (e.g. `Error: ollama not installed`) instead of just an exit code.
## Changed
diff --git a/client/src/components/imageGen/ImageGenControls.jsx b/client/src/components/imageGen/ImageGenControls.jsx
index 086fe79b42..b301fc6e45 100644
--- a/client/src/components/imageGen/ImageGenControls.jsx
+++ b/client/src/components/imageGen/ImageGenControls.jsx
@@ -13,6 +13,7 @@ import { filterResolutions, resolveResolutionLabel } from '../../lib/imageGenRes
import { randomSeed } from '../../lib/genUtils';
import { RUNNER_FAMILIES } from '../../lib/runnerFamilies';
import { IMAGE_GEN_MODE } from '../../lib/imageGenBackends';
+import ModelDownloadBadge, { deriveSizeEstimate } from '../media/ModelDownloadBadge';
const QUANTIZE_OPTIONS = [
{ value: '3', label: '3-bit' },
@@ -39,6 +40,14 @@ export default function ImageGenControls({
// Optional column override — defaults to 2/3 like the Image Gen page.
// Pass e.g. "grid-cols-2 sm:grid-cols-4" to fit a denser layout.
className = 'grid grid-cols-2 sm:grid-cols-3 gap-3',
+ // Pre-download badge integration. `modelStatus` is the per-model entry from
+ // useModelDownloadStatus().getStatus(modelId); `onModelDownload` /
+ // `onModelDownloadCancel` are optional triggers. Omitting the props hides
+ // the badge — callers that don't care (Universe Builder batch render) opt
+ // out by simply not passing them.
+ modelStatus = null,
+ onModelDownload,
+ onModelDownloadCancel,
}) {
const isLocal = mode === IMAGE_GEN_MODE.LOCAL;
const isCodex = mode === IMAGE_GEN_MODE.CODEX;
@@ -71,6 +80,14 @@ export default function ImageGenControls({
>
{models.map((m) => )}
+ {onModelDownload && modelStatus && (
+ onModelDownload(modelId)}
+ onCancel={onModelDownloadCancel}
+ estimateLabel={deriveSizeEstimate(currentModel?.name)}
+ />
+ )}
)}
diff --git a/client/src/components/media/ModelDownloadBadge.jsx b/client/src/components/media/ModelDownloadBadge.jsx
new file mode 100644
index 0000000000..3898c598ef
--- /dev/null
+++ b/client/src/components/media/ModelDownloadBadge.jsx
@@ -0,0 +1,107 @@
+// Inline "Available · 7.8 GB" / "Download (~8 GB)" badge for the image and
+// video gen model pickers. Drops below the model