Skip to content

feat([issue-2036]): re-render generated images from annotations (phase 2)#2086

Merged
atomantic merged 5 commits into
mainfrom
next/issue-2036
Jul 3, 2026
Merged

feat([issue-2036]): re-render generated images from annotations (phase 2)#2086
atomantic merged 5 commits into
mainfrom
next/issue-2036

Conversation

@atomantic

Copy link
Copy Markdown
Owner

Sketch & Annotation Canvas — Phase 2 (Refs #2036)

Ships phase 2: a user-triggered "Re-render with annotations" action that feeds your markup back into img2img. Draw over a generated image, then re-render it guided by the marks.

What shipped (Phase 2)

  • Re-render action on the annotate page (/media/annotate/:mediaKey) — a toolbar button opens a confirmation dialog that names the exact local model the render will use (source-aware: GET /regen/availability?filename= resolves the model a regen of that image would run, so multi-model installs disclose the real model — no cold-bootstrap AI). Optional prompt + strength slider (bounds from availability), then Save-then-enqueue.
  • ServerPOST /image-gen/:filename/regenerate gains annotated: true: it flattens the saved annotation (source + strokes) and stages it as the img2img init image through the existing local-FLUX regen pipeline. Higher default denoise (0.5) so the marks actually reshape the render; GPU-only (rejects the light method); 400 when no annotation is saved.
  • The queued re-render lands in Media History via the existing completion path; lineage anchors at the source (regenOf).

Design notes (from review)

  • The runner re-validates initImagePath against its approved image roots, and data/media-sketches/ isn't one — so the flattened annotation is staged into the image-refs root as init-<uuid>.png (which imageRefsGc.js sweeps once unreferenced), only after every gate passes so a rejected request leaves nothing behind. This also freezes the markup at enqueue time.

Out of scope (remains open on #2036)

  • Phase 3 — blank-canvas storyboard sketches attachable to pipeline scenes.

Umbrella issue stays open (Refs, not Closes) per the issue's phased convention; I'll comment there on what shipped/remains.

Tests

  • Server: buildRegenParams annotated override; getSketchPngPath; the annotated regenerate route (no-annotation 400, light+annotated 400, staged init path resolves under an approved root — the regression guard for the silently-dropped-init-image bug, init- GC-covered naming); source-aware /regen/availability?filename= (threads source model, traversal-safe basename read).
  • Client: MediaAnnotate re-render flow (saves annotation → enqueues, model disclosed, unavailable-gating).

Test plan

  1. Media History → an image → Annotate, draw strokes.
  2. Click Re-render — the dialog names the local model; set an optional prompt/strength → confirm.
  3. The re-render queues and appears in Media History guided by your marks.
  4. On an install with no local FLUX runner, the dialog explains it's unavailable.

https://claude.ai/code/session_013aGBsru3A2x8MHTxeaYXBp

atomantic added 5 commits July 3, 2026 07:25
…e 2)

Adds a user-triggered "Re-render with annotations" flow to the annotate page:
flatten the source image + drawn strokes and feed the flattened PNG back through
the existing local-FLUX img2img regen as the init image, so the marks reshape the
render. Provider/model is named in a confirm dialog before any AI call runs (no
cold-bootstrap), with optional prompt + strength.

- server: /image-gen/:filename/regenerate gains `annotated` — seeds initImagePath
  from the saved sketch PNG sidecar (getSketchPngPath), higher default denoise,
  GPU-only (rejects the light method), 400 when no annotation saved.
- client: rerenderWithAnnotations API + Re-render button/modal on MediaAnnotate,
  gated on getRegenAvailability; saves the annotation then queues the re-render.
- tests: buildRegenParams annotated override, getSketchPngPath, the annotated
  route (no-annotation/ light / enqueue) paths, and the MediaAnnotate flow.

Refs #2036
… root

The local runner re-validates initImagePath against its approved image roots
(gallery/image-refs/visual-templates) and silently drops anything else. The
sketch sidecar lives in data/media-sketches/, which isn't approved — so the
annotated re-render would have discarded the markup and produced a plain
text-to-image render. Stage a snapshot of the flattened annotation into the
image-refs root the runner accepts (also freezing it at enqueue time), and add
a regression test asserting the enqueued initImagePath resolves via
resolveImageInputPath.
The staged img2img init snapshot went to data/image-refs as annot-<uuid>.png,
which imageRefsGc.js (issue #1214) doesn't match (it sweeps init-/ref- prefixes
only) — so a canceled/failed annotated re-render would leak the full-size file
indefinitely. Name it init-<uuid>.png: it IS this job's init image and the
existing GC now sweeps it once unreferenced. Test pins the init- naming.
…sure

- P3: stage the flattened annotation into image-refs only AFTER the light-method,
  no-annotation, and backend-availability gates pass, so a rejected re-render
  never leaves an orphaned init-<uuid>.png behind.
- P2: thread the source image's model through the regen-availability preflight
  (GET /regen/availability?filename=, getRegenAvailability({sourceModelId})) so
  the annotate confirm dialog names the EXACT model the POST will enqueue on a
  multi-model install — honoring the "disclose the model before the render"
  requirement. Client passes the source filename.
…regenInfo

- P2: the /regen/availability sidecar read used the raw ?filename query value;
  resolveGalleryImage only validates the basename, so a ../-prefixed value could
  traverse out of PATHS.images. Read the sidecar by basename(resolvedGalleryPath).
- P2: clear regenInfo at the start of the availability effect so a slow probe on
  a new :mediaKey can't leave the previous image's model/bounds visible in the
  re-render dialog.
@atomantic atomantic merged commit 93e5d37 into main Jul 3, 2026
2 checks passed
@atomantic atomantic deleted the next/issue-2036 branch July 3, 2026 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant