Resize minimum-size limit grows as the user zooms out, making objects unshrinkable at low zoom #77
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_whiteboard#77
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Bug
The live resize floor enforced by the Konva Transformer's
boundBoxFuncis interpreted as world units, but Konva supplies the box dimensions in absolute (stage-scaled) coordinates. As a result, the effective world-unit minimum grows as the user zooms out — at low zoom levels objects become impossible to shrink at all, even though their on-screen size is already small.Reproduction
Reproducible with calendar (most obvious), kanban, and any object once it gets near the 40x30 hard floor.
Root cause
In
crates/hero_whiteboard_ui/static/web/js/whiteboard/tools.js(the transformer'sboundBoxFunc):Konva builds
newBoxin__getNodeShapeaswidth: rect.width * absoluteScale.x.getAbsoluteScale()walks up to the stage and includesstage.scaleX(). SonewBox.width = world_width * stage.scaleX()— i.e., screen pixels.But the constants in this function (
40,30, calendargetMinSize()returns 280/260/etc., kanbancols * 108 + ...) are world units. Comparing world-unit constants against screen-pixel box widths means: the smaller the zoom, the larger the effective world-unit floor.Scope
Affects every resizable object — every selection routes through the same transformer's
boundBoxFunc. So the fix needs to cover sticky, text, shape, frame, document, image, webframe, drawing, calendar, kanban, mindmap.Expected behavior
The minimum-size limit should be evaluated in world units, independent of zoom. The user can shrink a calendar to its content-readability minimum (e.g., 280 world units for month view) at any zoom level, just as they can at 100% zoom.
Affected file
crates/hero_whiteboard_ui/static/web/js/whiteboard/tools.js— theboundBoxFuncinside theKonva.Transformerconfig (around lines 51-71).Only the live
boundBoxFuncis affected. The post-resize per-type floors inobjects.js(Math.max(60, Math.round(bg.width() * scaleX)), etc.) already work in world units becausebg.width()is the unscaled property andscaleXis the relative transform factor — no fix needed there.Fix sketch
Divide
newBox.widthandnewBox.heightby the stage's scale before comparing against the world-unit minimums:Acceptance criteria
getMinSize()floor (280x260 month, 320x220 week, 200x280 day) at any zoom.getAbsoluteScalevalues (the stage scale is what matters).Implementation Spec for Issue #77
Objective
Make the live resize floor zoom-independent by evaluating the transformer's
boundBoxFuncthresholds in world units, so every object can be shrunk to the same minimum size regardless of the stage's current zoom level.Requirements
Files to Modify
crates/hero_whiteboard_ui/static/web/js/whiteboard/tools.js— theboundBoxFuncdefined on theKonva.Transformer(around lines 51-71).No other file needs changes. The post-resize per-type floors in
objects.js(Math.max(60, Math.round(bg.width() * scaleX)), etc.) already operate in world units (bg.width()is the unscaled property,scaleXis the relative transform factor) and continue to act as a back-stop after the user releases.Implementation Plan
Step 1: Convert boundBoxFunc thresholds to world units
Files:
crates/hero_whiteboard_ui/static/web/js/whiteboard/tools.jsInside the existing
boundBoxFunc(oldBox, newBox):newBox.width/newBox.heightwithworldW/worldHin the two existing comparisons:if (worldW < 40 || worldH < 30) return oldBox;if (minW > 0 && (worldW < minW || worldH < minH)) return oldBox;newBoxin stage-scaled coords and we divide by stage scale to compare against world-unit floors.Dependencies: none.
Acceptance Criteria
getMinSize()floor (month 280x260, week 320x220, day 200x280) at any zoom.stage.scaleX() === 1),worldW === newBox.widthandworldH === newBox.height— no behavior change there.< 40/< 30guard catches them after division by a positive stage scale).Notes
newBoxinTransformer.__getNodeShapeaswidth: rect.width * absoluteScale.x, wheregetAbsoluteScale()walks up to the stage and includesstage.scaleX(). SonewBox.width = world_width * stage.scaleX(). Dividing bystage.scaleX()recovers the world-unit width.getAbsoluteScalethat diverges from the stage's, except mindmap which keeps a per-node group scale; mindmap doesn't have a min inboundBoxFuncso it's not part of the comparison).Test Results
cargo test --workspace --lib: 4 lib targets, 0 tests / 0 passed / 0 failed each (no Rust unit tests for these crates).cargo clippy --workspace -- -D warnings: clean.node --check tools.js: parses cleanly.This is a JS-static-asset-only change; there are no JS unit tests in the repo. Recommended manual verification: open the same board in two windows at very different zoom levels (e.g., 100% and 17%) and confirm the same object can be shrunk to the same world-unit minimum in both.
Implementation Summary
Changes
crates/hero_whiteboard_ui/static/web/js/whiteboard/tools.js(+13 / -3 lines)Konva.Transformer'sboundBoxFunc, read the stage's current scale (stage.scaleX()/scaleY()).newBox.width/newBox.heightto world units by dividing by the stage scale.getMinSize()per view, kanban formula).Why this fixes it for every object
Every resizable object's selection routes through this single transformer's
boundBoxFunc— sticky, text, shape, frame, document, image, webframe, drawing, calendar, kanban, mindmap. Fixing the comparison once covers them all.Verification
cargo test --workspace --libpasses (4 lib targets, 0 tests).cargo clippy --workspace -- -D warningsclean.node --check tools.jsparses cleanly.Notes / caveats
stage.scaleX() === 1),worldW === newBox.widthand behavior is identical to before.objects.js(sticky 60, frame 80, document 140x100, webframe 180x120, image 40, etc.) already work in world units (bg.width()is unscaled,scaleXis the relative transform factor) and continue to act as a back-stop after release.