fix: prevent rect mutation during tag bin probing#67
fix: prevent rect mutation during tag bin probing#67gminor7 wants to merge 1 commit intosoimy:masterfrom
Conversation
Greptile SummaryThis PR prevents the original
Confidence Score: 4/5The fix is safe for plain-object callers (the documented and tested pattern) but introduces a regression for Rectangle class instance callers in the non-exclusive-tag path. One P1 finding: the spread clone omits prototype getters, silently breaking bin-sharing for Rectangle class instance users. A minor P2 comment-formatting artifact is also present. The core mutation bug is correctly fixed for the predominant plain-object usage pattern, so the risk is bounded to a specific combination (Rectangle instances + non-exclusive tags), but it still needs to be addressed before merging. src/maxrects-packer.ts — the Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["addArray(rects) — non-exclusiveTag path"] --> B["testBin = bin.clone()"]
B --> C["For each rect in same-tag group"]
C --> D{"rect is plain object?"}
D -- Yes --> E["testRect = { ...rect, rot:false, data:{...rect.data} }\ncopies width, height, x, y ✓"]
D -- No: Rectangle instance --> F["testRect = { ...rect, rot:false, data:{...} }\ncopies _width, _height ... NOT width/height ✗"]
E --> G["testBin.add(testRect)\nfindNode(testRect.width + pad, ...) → valid"]
F --> H["testBin.add(testRect)\nfindNode(undefined + pad = NaN, ...) → always undefined"]
G -- "undefined (no fit)" --> I["return false — try next bin"]
G -- "non-undefined (fits)" --> J["continue loop"]
H --> I
I --> K["targetBin never found → new bin created for every group"]
J --> L["All rects fit → add to current bin ✓"]
Reviews (1): Last reviewed commit: "fix: prevent rect mutation during tag bi..." | Re-trigger Greptile |
| const testRect = { ...rect, rot:false, data: { ...rect.data } } | ||
| if (testBin.add(testRect) === undefined) { |
There was a problem hiding this comment.
Spread breaks placement test for
Rectangle class instances
{ ...rect } on a Rectangle instance only copies the private backing fields set directly in the constructor (_width, _height, _x, _y, _rot, etc.) — not the prototype getters (width, height, x, y). As a result, testRect.width is undefined, and place() calls findNode(undefined + padding, ...) which returns undefined every time. testBin.add(testRect) therefore always returns undefined, causing every existing bin to be rejected during probing and all rects to be placed in new bins instead of being packed together.
The fix works correctly when callers pass plain objects implementing IRectangle (the pattern shown in all existing tests), but silently breaks bin-sharing for users who pass Rectangle class instances. To cover both cases, extract the getter values explicitly:
const testRect: IRectangle = {
width: rect.width,
height: rect.height,
x: rect.x,
y: rect.y,
rot: false,
data: rect.data ? { ...rect.data } : rect.data,
tag: (rect as any).tag,
};| + // Use a cloned rect for test placement to avoid mutating | ||
| + // the original rect (especially `rot`) during bin probing |
There was a problem hiding this comment.
Git diff markers accidentally committed into source
Lines 170–171 contain a literal + character (a git diff marker) in the middle of the comment text, e.g. + // Use a cloned rect.... These should be plain comment lines.
| + // Use a cloned rect for test placement to avoid mutating | |
| + // the original rect (especially `rot`) during bin probing | |
| // Use a cloned rect for test placement to avoid mutating | |
| // the original rect (especially `rot`) during bin probing |
Fixes issue #66