Skip to content

Keep text and images in place when resized on defense - #227

Merged
SunkenInTime merged 6 commits into
mainfrom
t3code/defense-text-box-resizing-main
Sep 29, 2026
Merged

SunkenInTime merged 6 commits into
mainfrom
t3code/defense-text-box-resizing-main

Conversation

@SunkenInTime

@SunkenInTime SunkenInTime commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

On defense, dragging a text box's resize handle moved the wrong edge. Images had the same bug.

A text box or image on defense mirrors its attack placement, so it hangs from its bottom-right corner. When the width grew, the left edge moved and the right edge stayed put. The width was measured from that moving left edge to the pointer, so each drag update fed the next one and the box shot off to the left, away from the cursor.

Reproduction

  1. Open a strategy and switch to defense.
  2. Place a text box and drag its right-hand resize handle to the right.
  3. Expected: the right edge follows the pointer and the box stays where it is, as on attack. Actual: the right edge stays put and the box grows leftward, far past the pointer.

Before and after: defense, dragging the resize handle 120 px right. Rows are start, mid-drag, released; the red dot is the pointer

Left: before. Right: after. Rows: start, mid-drag, released. The red dot is the pointer. These captures come from a throwaway widget test that renders the real PlacedTextBuilder on defense (run on the cloud branch, where the code is identical).

