Skip to content

Fix legacy persona avatar crop parsing#1383

Merged
cha1latte merged 2 commits into
Pasta-Devs:refactorfrom
cha1latte:fix/refactor-issue-1364-persona-avatar-crop
May 27, 2026
Merged

Fix legacy persona avatar crop parsing#1383
cha1latte merged 2 commits into
Pasta-Devs:refactorfrom
cha1latte:fix/refactor-issue-1364-persona-avatar-crop

Conversation

@cha1latte

@cha1latte cha1latte commented May 27, 2026

Copy link
Copy Markdown
Contributor

Linked issue

Closes #1364

Why this change

  • Refactor dropped support for legacy persona avatar crop values shaped as zoom/offsetX/offsetY/fullImage, so imported legacy personas could lose their saved avatar framing.

What changed

  • Added legacy avatar crop parsing and rendering support to the shared crop helpers.
  • Widened persona/avatar render data types so legacy crop values can flow to existing render surfaces.
  • Updated the persona editor and crop widget to preserve legacy crop rendering while emitting the current crop shape after user edits.
  • Added unit coverage for current crop parsing, legacy crop parsing, legacy crop styling, and malformed legacy rows.

Refactor impact

Primary owner:

  • catalog personas / shared UI

Impact areas reviewed:

  • Persona contract and editor hydration.
  • Shared avatar crop parser/style helper.
  • Chat/game/avatar visual data flow types that render persona crops.

Boundary notes:

  • No storage schema, dependency, version, remote-runtime, or prompt-pipeline changes.
  • Legacy rows are preserved at read/render time; this PR does not eagerly migrate saved rows.

Pressure points touched:

  • Persona editor avatar crop hydration.
  • Shared AvatarCropWidget.
  • GameSurface and GameNarration avatar info typing.

Validation

  • pnpm check passes locally
  • pnpm typecheck passes locally
  • pnpm build passes locally
  • pnpm check:architecture passes locally
  • pnpm check:docs passes locally
  • cargo check --manifest-path src-tauri/Cargo.toml --workspace passes locally
  • Rust clippy/tests were run for Rust behavior changes
  • Browser or Tauri app manual verification completed
  • Playwright, screenshot, or recording evidence added for UI changes
  • Remote runtime smoke checked when relevant

Manual verification notes

  • Codex reproduced the regression with node scratch\issue-1364-avatar-crop-proof.mjs before the fix.
  • Codex verified the original repro now passes with node scratch\issue-1364-avatar-crop-proof.mjs.
  • Codex ran pnpm exec vitest run src\shared\lib\avatar-crop.test.ts; 1 test file / 4 tests passed.
  • Codex ran pnpm check; architecture, TypeScript, Rust cargo check, and docs checks passed.
  • Codex ran node C:\Users\celia\.codex\tools\marinara-proof-gate.mjs scratch\bugfix-verification.json; proof gate passed.
  • No manual browser/Tauri import was run. A human may still manually import a legacy persona with zoom/offset crop data to visually confirm framing in the app before ticking manual UI boxes.

Docs and release impact

  • No docs changes needed
  • Updated README.md
  • Updated CONTRIBUTING.md
  • Updated docs/developer/
  • Updated repo skills or AGENTS.md
  • Confirmed this PR does not restore old staging/package-workspace/release claims

UI evidence

  • No screenshot evidence. This fix is covered by parser/style unit coverage, the scratch compatibility harness, TypeScript flow validation, and baseline checks.

Summary by CodeRabbit

  • New Features

    • Added backwards-compatible support for legacy avatar crop formats during persona imports, enabling seamless import of personas created with earlier versions.
  • Tests

    • Added comprehensive avatar crop JSON compatibility test suite covering parsing and styling scenarios.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bc886476-55b3-49c7-953f-1fa279caf103

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR extends the avatar crop system to recognize and render legacy crop data (zoom, offsetX, offsetY, fullImage) alongside current source-rectangle crops. Core utilities introduce a LegacyAvatarCrop type and unified AvatarCropValue union, update parsing and style rendering, and propagate the new union type through persona contracts, UI components, and feature modules.

Changes

Legacy Avatar Crop Support

