Skip to content

feat(cl2k): mirror the artwork horizontally - #579

Merged
chodeus merged 2 commits into
mainfrom
feat/cl2k-mirror-artwork
Aug 22, 2026
Merged

feat(cl2k): mirror the artwork horizontally#579
chodeus merged 2 commits into
mainfrom
feat/cl2k-mirror-artwork

Conversation

@chodeus

@chodeus chodeus commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a Mirror toggle to the CL2K poster, square-art and background-art makers that flips the artwork left-to-right — for backdrops whose subject faces out of frame, or whose composition sits on the wrong side for the logo.

Related issue

N/A — user request.

Type of change

  • Bug fix (non-breaking)
  • New feature (non-breaking)
  • Breaking change (config migration, schema, endpoint, or UI behavior)
  • Docs only
  • Refactor / internal cleanup

Where the flip lands, and why it matters

At the end of framing, not on the source bytes — _framed_inset_base and render_framed_art are the two points where framing is finished and nothing else has been drawn yet, so both flop() there. Two things forced that placement:

  • The AI text-removal mask and the extend outpaint are built in source space (modules/cl2k_maker.pyfit_extend_canvas, then text_removal.remove_text). Flipping the source first would put every brush stroke, and every Detect/Tighten result, on the wrong side of the picture.
  • The logo and label are composited after the artwork. Flipping the finished poster would render the season band backwards.

A useful consequence: because the flip is last, no coordinate is re-mapped. The framer keeps showing the source as it is, and toggling Mirror leaves the crop box and focal point exactly where they were.

Both callers of _framed_inset_base inherit it, so the .psd export stays pixel-identical to the poster beside it, and the season batch carries the flag so a bulk run matches its preview. The clear-logo maker deliberately does not get the toggle — a mirrored wordmark is just broken art.

Testing

tests/test_cl2k_mirror.py (24 tests, new):

  • the flip on every render path — poster, square, background, and the .psd POSTER frame;
  • the artwork mirrors and the label never does — the strip below the gradient's full-black row is asserted pixel-identical between mirror on/off, with a control asserting that strip is genuinely asymmetric so the check has power;
  • mirror composes with framing rather than replacing it: at focus_x=0.2, v_pos=0.3, zoom=1.6 the mirrored frame equals the flop of the unmirrored one, compared losslessly on frame_backdrop's PNG;
  • every hop of the flag — request model → endpoint → module maker → renderer — asserted per layer, plus one end-to-end TestClient POST that renders real mirrored pixels over HTTP;
  • mirror defaults to False on all four request models, so nothing already made re-renders differently.

Mutation-tested: dropping either flop(), flipping the whole poster instead of just the art, dropping one API forward, dropping one module→renderer forward, or removing the model field each fails the suite. Control run green.

Full suites: pytest 2123 passed; ruff check . clean; npm run lint clean (one pre-existing warning, unrelated); prettier --check src clean; npm run build clean; frontend vitest 78 passed.

Screenshots

Rendered before/after of the same source (subject swaps sides; MIRROR DEMO and SEASON ONE stay put and readable) — attached in review.

Checklist

  • Config schema updated if applicable — N/A, no config field; per-render request flag only
  • Frontend builds cleanly (cd frontend && npm run build)
  • No new DAPS references introduced
  • Docs / wiki drafts updated if behavior or shape changed
  • CHANGELOG entry added for user-visible changes — release-please generates it from the feat(cl2k): commit

Summary by CodeRabbit

  • New Features

    • Added a Mirror setting for poster, square artwork, background artwork, season generation, and PSD exports.
    • Artwork can now be flipped horizontally in previews and generated assets.
    • Mirror controls preserve correct logo, label, mask, and framing orientation.
  • Bug Fixes

    • Ensured mirrored artwork renders consistently across supported workflows while retaining existing crop and focal-point behavior.

A Mirror toggle on the poster, square-art and background-art makers
flips the artwork left-to-right — for backdrops whose subject faces out
of frame.

The flip lands at the END of framing, not on the source bytes, and that
placement is the whole design. The AI text-removal mask and the extend
outpaint are built in source space, so flipping the source first would
put every brush stroke on the wrong side of the picture; the logo and
label are composited after the artwork, so flipping the finished poster
would render the season band backwards. _framed_inset_base and
render_framed_art are the two points where framing is finished and
nothing else is drawn yet, so both flop there.