Fix

  • CanonicalPositionedBox takes an optional pinnedScreenPosition. While it is set, the box keeps that on-screen top-left at any size.
  • PlacedTextBuilder and PlacedImageBuilder now position themselves, so they can pin during a resize. The lists pass them isAttack, which they already read. When the drag starts they pin the current top-left. On release they store the new size, and the canonical position that keeps the top-left there, synchronously, so a page save right after sees it. The pin holds for one more frame. Once the final size is laid out, the position is stored again if it moved, but only while the provider still holds the exact item that was resized. So a page switch can't touch the next page's copy (same id). On attack that position is the one already stored.
  • A side switch while the handle is held moves the pin to where the box now shows. It also records how far the left edge moved, so the width keeps following the pointer (from Greptile's review).
  • TextProvider.updateSize became resize(id, size:, position:) and PlacedImageProvider.updateScale became resize(id, scale:, position:). They look up by id rather than index.

No data or payload shape changes. The fix writes the same size/scale and position fields as before.

Tests

  • New test/placed_box_resize_test.dart covers text and images on attack and defense, plus a side switch mid-resize a release in the same frame as the last move, and a page switch right after release. It drives the real resize handle and checks that the top-left never moves, both during the drag and after release from the stored position alone. With the pin disabled, the defense text case fails: the left edge moved 15.5 px on the first update.
  • test/text_widget_resilience_test.dart harness: PlacedTextBuilder now positions itself, so it is no longer wrapped in Positioned.
  • Full flutter test --no-pub: 649 passed. dart analyze is clean on touched files.

This bug shipped to desktop in desktop-stable-v4.6.2+102. This is the main copy of #226 plus #231 (cloud); the only difference is that main's ImageWidget keeps its link: argument, and the new test stubs the Hive-backed color library and strategy storage that main reads.

🤖 Generated with Claude Code

RetriggerConfidence Score: 4/5

Not safe to merge until an immediate page switch preserves the position of resized text and images on defense.

Findings

  1. P1 Page switch saves shifted resize ▶

Summary

The PR saves text and image dimensions immediately when resizing ends, then corrects their positions after the next frame. On defense, switching pages before that frame can save an incorrect position, causing the item to reopen shifted. This needs fixing before merge.

Reviews (4) · Last reviewed commit: "Store a resize's size on release and set..."

On defense a text box or image mirrors attack, so it hangs from its
bottom-right corner. Dragging the right-hand resize handle moved the
left edge instead, and because the width was measured from that moving
edge, the box ran away from the pointer.

A resize now pins the box's on-screen top-left, and on release stores
the size together with the canonical position that keeps it there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: a08cf222-5691-4b35-bd78-063587f29e64

📥 Commits

Reviewing files that changed from the base of the PR and between 3724d85 and dcf217c.

📒 Files selected for processing (8)
  • lib/providers/image_provider.dart
  • lib/providers/text_provider.dart
  • lib/widgets/draggable_widgets/canonical_positioned.dart
  • lib/widgets/draggable_widgets/image/placed_image_builder.dart
  • lib/widgets/draggable_widgets/placed_widget_builder.dart
  • lib/widgets/draggable_widgets/text/placed_text_builder.dart
  • test/placed_box_resize_test.dart
  • test/text_widget_resilience_test.dart

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Text and image resizing now tracks a pinned screen position and saves both size and position through provider methods that look up items by ID. The resize builders also handle changes between attack and defense sides during a resize.

Changes

Placed Box Resizing

Layer / File(s) Summary
Resize state and positioning
lib/providers/image_provider.dart, lib/providers/text_provider.dart, lib/widgets/draggable_widgets/canonical_positioned.dart
The providers replace index-based, size-only updates with resize methods that update size and position by item ID. CanonicalPositionedBox accepts a pinned screen position and relayouts when it changes.
Resize gestures and integration
lib/widgets/draggable_widgets/image/placed_image_builder.dart, lib/widgets/draggable_widgets/text/placed_text_builder.dart, lib/widgets/draggable_widgets/placed_widget_builder.dart, test/placed_box_resize_test.dart, test/text_widget_resilience_test.dart
The builders track resize geometry, adjust the pinned position when the attack/defense side changes, and save size and position at resize end. Parent builders provide drag callbacks. Tests cover text and image resizing, side changes during text resizing, release before the next frame, and text harness setup.

Priority: ⬇️ Low

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

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant Pointer
  participant PlacedTextBuilder
  participant TextProvider
  participant StrategyState
  Pointer->>PlacedTextBuilder: Move resize handle
  PlacedTextBuilder->>PlacedTextBuilder: Update pinned position and displayed size
  Pointer->>PlacedTextBuilder: End resize
  PlacedTextBuilder->>TextProvider: Save size and document position by ID
  PlacedTextBuilder->>StrategyState: Mark strategy unsaved
Loading

Merge Risk: ⚪ Minimal · up to dcf21

No actionable resize regression remains identified; the change is mergeable after normal checks.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to dcf21

A resize appears capable of changing an item on a different page if the page changes before the gesture ends. This is a conditional document-integrity risk, not an identified access-control issue.

Retained concerns

  • Medium · reliability · inferred: A resize completed after a page switch may persist the originating page's size and position onto a copied, same-ID item on the newly active page.
Security review details

Security Blast Radius

  • inferred — The identified ownership failure is bounded to an active strategy's page-local text or image geometry, including copied items with matching IDs; evidence does not establish cross-principal reachability.

Trust Boundaries and Controls

  • observed — Both providers reject an ID missing from current state, but neither resize contract binds a present ID to the page on which its gesture began.

Hardening Proposals

  • proposed — Bind an active resize to its originating page or generation, and discard or cancel its transient pin when that ownership changes before release.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preserving text and image positions during resizing on defense.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

SunkenInTime and others added 2 commits September 29, 2026 01:10
Switching sides while holding a resize handle left the pin at the old
side's screen position, and release read it on the new side, so the
stored position landed hundreds of pixels off. The pin now moves to
where the box shows on the new side.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A side switch mid-resize mirrors the box away from the held pointer, and
the next drag update measured the width from the new left edge, so the
box jumped hundreds of pixels wide. The builders now remember how far
the left edge moved and measure from where it was.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread lib/widgets/draggable_widgets/text/placed_text_builder.dart Outdated
@greptile-apps

greptile-apps Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Comments Outside Diff

These findings could not be posted inline.

  • P2 Immediate defense resize and page switch persists a shifted text or image ▶

    • Bug
      • A final resize movement followed by release before layout saves the new width or scale with a position calculated from the previous rendered box size. Switching pages before the next frame prevents correction. On reload, the reproduced text and image were each 40 px left of their pinned position.
    • Cause
      • _finishResize immediately calls _storeResize before awaiting endOfFrame (lib/widgets/draggable_widgets/text/placed_text_builder.dart:97–106; analogous image/placed_image_builder.dart:91–102). _storeResize reads the stale RenderBox.size to calculate the defense position (text:117–128; image:112–123). The later correction is skipped when the original item is no longer loaded.
    • Fix
      • Calculate the release position using the final resized dimensions before saving, rather than relying on a subsequent frame to correct a position that may already have been flushed.

A resize waited one frame after release before storing, so the final
size was laid out. A page switch inside that frame could land the
resize on the next page's copy of the item, which keeps the same id.

Pointer events arrive between frames, and a frame builds and lays out
together. So on release the box on screen always matches the size it
was last built at. Storing that size with the rendered box makes the
commit synchronous and exact. A final move the frame never drew is
dropped, so the box stays where the user last saw it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread lib/widgets/draggable_widgets/text/placed_text_builder.dart Outdated
Storing the size last drawn at release dropped movement that arrived
after the last frame: a quick drag could be lost entirely. Go back to
storing the final size one frame after release, when it is laid out.
Write it only if the text or image being resized is still loaded. A page
switch in that frame loads the next page's copy, which keeps the same
id, so the resize can no longer land on it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread lib/widgets/draggable_widgets/text/placed_text_builder.dart
With the write deferred a frame, a page save right after release still
read the old size, and the page switch then replaced the item, so the
resize was lost. Store the size, and a position from the current
layout, as soon as the handle is released. Hold the pin one more frame,
then store the position again if the final layout moved it, only while
the same item is still loaded. Normally that second write finds nothing
to change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread lib/widgets/draggable_widgets/text/placed_text_builder.dart
@SunkenInTime
SunkenInTime merged commit d8f2a2d into main Sep 29, 2026
3 checks passed
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