Layer / File(s) Summary
Shared crop types and parsing
src/shared/lib/utils.ts
Adds LegacyAvatarCrop interface and AvatarCropValue union type, introduces isLegacyAvatarCrop type guard, and extends parseAvatarCropJson to validate and parse both current and legacy crop formats with numeric range checks.
Crop style rendering for legacy format
src/shared/lib/utils.ts
Updates getAvatarCropStyle to accept AvatarCropValue, computes CSS objectFit and transform styles for legacy crops including full-image mode and zoom/offset, and optimizes behavior when zoom <= 1.
Persona contract type support
src/engine/contracts/types/persona.ts
Introduces LegacyPersonaAvatarCrop interface and PersonaAvatarCropValue union type, updates Persona.avatarCrop field to accept both current and legacy formats.
Persona editor legacy crop handling
src/features/catalog/personas/components/PersonaEditor.tsx
Extends form data and row types to support legacy crops and string-encoded values, introduces parseAvatarCropValue normalization helper, replaces manual validation with unified parsing during persona loading and DescriptionTab display.
Avatar crop widget legacy support
src/shared/components/ui/AvatarCropWidget.tsx
Extends props to accept LegacyAvatarCrop, updates crop-sync and image-load logic to detect legacy crops and fall back to centered max-square default instead of converting to canvas selection state.
Character avatar crop resolution
src/features/catalog/characters/components/CharacterAvatarImage.tsx
Introduces resolveAvatarCrop function to parse crop values (strings or objects) via parseAvatarCropJson, returns AvatarCrop or LegacyAvatarCrop, with null fallback on parse failures.
Crop type propagation
src/features/modes/game/components/GameNarration.tsx, src/features/modes/game/components/GameSurface.tsx, src/features/runtime/visuals/types.ts
Updates import and type annotations to use AvatarCropValue instead of AvatarCrop in speaker avatar info, cropped avatar props, and visuals structures.
Avatar crop compatibility tests
src/shared/lib/avatar-crop.test.ts
New Vitest suite validates current and legacy crop JSON parsing, legacy crop type detection, malformed input rejection, and CSS style output including full-image mode and zoom/offset transforms.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

The PR introduces a new union type with moderate complexity: type definitions and parsing are straightforward, but the changes are distributed across multiple layers (shared utilities, UI components, persona editor, feature modules). The logic density is moderate—parsing validation and CSS style computation for legacy crops require careful review, and the persona editor's normalization logic spans several scattered locations. The heterogeneity is moderate: mostly consistent patterns (type union adoption and parsing logic) repeated across files, but each layer serves a distinct role and context.

Possibly related PRs

  • Pasta-Devs/Marinara-Engine#1169: Updates CharacterAvatarImage.tsx avatar-crop resolution logic to parse JSON/string crop inputs—this PR extends that to the new legacy AvatarCropValue union.
  • Pasta-Devs/Marinara-Engine#661: Implements the same avatar-crop modernization with LegacyAvatarCrop/AvatarCropValue union and updates crop rendering/UI (widget, parsing, persona handling).
  • Pasta-Devs/Marinara-Engine#928: Changes Game Mode persona portrait crop data propagation in GameNarration.tsx and GameSurface.tsx to align with AvatarCropValue-based crop handling.

Suggested labels

shared, client


