# Release v1.51.0

Released: 2026-05-07

## Added

### Image Cleaner (Dev Tools)

New `/devtools/image-clean` page strips C2PA provenance metadata and median-filters pixel-level noise from `gpt-image-1` / Codex output. Drag-and-drop or browse-to-upload PNG/JPEG/WebP images (up to 40MB), pick a cleaning level (`light` = median(1), `aggressive` = median(3) + sharpen), and download the re-encoded result. Side-by-side before/after preview with size delta, dimensions, format, and a "C2PA stripped" badge when the source PNG carried a `caBX` chunk. Backed by a new `POST /api/image-clean` route powered by `sharp`, with magic-byte format sniffing so client-supplied MIME types aren't trusted. Reachable via `⌘K` and voice (`ui_navigate`) through new `nav.devtools.image-clean` manifest entry.

### Writers Room — Read view + cross-linked storyboard

A new `?view=read` mode for the Writers Room editor renders prose with scene anchors, inline character/setting/object highlighting, and hover tooltips that show extracted profile details. Hovering a token in the prose rings the matching chip on its scene card and the matching row in the bible; clicking jumps the sidebar to the right tab. Hovering a scene card flashes the matching scene marker in the prose.

Scene cards now use a stronger visual treatment when active (accent ring + tint + faint glow), and "jump to scene" smoothly tweens the textarea (220ms easeInOutCubic) instead of snapping.

A new third extraction kind, **Objects**, extracts recurring symbolic items (the letter, the fedora) alongside Characters and Settings. Editable in a new Objects tab in the storyboard sidebar, with the same AI-fills-blanks merge rule as the other bibles.

### Reference Repos — track upstream code we borrow from

Each app in `data/apps.json` (including PortOS itself) can now declare a `referenceRepos` array of upstream repos it watches for adoptable changes. Each entry stores the repo URL (or local path), branch, free-text notes describing what features rely on it, and a `lastReviewedSha` checkpoint. A new `reference-watch` self-improvement task type runs weekly (off by default), fetches each ref into `data/cos/reference-repos/<refId>/`, computes commits since `lastReviewedSha`, and dispatches a CoS sub-agent that writes a propose-only `REFERENCE_REVIEW.md` (Adopt / Maybe / Skip) to the app's repo. Read-only — no auto-implementation.

REST surface is mounted at `/api/apps/:appId/reference-repos` with full CRUD plus `POST /:refId/check` (run a check now) and `POST /:refId/reviewed` (manually pin the reviewed SHA). 26 new tests covering the service + route layers.

### Multi-keyframe video interpolation (LTX-2)

The video gen route now accepts an arbitrary `keyframes` array (`[{ file, index }, ...]`, length 2-8) on `mode='fflf'` for ltx2-runtime models, replacing the implicit two-frame cap of the existing `sourceImageFile` + `lastImageFile` pair. The underlying `KeyframeInterpolationPipeline` already supported N anchors at arbitrary pixel-frame indices — PortOS just exposes it now.

This is the compositional primitive Writers Room storyboards need: anchor every shot's keyframe-0 to the same character still for cross-shot continuity, pin pose/framing at specific timecodes, or set N storyboard panels as keyframes inside one render. Validation rejects non-ascending or out-of-range indices before the spawn so a typo doesn't burn a 30-second model load. Multi-keyframe is incompatible with `chunks > 1` (chains anchor a single clip).

### Live render dock

A page-level run dock now slides up from the bottom of the Writers Room while image-gen jobs are queued or rendering. Each row shows the scene label, status, progress bar, ETA, and a per-job stop button; "Stop all" cancels every queued and in-flight render. The dock auto-hides one second after the last job completes.

## Changed

- Chief of Staff metrics now live inside the CoS sidebar on desktop, mirroring the mobile compressed layout. The standalone five-card stats row above the tab nav is gone for SVG/canvas avatars; the sidebar instead carries a 2-col compact grid (Active/Pending, Done/Issues, Learning/Start-Stop) below the status bubble, with the Start/Stop button absorbed into the grid as its sixth tile. The QuickSummary and ActionableInsightsBanner widgets — both tasks-tab-only — now render inside the Tasks tab panel under the tab nav instead of stretching above tabs they don't apply to. Ascii/Terminal avatar mode keeps its existing standalone stats bar.
- Chief of Staff left rail uses desktop space better: the avatar UI section now uses tighter horizontal padding (`lg:px-4` instead of `lg:p-8`), the Event Log expands to fill remaining vertical space (up to 32rem) and shows up to 25 events instead of 5, and the Start/Stop control row is pinned to the bottom of the panel via `mt-auto`. Mobile layout unchanged.
- The "Rendering N scenes…" inline banner inside the Boards tab has been removed; the new run dock subsumes it and is visible from any tab.
- `STORYBOARD_TAB` enum now includes `OBJECTS` between `WORLD` and `SCENES`.
- `ANALYSIS_KINDS` server enum now includes `'objects'`.
- App selectors throughout the UI (task add form, OpenClaw) now list apps alphabetically by name via the shared `AppContextPicker`, instead of preserving the underlying storage order.
- Image, video, and SD-API prompt/negative-prompt max length bumped from 2,000 → 8,000 chars across `imageGen`, `videoGen`, `sdapi`, and the Creative Director scene schemas. Long Writers Room scene prompts (extracted setting + character + object detail + camera/lighting direction) were hitting the 2k ceiling and getting rejected at the validation layer before reaching the dispatcher.