Applying it last also means no coordinate is re-mapped: the framer keeps
showing the source as it is, and toggling Mirror leaves the crop box and
focal point exactly where they were.

The .psd export follows the poster (both go through frame_backdrop) and
the season batch carries the flag, so a bulk run matches its preview.
@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1ed171bc-251e-4029-983c-e05d37f3e68d

📥 Commits

Reviewing files that changed from the base of the PR and between ccd39cd and 0b88ca2.

📒 Files selected for processing (5)
  • backend/api/cl2k_maker.py
  • backend/modules/cl2k_maker.py
  • backend/util/cl2k/renderer.py
  • frontend/src/pages/poster/Cl2kMakerPage.jsx
  • tests/test_cl2k_mirror.py

Included review availability: 0 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 1 review per hour.


📝 Walkthrough

Walkthrough

The PR adds a mirror option to CL2K request models, rendering workflows, CLI commands, and frontend framing controls. Mirroring flips artwork horizontally while preserving source-space framing and readable logos and labels.

Changes

CL2K Mirror Rendering

Layer / File(s) Summary
Request and generation pipeline
backend/api/cl2k_maker.py, backend/modules/cl2k_maker.py
Request models and maker workflows accept mirror and forward it through previews, generation, PSD export, and season generation.
Mirrored rendering and CLI
backend/util/cl2k/renderer.py
Renderers flip completed artwork horizontally. Framing, masks, logos, and labels retain their defined orientation. The CLI exposes --mirror.
Frontend mirror controls and requests
frontend/src/pages/poster/Cl2kMakerPage.jsx
Poster, square-art, and background-art controls persist mirror state, update previews, and include the setting in generation requests.
Mirror behavior validation
tests/test_cl2k_mirror.py
Tests cover rendering, defaults, endpoint propagation, asset makers, season generation, and end-to-end preview handling.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: ⚪ Minimal · up to 0b88c

The optional Mirror control changes artwork orientation while preserving existing behavior by default and keeping labels readable. No actionable merge-blocking risk remains after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant Frontend
  participant FastAPIEndpoint
  participant cl2k_maker
  participant render_cl2k
  Frontend->>FastAPIEndpoint: submit mirror setting
  FastAPIEndpoint->>cl2k_maker: forward mirror value
  cl2k_maker->>render_cl2k: render artwork with mirror
  render_cl2k-->>FastAPIEndpoint: return mirrored asset
  FastAPIEndpoint-->>Frontend: return preview or generated asset
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.79% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 38 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding horizontal artwork mirroring to CL2K workflows.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cl2k-mirror-artwork

Comment @coderabbitai help to get the list of available commands.

@chodeus

chodeus commented Aug 22, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chodeus

chodeus commented Aug 22, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chodeus
chodeus merged commit 712c86b into main Aug 22, 2026
20 checks passed
@chodeus
chodeus deleted the feat/cl2k-mirror-artwork branch August 22, 2026 04:06
chodeus added a commit that referenced this pull request Aug 22, 2026
CodeRabbit, against the repo's own path instructions: these carried
implementation history and rationale rather than a one-or-two-line what/gotcha.
The "why we bundled it" argument belongs in the PR body, where it already is.

Kept the gotchas that stop someone breaking things — the wire format stays flat,
a partial crop is ignored, and mirror lands at the end of framing because the AI
mask is built in source space. Dropped the essays around them.

Also trims the same violation in _framed_inset_base, which I wrote in #579 and
which the diff-scoped review therefore never saw.
chodeus added a commit that referenced this pull request Aug 22, 2026
…582)

focus_x / fit_mode / crop / v_pos / zoom / mirror were six parameters threaded
through four layers — 41 occurrences across three backend files, so adding the
sixth (mirror, #579) cost 24 mechanical signature edits and nothing else. They
are now one frozen geometry.Framing, making the seventh knob a one-line change.

The wire format is unchanged: the frontend still posts flat scalars, every
request model is byte-identical, and _framing(req) at the endpoint boundary is
the single place that shape becomes the object. No frontend or config change.

The AI-extend reset becomes dataclasses.replace(), which keeps mirror and
focus_x exactly as the old four-way tuple assignment did — with a test pinning
that mirror survives it, since clearing it would silently un-flip every
extended poster.

Verified behaviour-preserving rather than assumed: 13 framings across all three
render entry points, compared by pixel signature against main — 39 renders, all
identical.
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