# PortOS — Development Plan

For project goals, see [GOALS.md](./GOALS.md). For completed work, see [.changelog/](./.changelog/) and `git log`.

## Next Up

- [ ] [bump-stale-shipped-md5-on-settled-migrations] **Bump stale `NEW_SHIPPED_MD5` entries on settled migrations + add `runPromptMigrationTests` coverage.** Migrations 003 / 004 / 005 / 013 still record their original NEW hash, but downstream migrations have since evolved the same files (e.g. 003's `pipeline-idea-expansion.md` is at the post-025 hash today, 005's `pipeline-arc-overview.md` is at the post-019 hash, 013's `pipeline-comic-script.md` is past `133d200d069c2e8173b7c129eea58f53`). That's a latent bug: a fresh install with `data/migrations.applied.json` cleared misclassifies every shipped file as "customized" instead of "already current," and `runPromptMigrationTests`'s drift-catch can't be wired up. Per the 019 convention, bump each migration's `NEW_SHIPPED_MD5` entry to the current `data.sample` hash and append the intermediate hash(es) to `ACCEPTED_OLD_MD5`; then add a `<num>.test.js` that calls `runPromptMigrationTests`. Skipped from `[backport-pre-023-migrations-to-_lib]` because that PR was scoped to a pure refactor.
- [ ] [useautorefetch-poll-result-dedup] **`useAutoRefetch` poll-result deduplication.** Surfaced by /simplify during `[codex5-polling-consolidation]`. After migrating 20 callers to `useAutoRefetch`, every poll still calls `setData(result)` unconditionally — re-rendering even when the API returned bytewise-identical state. `EpisodeVideoStage` is the only pre-existing dedup site (`(prev, p) => prev.updatedAt === p.updatedAt && prev.status === p.status`); the rest (ProcessesTab, AgentsPage, Brain summary, DigitalTwin status, meatspace OverviewTab fan-out, MediaJobsQueue, ChiefOfStaff, Browser status, etc.) re-render every interval. Options: (a) accept an optional `compareKey: (data) => string` option on the hook so callers can opt into signature-compare, (b) wrap the hook's internal `setData` to default to `Object.is` comparison and only the rare case (deep object reuse) needs explicit opt-in. Low priority — UI churn is invisible but burns CPU on the Dashboard's many widgets.
- [ ] [useautorefetch-shared-visibility-listener] **Shared `visibilitychange` listener for `useAutoRefetch` consumers.** Each `useAutoRefetch` caller currently attaches its own `document.addEventListener('visibilitychange', ...)`. On a page with 20+ widgets (Dashboard), tab show fires 20+ near-simultaneous `loadData()` calls. Extract a singleton emitter (e.g. `client/src/hooks/useVisibilityEvent.js`) and have `useAutoRefetch` subscribe to that instead. Trivial overhead today; revisit if Dashboard widget count grows or profiling flags it.
- [ ] [useautorefetch-pollonly-mode] **`useAutoRefetch` `{ pollOnly: true }` mode.** Several callers (Brain, DigitalTwin, ChiefOfStaff, meatspace OverviewTab, MediaJobsQueue, Loops, etc.) use the hook purely as a poll tick — they own their own state via the fetchFn's side-effects and ignore `data` / `loading`. The `return null;` ceremony is documented but signals "I'm not using the hook's data". Add a `{ pollOnly: true }` option that skips the `data` / `loading` useStates and returns `{ refetch }` only, so side-effect-only callers don't carry unused state. Defer until there are enough such callers that the noise is worth abstracting.
- [ ] [agents-tab-cooldown-tick-extraction] **Extract a shared cooldown-tick hook for the agents tabs.** `agents/{OverviewTab,ToolsTab,WorldTab}.jsx` each spin a 1s `setInterval` that both (a) ticks `setTick(t => t + 1)` so countdown labels update and (b) conditionally refetches rate limits when every cooldown has expired. Not a fit for `useAutoRefetch` (the tick is primarily UI-driven, the refetch is incidental). Extract `useCooldownTick({ cooldownEnds, onAllExpired })` so all three callers share one tested implementation. Surfaced while migrating sibling pollers in `[codex5-polling-consolidation]`.
- [ ] [codex5-onboarding-capability-map] **[P2][ONBOARDING]** Capability map of connected systems. One page showing each integration's status: Providers (per-provider configured/available/throttled), Calendar, Brain/memory embeddings, Voice (mic + TTS), Tailscale + HTTPS, Genome/health imports, Telegram/messages, App registry/PM2. Each row links to the relevant settings page. Doubles as a setup checklist and a runtime health overview.
- [ ] [dedupe-hhmm-time-window-helpers] **Dedupe HH:MM time-window helpers across server.** `server/services/voice/proactiveSpeech.js` exports `HHMM_RE` + `parseHHMM` + `isWithinQuietHours` (lines 26–57). `server/services/dashboardLayouts.js#sanitizeActivateWindow` + the inlined `isInTimeWindow` logic (post `[codex5-dashboard-intent-layouts]`) duplicate the same predicate (~25 LOC). Lift the three primitives to `server/lib/timezone.js` (where `getLocalParts` already lives) and have both `proactiveSpeech.js` and `dashboardLayouts.js` import from there. The dashboard route's Zod schema (`TIME_STRING_RE`) can keep aliasing the shared regex. Also evaluate whether `client/src/utils/timeWindow.js` (intentional client mirror) needs a parity test against the server primitive — surfaced by /simplify reuse review during `[codex5-dashboard-intent-layouts]` claim 2026-05-20.
- [ ] [dashboard-active-layout-revert-tracks-server-confirmed-id] **Active-layout revert should fall back to the last server-confirmed id, not `previousId`.** In `Dashboard.jsx#selectLayout`, two consecutive failed `setActiveDashboardLayout` PUTs revert each to their respective `previousId` snapshot — but `previousId` of the second call may be the optimistic-but-uncommitted id of the first call. So after two consecutive failures the UI snaps to a layout that never landed at the server. Mitigated in practice by single-user Tailscale deployment + write-tail serialization making double-failures rare, but worth a fix: track the last server-confirmed active id (from GET or successful PUT) and revert to that, or refetch on failure. Surfaced by codex iter-4 review during `[codex5-dashboard-intent-layouts]` claim.
- [ ] [extract-layout-doc-io-helper-from-migrations] **Extract `readLayoutsDoc` / `writeLayoutsDoc` helper for migrations.** Migrations 029 and 030 both repeat the same `readFile → JSON.parse → Array.isArray(doc.layouts) guard → writeFile` shell with near-identical logging strings. `scripts/migrations/_lib.js` already exists as the shared library for prompt-replace migrations — add a layouts-doc helper there so a future migration that touches `data/dashboard-layouts.json` doesn't fork the pattern a third time. Surfaced by /simplify quality review during `[codex5-dashboard-intent-layouts]` claim 2026-05-20.
- [ ] [migrate-two-remaining-local-formatters] **Migrate two remaining local formatters surfaced during the module-discovery PR.** Two additional local format definitions were found that aren't in the original tracker: (1) `client/src/components/calendar/EventDetail.jsx:10` defines `formatDateTime(dateStr, isAllDay)` with an all-day branch the shared `formatDateTime` lacks — extend the shared helper to take `{ allDay: true }` or keep this local and rename to `formatEventDateTime`. (2) `client/src/components/cos/tabs/TaskItem.jsx:78` defines `formatDurationMin(mins)` with a `~` approximation prefix (`~3h 30m`) — extend shared `formatDurationMin` with an `{ approximate: true }` option. Low priority; visual semantics need to be preserved exactly.
- [ ] [wire-proactive-cos-speech-to-real-triggers] **Wire proactive CoS speech to real triggers.** Plumbing landed (`POST /api/voice/speak` + `voice:speak` socket event); hook to high-severity `errorEvents`, `task:ready`, and `notificationEvents` with per-source rate-limits.
- [ ] [optimize-voice-ui-index-text-payload-lazy-only-run] **Optimize `voice:ui:index` text payload.** Lazy: only run `extractVisibleText` when server requests via `voice:ui:read-request`. Keep current behavior as fallback.
- [ ] [voice-agent-explicit-long-term-memory-routing-on] **Voice agent — explicit long-term memory routing.** On retrieval-shaped voice turns, inject top-N relevant memories into the system prompt via `brain_search`.
- [ ] [flux2-multi-reference-python-runner] **FLUX.2 multi-reference Python runner.** The UI + server contract for multi-reference editing shipped 2026-05-17 (slug `multi-reference-image-editing-for-flux-2-ui`); the Python runner (`scripts/flux2_macos.py`) currently ignores the `--reference-images`/`--reference-strengths` args that `local.js` now passes. Wire diffusers' multi-reference API in the runner and swap `server/lib/mediaModels.js#flux2-klein-9b` `tokenizerRepo` to `FLUX.2-klein-9B-kv` (gated repo — requires the user to accept the license on HF). Validate end-to-end with 2–4 uploaded refs.
- [ ] [voice-cos-tool-expansion-calendar-today-calendar] **Voice CoS tool expansion** — `calendar_today` / `calendar_next` (existing Google Calendar MCP), `meatspace_log_workout` (wraps `meatspaceHealth.js`), `weather_now` (pick API: OpenWeather / WeatherKit / NWS), `timer_set` (reuses `agentActionExecutor.js`).
- [ ] [voice-agent-vision-fallback-ui-describe-visually] **Voice agent vision fallback** — `ui_describe_visually`: screenshot tab and send to a vision-capable model so "what's on this chart?" works on CyberCity / graph views.
- [ ] [apple-health-integration-live-sync] **Apple Health integration for MeatSpace.** iOS live sync (HealthKit Shortcut → `POST /api/meatspace/apple-health` endpoint) plus a bulk historical import path for an exported `export.xml`. Wire into existing MeatSpace tabs so steps / sleep / heart rate / VO2 max / resting HR show alongside the alcohol / blood / body / epigenetic tracks already shipped. GOALS.md flags this as a documented Secondary Goal ("Apple Health integration planned") but no implementation tracking existed until this entry.

## Deferred Architecture (human-led planning)

God-file decomposition candidates — none are bugs; pick up when touching the file for unrelated reasons.

- `server/services/cos.js` (3115 LOC) — split into cosTaskStore / cosTaskGenerator / cosJobScheduler / cosHealthMonitor.
- `server/services/agentLifecycle.js` (1446 LOC) — extract prepareAgentWorkspace / resolveProvider / processCompletion.
- `server/services/identity.js` (1917 LOC) — separate genomic markers + longevity + goals + todos.
- `server/services/taskSchedule.js` (2369 LOC) — extract prompt management to `taskPromptService.js`.
- `server/services/taskLearning.js` (1939 LOC) — separate metrics aggregation from heuristic routing.
- `server/services/autonomousJobs.js` (1567 LOC) — extract job registry / scheduler / execution paths.
- `server/services/voice/tools.js` (1284 LOC) — group by domain (UI / calendar / brain / media) into siblings.
- `server/services/git.js` (1271 LOC) — extract command builders + parsers.
- `server/cos-runner/index.js` (1076 LOC) — extract spawn / lifecycle / IPC layers.
- `server/services/memory.js` (1049 LOC) — separate retrieval, classification, persistence.
- `server/services/xcodeScripts.js` (1131 LOC) — collapse repeated AppleScript builders.
- `server/routes/apps.js` (1180 LOC) — extract `npm install` orchestration to `appBuilder.js`.
- `client/src/pages/VideoGen.jsx` (1361 LOC) — extract mode-specific control panels (i2v / a2v / extend / FFLF).
- `client/src/pages/ImageGen.jsx` (1182 LOC) — extract preset picker + multi-reference uploader.
- `client/src/components/goals/GoalDetailPanel.jsx` (1252 LOC) — god component.
- `client/src/components/meatspace/tabs/CalendarTab.jsx` (1269 LOC) — extract grid renderer + goal-link panels.
- `client/src/components/cos/tabs/ScheduleTab.jsx` (1088 LOC) — extract schedule editor + run history table.
- `client/src/components/writers-room/StoryboardPanel.jsx` (1199 LOC) — extract scene tile + render dock subcomponents.
- `autofixer/ui.js` (972 LOC) — inline HTML template needs extraction.
- API contract — standardize error response shapes (`asyncHandler` + `ServerError` everywhere).

---

## Blocked on decision / design (skip for `/claim`)

Items here need a research / design pass, an explicit decision, or a precondition to fire before any code can land — `/claim` should skip them by default. Move an item back up to the Backlog once the gating question is answered.

### Audio / Video / Image gen

- [ ] [render-slowness-on-long-sessions-per-scene-render] **Render slowness on long sessions.** Per-scene render time degraded from ~3.5 min to 10–30 min within one project. Profile after sustained use; verify round-22 dedup helped.
- [ ] [pipeline-audio-phase-4c-2-4c-3-4d-2-local-oss] **Pipeline Audio Phase 4c.2/4c.3/4d.2.** Local OSS music gen (MusicGen sidecar; pick generator first); 3rd-party engine stubs; VO line muxing into the CD stitch with per-line offsets + music-bed ducking.
- [ ] [native-fflf-deeper-test-on-real-keyframe-pairs] **Native FFLF deeper test on real keyframe pairs.** Validate with last frame of clip A + first frame of clip B from the same scene; expose more pipeline knobs (cfg-scale, stg-scale, stage1-steps) if interpolation looks weak.
- [ ] [world-builder-phase-2-external-sd-api-per-bucket] **World Builder Phase 2 — external SD-API + per-bucket model overrides.** Wire Together / Replicate / Fal into world-builder batch path so high-end renders are practical; let each bucket pick its own model.
- [ ] [rich-text-editor-for-prose-stage-replace-plain] **Rich-text editor for prose stage.** Replace plain textarea in `ProseStage.jsx` — reuse Writers Room editor or pick a minimal markdown editor.
- [ ] [episode-video-provider-picker-runwayml-third-party] **Episode-video provider picker (RunwayML / third-party).** Once the abstraction lands, expose picker on EpisodeVideoStage.
- [ ] [comic-book-pdf-export-once-stages-comicpages] **Comic-book PDF export.** Once `stages.comicPages` carries enough panel data + rendered images, export print-ready PDF.
- [ ] [ai-assisted-panel-scene-prompt-generation-reserve] **AI-assisted panel/scene prompt generation.** Reserve `pipeline-comic-panel-image-prompt.md` and `pipeline-storyboard-image-prompt.md` for a future "turn script fragment into N image-gen prompts" button.
- [ ] [whole-episode-audio-generation-strategy-stop] **Whole-episode audio generation strategy.** Stop relying on per-clip audio; drive audio gen from episode-level prose/script arc. Generator candidates: Suno (commercial, duration control), MusicGen-MLX (local, bounded ~30s), AudioLDM2. New `audioMode: 'per-clip' | 'silent' | 'generated' | 'uploaded-track'`. Treat as a new sub-brainstorm when picked up — investigation first.

### Large product / UX redesigns

- [ ] [writers-room-phases-4-5-phase-4-synced-prose] **Writers Room Phases 4–5.** Phase 4 synced prose/script/media review; Phase 5 realtime CD feedback. Builds on the unified bible/scene model. See [writers-room.md](./docs/features/writers-room.md).
- [ ] [cybercity-v2-phase-2-deeper-drill-down-per-agent] **CyberCity v2 Phase 2+** — deeper drill-down: per-agent spatial trail, system flow lines between buildings, recent-action timeline overlay. See [cybercity-v2.md](./docs/features/cybercity-v2.md).
- [ ] [m50-p9-cos-automation-rules-automated-email] **M50 P9 — CoS Automation & Rules.** Automated email classification, rule-based pre-filtering, email-to-task pipeline.
- [ ] [m50-p10-auto-send-with-ai-review-gate-per-account] **M50 P10 — Auto-Send with AI Review Gate.** Per-account/per-recipient trust level + dual-LLM review (drafter + reviewer). Auto-send only when both approve or trust ≥ 0.9. See [messages-security.md](./docs/features/messages-security.md).
- [ ] [m34-p5-p7-digital-twin-multi-modal-capture-voice] **M34 P5-P7 — Digital Twin.** Multi-modal capture (voice/video/image identity sources), advanced testing, personas. Ties to GOALS.md "Multi-Modal Identity Capture".
- [ ] [cross-domain-insights-engine-m42-p5] **M42 P5 — Cross-Domain Insights Engine.** Connect genome ↔ health ↔ taste ↔ identity ↔ creative output into narrative insights surfaced in Brain. Needs a design pass on (a) which signal pairs are worth correlating, (b) where insights land (new Insights surface vs. Brain daily/weekly digest), (c) how the user dismisses / accepts an insight, and (d) whether the LLM gets a structured "facts table" or raw stage data. GOALS.md flags this as the next milestone for "Digital Identity Modeling" but no design has been written.
- [ ] [codex5-unified-review-queue] **[P1][PRODUCT]** Cross-domain Review Queue (the "inbox zero" for PortOS). One page (`/review` or `/inbox`) that collects: Brain inbox items needing classification, Ask answers ready for promotion, CoS completed tasks pending approval, Message drafts awaiting send, Health anomalies pending acknowledgement, and failed syncs / imports / backups. Each row has accept / dismiss / drill-down. Build the surface aggregator first, then opt each producer into it.
- [ ] [codex5-health-provenance-chips] **[P1][HEALTH][TRUST]** Extend Ask's source-chip pattern to MeatSpace / genome / death clock / longevity views. Each insight tags itself as `data-backed | inferred | experimental | speculative` with a "what would change this?" explainer and a clinician-export view for blood + lifestyle data. Pairs with `[codex5-decision-provenance-everywhere]`.
- [ ] [codex5-autonomy-guardrails] **[P1][AUTONOMY]** Per-domain autonomy levels + dry-run + budgets + while-away briefing. Today CoS has a single global autonomy posture. Split into per-domain knobs (Brain auto-classify, Memory auto-extract, CoS auto-run, Messages auto-send) each with `off | dry-run | execute`. Add spending / time / token budgets per domain. Add a "what did agents do while I was away?" digest pulling from the existing CoS / agent run logs into a daily briefing card.
- [ ] [codex5-mobile-task-flows] **[P2][MOBILE]** Five phone-native flows: (1) system health + one-tap restart of an app, (2) one-tap voice/text Brain capture, (3) "what should I do now?" Ask, (4) approve / reject a CoS result from a notification, (5) log a health/lifestyle event in ≤10 seconds. Each should be reachable in ≤2 taps from a Tailscale-launched mobile session.
- [ ] [codex5-sidebar-collapse-ia] **[P1][IA]** Collapse sidebar to 8–10 top-level domains + Pinned + Recent. Today the sidebar carries deep nested tabs at the same level as top-level domains; review recommends `Home, Ask, Apps, CoS, Brain, Calendar, Goals, Health, Dev Tools, Settings` only at the top level with deep tabs pushed into each page's local tab bar and Cmd+K. Add Pinned + Recent sections driven by the user's actual working set. Touch every consumer of `Layout.jsx`'s nav tree; expect alphabetical ordering convention to survive.
- [ ] [codex5-visual-modes] **[P2][DESIGN]** Product-specific visual modes: `Ops` (dense terminal-like, minimal animation), `Focus` (quiet, spacious, only next actions), `Health` (warmer, less cyber, more explanation), `Review` (document-like, optimized for reading + decisions). Needs a day-mode selector to switch between them. Extend the existing Tailwind token system rather than introducing a parallel theming layer.
- [ ] [codex5-decision-provenance-everywhere] **[P2][DESIGN]** Decision/provenance pattern across CoS, Insights, Health, Goals, Dashboard widgets. Each AI-generated or rule-driven surface answers: "Why am I seeing this?", "What data backs this?", "What changed?", "What can I do next?" Reuse the Ask source-chip component. Pairs with `[codex5-health-provenance-chips]`.
- [ ] [codex5-empty-states-teach] **[P3][ONBOARDING]** Workflow-teaching empty states. Each "no data" state replaces "Nothing here" with a setup hint that names the next action: "Connect calendar to unlock schedule-aware goals", "Add birth date + genome file to unlock longevity estimates", "Capture 10 notes to improve Ask Yourself", "Configure one API provider to enable autonomous CoS". Inventory empty states first, then prioritize by which pages new users hit earliest.

### Trigger-gated (waiting for a precondition)

- [ ] [scene-level-wardrobe-picking-per-scene] **Scene-level wardrobe picking.** Per-scene `characterAppearances: [{ characterId, wardrobeId? }]` on storyboard scenes with wardrobe-picker dropdown. Decide first: does the extractor guess or does the user pick? Append wardrobe after physicalDescription vs substitute body fields?
- [ ] [useasyncaction-post-unmount-setstate-guard-add] **`useAsyncAction` post-unmount setState guard.** Add `mountedRef` to gate `setRunning(false)`. YAGNI today; do at 4th consumer.
- [ ] [extract-usecanonpatch-universe-setuniverse] **Extract `useCanonPatch(universe, setUniverse, universeId, mountedRef)`.** `UniverseCanon.jsx` + `NounsStage.jsx` 95% identical optimistic-patch handlers. Extract when a 3rd caller appears.
- [ ] [shallow-equal-guard-in-usemediaannotations-socket] **Shallow-equal guard in `useMediaAnnotations` socket handler.** Speculative micro-opt; theoretical until observed.
- [ ] [low-client-extract-shared-usepopoverposition-hook] **[LOW][CLIENT]** Extract shared `usePopoverPosition` hook — **premise eroded; revisit when a 3rd caller lands.** Original PLAN flagged 4 components but `VisualStylePicker` no longer exists and `AddToCollectionMenu` / `BulkTargetPicker` no longer use portal positioning. Today the pattern lives in exactly two components (`client/src/components/ThemeSwitcher.jsx` and `client/src/components/media/CollectionPickerShell.jsx`) — both implement portal-with-fixed-positioning + `useLayoutEffect` measurement + rAF-coalesced scroll/resize listeners. With only 2 callers the abstraction isn't worth the indirection; extract when a third surface needs the same shape.
- [ ] [setup-data-import-drift-tables-from-migrations] **`setup-data.js` should import drift tables from each migration file.** `SHIPPED_PROMPT_OLD_MD5` + `SHIPPED_PROMPT_NEW_MD5` in `scripts/setup-data.js:139-196` manually mirror the `ACCEPTED_OLD_MD5` + `NEW_SHIPPED_MD5` exports each migration file already maintains. The mirror is hand-edited every time a migration ships and is the most likely place to drift. After `[backport-pre-023-migrations-to-_lib]` lands, every prompt-replace migration will export those constants — `setup-data.js` can build `SHIPPED_PROMPT_OLD_MD5` / `SHIPPED_PROMPT_NEW_MD5` by `import * as m025 from './migrations/025-idea-stage-character-detail.js'`-style sweep (or by glob+dynamic import) and removing the mirror entirely. Single source of truth, no hand sync. Surfaced by /simplify during `[pipeline-idea-stage-character-detail-plumbing]`.
- [ ] [parallelize-applypromptreplacemigration-files] **Parallelize `applyPromptReplaceMigration` per-file scan.** Today every prompt-replace migration covers exactly one file, so the serial `for (await readFile…)` loop in `scripts/migrations/_lib.js` costs nothing. When the first multi-file migration lands, fold the loop into `Promise.all(Object.keys(accepted).map(async filename => …))` and accumulate the `{ updated, alreadyCurrent, skipped }` counters post-flight. Theoretical until needed.
- [ ] [shots-aware-scene-output-contract-partial-split] **Shots-aware scene-output-contract partial.** Split into `_partials/scene-fields-core.md` + `_partials/scene-fields-shots.md` when a third shots-using stage appears.

## Future Ideas

- **Identity Context Injection** — per-task-type digital twin preamble toggle.
- **Content Calendar** — unified calendar across platforms.
- **Goal Decomposition Engine** — auto-decompose goals into task sequences.
- **Knowledge Graph Visualization** — extend BrainGraph 3D to full knowledge graph.
- **Autobiography Prompt Chains** — LLM follow-ups building on prior answers.
- **Legacy Export Format** — identity as portable Markdown/PDF (closes GOALS "Knowledge Legacy" gap currently at Early status). Bundle scope: autobiography, Brain notes + memories, key decisions, goals + milestones, digital-twin prompt, health summaries with source caveats, and a machine-readable manifest.
- **Workspace Contexts** — project context syncing across shell, git, tasks.
- **Inline Code Review Annotations** — one-click fix from self-improvement findings.
- **Major Dependency Upgrades** — React 19, Zod 4, PM2 6, Vite 8.
- **Workflow tab Phase 2** — drag-and-drop ordering of stages, custom user-defined stages, per-app workflow overrides.
- **Inspiration & Mood Board Canvas** — dedicated mood-board surface for collecting visual + textual references that feed into Universe Builder, Writers Room, and Creative Director (distinct from raw Media History). Items can be pinned from any media surface; canvas surfaces ref images when starting a new universe / scene / treatment. Documented as a Secondary Goal in GOALS.md but had no tracking.
- **Generative regen path for SynthID defeat (image cleaner) — research/backlog, hardware-gated.** Current `cleanImageBuffer` (`server/lib/imageClean.js`) only strips the C2PA `caBX` chunk + runs `median(3).sharpen()` — SynthID survives by design (verified 2026-05-20 against codex output: post-clean image reads as `C2PA removed, SynthID detected` on openai.com/synthid). The detector heatmap that OpenAI's verifier renders is a one-way correlation map `∫ image · template`, not the watermark template itself — so deterministic image-processing approaches (subtracting the heatmap, overlaying a second "PortOS SynthID," frequency-band cancellation) can't work without the secret key. The only honest defeat path is to round-trip pixels through a generative model: short-step img2img (low step count + low–moderate denoise, ~0.35–0.5) on a local FLUX runner so composition holds but the per-pixel watermark signal is overwritten by fresh sampling. **Scope narrowed (2026-05-20):** post-hoc, history-only. Surface as a second button next to "Clean (aggressive)" on the image-settings/lightbox modal — never auto-applied, never wired into the active gen flow, no per-render checkbox. Each run is ~5–15s on local mflux and GPU/unified-memory heavy; wants its own queue lane. Sidecar records `regenerated: true, regenSteps: N, regenStrength: 0.4, regenModelId: 'flux-v1'` so lineage stays honest. **Hardware-gated** — defer until the 128GB unified-memory machine arrives; current dev hardware can't comfortably host the local diffusion runner alongside the rest of PortOS. Pull in only after that hardware is online and the user wants to play with it. UX caveat: this is the only honest watermark-defeat path; the existing C2PA-strip-and-denoise will always be a no-op against SynthID and should never be relabeled as a watermark stripper.