## Fixed

- **Day-mode chip legibility**: Pastel chip text (Worktree, Simplify, BookLoom, model badge, BTW, "main", PR, Review, etc.) used 300/400/500 Tailwind tones designed for dark mode and were unreadable on cream/white day-mode themes (Phosphor Paper, Drafting Paper, Lumen Glass Day, Classic Noon). Added `[data-port-theme-mode="day"]` overrides that remap each hue to its 700-tone (yellow → 800) so chips keep their semantic color but read clearly on light backgrounds.
- HF token resolution now falls back to `~/.cache/huggingface/token` (written by `hf auth login`) after settings and env vars come up empty, so users who already authenticated through the Hugging Face CLI no longer see the "FLUX.2 access requires Hugging Face token" banner. Resolution order is unchanged otherwise: Settings → `HF_TOKEN` / `HUGGINGFACE_HUB_TOKEN` / `HUGGINGFACEHUB_API_TOKEN` → CLI cache.
- Media Gen cards no longer overflow their action button row at narrow widths (6-column `lg:` grid in Image/Video Gen recent-renders). The button strip now uses `flex-wrap` so excess icon buttons spill cleanly onto a second row instead of bleeding past the card border, and the Remix/Continue label truncates instead of forcing the row wider.
- Media Gen "Add to collection" popover is now portalled into `<body>` with viewport-aware fixed positioning, so it stacks above the sidebar and is no longer clipped by the gallery grid's `overflow-auto` parent (previously the menu was getting visually cut off and ducking under the nav). Once a user has 6+ collections, a search filter appears at the top of the menu so big collection lists are addressable by typing instead of scrolling.
- Stale-chunk auto-reload now covers Safari ("Importing a module script failed") and Firefox ("error loading dynamically imported module") in addition to Chrome — previously iOS Safari users hit a "Something went wrong" error after a rebuild and had to tap Refresh manually. Detection is shared between `lazyWithReload` (the primary path) and `ErrorBoundary` (safety net), with a one-reload-per-session guard against infinite loops.
- `ErrorBoundary` now uses theme-aware `text-port-text` / `text-port-text-muted` / `text-port-on-accent` instead of hardcoded `text-white` / `text-gray-400`, so the fallback UI is readable on light themes (Lumen Glass, etc).
- App icon detection: ship `data.sample/apps.json` with PortOS's icon path pre-set to `client/public/portos-logo.png`, and harden `detectAppIcon()` to skip SVGs that embed external `<image href="…">` (the icon endpoint serves SVG with `default-src 'none'` CSP, so those embeds are blocked and the icon renders blank). Detector now also checks `apple-touch-icon.png` and `icon-{192,512}.png` so PWAs without a usable favicon still resolve to a high-res raster. Hit "Detect Icon" once on existing installs to repick.
- App icons now auto-recover when the stored `appIconPath` is an unusable external-image SVG: the icon route and the config-refresh route both validate SVG usability at serve time and trigger redetection if the cached path won't render under the route's CSP. Existing installs (e.g. PortOS itself, which had stored `client/public/favicon.svg` before the detector skipped it) heal on the next icon fetch instead of staying blank until manual re-detect.
- App icon thumbnails now render as iOS-style squircles in the Apps page rows and the dashboard `AppTile` grid: the wrapper's corner radius bumped from `rounded` (4px ≈ 12.5%) to `rounded-[22%]` (the iOS app-icon proportion), and the fetched icon image now fills the full 32×32 tile via a new `fillContainer` mode on `AppIcon` — previously the 18×18 image sat top-left of the wrapper so only the top-left corner showed the squircle clip.
- Image / Video Gen "Recent renders" cards now render at the same width as Media History (5 columns at `lg` instead of 6), so the same `MediaCard` component shows its full prompt/metadata/action row without cramming.
- Remix from Media History now actually populates the Image Gen form: `MediaHistory.handleRemix` passes `negativePrompt`, `guidance`, and `quantize` in addition to the existing fields (and skips the `(no prompt)` placeholder), and Image Gen reads all of them from the URL on mount, then strips the params so a hot-reload or back-nav doesn't re-clobber subsequent edits.

## Full Changelog

**Full Diff**: https://github.com/atomantic/PortOS/compare/v1.50.0...v1.51.0