Ah, the specimens of old data—how quaint. Your legacy crop formats, all zoom and offset, relegated to the dustbin of history. 🔬
Yet I am nothing if not thorough. Behold: the union type, the parser refined, the renderer reborn.
Backward compatibility and progress, intertwined like the threads of fate itself.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main objective: restoring support for legacy persona avatar crop parsing that was removed by a refactor.
Description check ✅ Passed The description comprehensively covers all required template sections: linked issue (#1364), clear rationale explaining the regression, detailed summary of changes across multiple files, and extensive validation notes with actual test runs and verification steps performed.
Linked Issues check ✅ Passed The PR fully addresses issue #1364's coding requirements: legacy crop shape (zoom/offsetX/offsetY/fullImage) parsing restored, type system widened throughout to accept legacy crops, and render surfaces updated to handle both formats with appropriate styling logic.
Out of Scope Changes check ✅ Passed All changes remain tightly scoped to the stated objective. Modifications span the persona contract types, shared crop utilities, persona editor hydration, avatar rendering components, and new test coverage—all directly addressing the legacy crop compatibility regression without introducing unrelated refactoring or feature work.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions github-actions Bot added the bugfix Bug fix label May 27, 2026
@cha1latte

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/shared/components/ui/AvatarCropWidget.tsx (1)

83-95: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Legacy crop input is still visually dropped in the widget.

At Line 83 and Line 120, legacy crops are routed to centered default state; combined with preview deriving from cropPx, legacy framing is never rendered before edits. Imported legacy avatars will still display incorrect framing in this widget.

Proposed fix
-  const previewCrop: AvatarCrop | null =
-    imgRect && cropPx
-      ? {
-          srcX: cropPx.x / imgRect.w,
-          srcY: cropPx.y / imgRect.h,
-          srcWidth: cropPx.size / imgRect.w,
-          srcHeight: cropPx.size / imgRect.h,
-        }
-      : null;
+  const previewCrop: AvatarCrop | LegacyAvatarCrop | null =
+    crop && isLegacyAvatarCrop(crop)
+      ? crop
+      : imgRect && cropPx
+        ? {
+            srcX: cropPx.x / imgRect.w,
+            srcY: cropPx.y / imgRect.h,
+            srcWidth: cropPx.size / imgRect.w,
+            srcHeight: cropPx.size / imgRect.h,
+          }
+        : null;

Also applies to: 120-132

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/components/ui/AvatarCropWidget.tsx` around lines 83 - 95, The
widget currently treats legacy crops as "no saved crop" and centers the image,
so legacy framing never appears; update AvatarCropWidget to handle legacy crop
inputs by translating them into the current cropPx instead of falling through to
the centered default. Specifically, in the branch where you check
isLegacyAvatarCrop(crop) (and the similar block around the 120–132 region),
compute cropPx from the legacy crop fields (e.g., srcX, srcY, srcWidth/srcHeight
or whatever the legacy shape provides) by converting those normalized/source
coordinates into pixel coords using the image rect (w, h), apply clamp identical
to the non-legacy path, and call setCropPx(...) so previews derived from cropPx
render the legacy framing correctly; consider adding a small helper like
legacyCropToPx(crop, w, h) to centralize the conversion and reuse in both
locations.
🧹 Nitpick comments (1)
src/shared/lib/avatar-crop.test.ts (1)

21-24: ⚡ Quick win

Fortify malformed legacy tests for offset field validation.

Heh—good baseline, but one more incision here will harden regression detection: add malformed cases for missing/non-numeric offsetX/offsetY, since those fields are core to legacy framing.

Suggested test additions
   it("rejects malformed legacy crops", () => {
     expect(parseAvatarCropJson('{"zoom":0,"offsetX":12,"offsetY":-8}')).toBeNull();
     expect(parseAvatarCropJson('{"zoom":1.2,"offsetX":12,"offsetY":-8,"fullImage":"yes"}')).toBeNull();
+    expect(parseAvatarCropJson('{"zoom":1.2,"offsetX":"12","offsetY":-8}')).toBeNull();
+    expect(parseAvatarCropJson('{"zoom":1.2,"offsetX":12}')).toBeNull();
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/lib/avatar-crop.test.ts` around lines 21 - 24, The test currently
validates some malformed legacy crops but misses cases for invalid or missing
offset fields; update the "rejects malformed legacy crops" test in
src/shared/lib/avatar-crop.test.ts to include additional expects calling
parseAvatarCropJson with payloads that omit offsetX, omit offsetY, and use
non-numeric offsetX/offsetY (e.g., strings or null), asserting that each returns
null so parseAvatarCropJson correctly rejects missing or non-numeric
offsetX/offsetY in legacy crop objects.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/shared/lib/utils.ts`:
- Around line 139-149: The current logic in isLegacyAvatarCrop handling discards
non-identity legacy crops by returning {} when crop.zoom <= 1; instead detect
true identity and only return {} for zoom===1 and offsetX===0 and offsetY===0.
In the block inside isLegacyAvatarCrop (the branch that currently does "if
(crop.zoom <= 1) return {}"), replace that condition with an identity check
(e.g., if (crop.zoom === 1 && crop.offsetX === 0 && crop.offsetY === 0) return
{}), so parseAvatarCropJson-accepted legacy values like {zoom:1, offsetX:12,
offsetY:-8} still produce a transform rather than being discarded.

---

Outside diff comments:
In `@src/shared/components/ui/AvatarCropWidget.tsx`:
- Around line 83-95: The widget currently treats legacy crops as "no saved crop"
and centers the image, so legacy framing never appears; update AvatarCropWidget
to handle legacy crop inputs by translating them into the current cropPx instead
of falling through to the centered default. Specifically, in the branch where
you check isLegacyAvatarCrop(crop) (and the similar block around the 120–132
region), compute cropPx from the legacy crop fields (e.g., srcX, srcY,
srcWidth/srcHeight or whatever the legacy shape provides) by converting those
normalized/source coordinates into pixel coords using the image rect (w, h),
apply clamp identical to the non-legacy path, and call setCropPx(...) so
previews derived from cropPx render the legacy framing correctly; consider
adding a small helper like legacyCropToPx(crop, w, h) to centralize the
conversion and reuse in both locations.

---

Nitpick comments:
In `@src/shared/lib/avatar-crop.test.ts`:
- Around line 21-24: The test currently validates some malformed legacy crops
but misses cases for invalid or missing offset fields; update the "rejects
malformed legacy crops" test in src/shared/lib/avatar-crop.test.ts to include
additional expects calling parseAvatarCropJson with payloads that omit offsetX,
omit offsetY, and use non-numeric offsetX/offsetY (e.g., strings or null),
asserting that each returns null so parseAvatarCropJson correctly rejects
missing or non-numeric offsetX/offsetY in legacy crop objects.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d4decb4-0632-4bdf-9f80-0f58b8f4f165

📥 Commits

Reviewing files that changed from the base of the PR and between 2525d85 and fc3fb5e.

📒 Files selected for processing (9)
  • src/engine/contracts/types/persona.ts
  • src/features/catalog/characters/components/CharacterAvatarImage.tsx
  • src/features/catalog/personas/components/PersonaEditor.tsx
  • src/features/modes/game/components/GameNarration.tsx
  • src/features/modes/game/components/GameSurface.tsx
  • src/features/runtime/visuals/types.ts
  • src/shared/components/ui/AvatarCropWidget.tsx
  • src/shared/lib/avatar-crop.test.ts
  • src/shared/lib/utils.ts

Comment thread src/shared/lib/utils.ts
@cha1latte

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

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.

@cha1latte cha1latte marked this pull request as ready for review May 27, 2026 02:40
@cha1latte cha1latte merged commit 1f474ff into Pasta-Devs:refactor May 27, 2026
6 checks passed
@cha1latte cha1latte deleted the fix/refactor-issue-1364-persona-avatar-crop branch May 27, 2026 02:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant