Text objects start at hardcoded 400px width and can't be resized — should auto-fit content and scale font on resize #149
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#149
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?
Bugs / desired behavior
A) New text starts at ~408px wide regardless of content
The default "Text" object renders with bg width 408px, leaving lots of empty padding. Even one short word ends up in a wide box.
B) The transformer resize handles do nothing functional on text
Grabbing a corner / side handle and dragging visually scales the text during the drag, but on commit the scale is reset to 1 and the content re-renders at the same size. The drag amount is silently discarded.
Desired behavior (from the user)
Root cause
A) Hardcoded maxWidth
crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js:380—renderTextContentpassesmaxWidth: 400toWhiteboardMarkdown.renderMarkdown.crates/hero_whiteboard_ui/static/web/js/whiteboard/markdown.js:483— everyKonva.Textline is created with explicitwidth: maxWidth - (line.indent || 0) - (inCodeBlock ? 16 : 0). SotextNode.width()is always ~400 regardless of actual content width.objects.js:387-394then computes the bg's width fromc.width() + 8, which is always ~408 because the line widths are forced.B) Resize discards the scale
crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js:1427-1432— the text branch of the transform-end handler just resetsscaleX/scaleYto 1 and re-renders with the same hardcoded maxWidth. There is no code path that turns the dragged scale into either a width change or a font-size change.Fix scope
Markdown renderer — opt-in no-wrap mode
autoWidthboolean option toWhiteboardMarkdown.renderMarkdown(or a sentinelmaxWidth: 0ornull). When set, lines are emitted asKonva.TextWITHOUT an explicitwidthso each line auto-sizes to its content. Word-wrap happens only at explicit\nin the source. All paths that depend onmaxWidth(table column width, image scaling, code-block padding) must either be skipped (no tables / images / code blocks in plain text mode) or fall back to the natural per-element width.maxWidth(their containers already have a known width).Text object
createTextBoxno longer needs_mdMaxWidth. Default font size 18 is unchanged. Initial render uses the auto-width markdown mode.renderTextContentcallsrenderMarkdown(group, text, { autoWidth: true, baseFontSize: group._mdFontSize, ... })and computes the bg dimensions from the actual rendered children:maxW = max(line.width()) + padding,sumH = sum(line.height()) + padding.objects.js:1427) for text:factor = min(scaleX, scaleY)so the text always fits inside the new bounding box (corner drag = uniform scale; side drag still uses the smaller axis as the limiting factor, so text doesn't overflow).group._mdFontSize = max(8, round(group._mdFontSize * factor))— clamp to a minimum to avoid invisible text.group.scaleX(1) / scaleY(1).min(scaleX, scaleY)naturally enforce fit.Sync
_mdFontSizeis already serialized for text (sync.js:289-293), so font-size changes from resize round-trip automatically. No new fields needed.payload.width/payload.heightare computed from the bg inserializeForServerand will reflect the new natural size — fine.createTextBoxinapp.js:363-374does not need a new opt; existingfontSizeis enough.Affected files
crates/hero_whiteboard_ui/static/web/js/whiteboard/markdown.js— addautoWidthmode that omitswidthonKonva.Textlines. Plain text lines only — bail on tables / code blocks / images when autoWidth is on (text objects don't need them; sticky/shape/document still have full markdown).crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js—renderTextContentswitches to autoWidth and computes bg from actual line widths. Text branch of the transform-end handler scales_mdFontSizeand re-renders.Acceptance criteria
Notes
_mdSourceround-trips fine; on next load they re-render with auto-width and may visually shrink. That's the intended behavior.Implementation Spec for Issue #149
Objective
Make text objects auto-fit to their natural content width by default and make the transformer handle scale the font size proportionally on resize, replacing the current hardcoded 400px width and the no-op resize handler. Sticky, shape, and document objects (which have known container widths) keep their existing fixed-maxWidth wrapping behavior; the change is opt-in via a new
autoWidthoption onWhiteboardMarkdown.renderMarkdown.Requirements
\nin the source — no soft-wrapping in autoWidth mode._mdFontSizebymin(scaleX, scaleY)(clamped to a floor of 8), resets node scale to 1, and re-renders so the bg matches the natural size at the new font.transformstart/transformendhandlers)._mdFontSizeround-trip insync.js(no changes tosync.jsorapp.js).renderMarkdownare byte-identical (samemaxWidthvalue, no new opt passed).Files to Modify/Create
crates/hero_whiteboard_ui/static/web/js/whiteboard/markdown.js— addautoWidthoption; when true, omit explicitwidthon plain text/heading/checkboxKonva.Textnodes, forcewrap: 'none', and degrade heavyweight elements (tables, code blocks, images, hr) gracefully.crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js— passautoWidth: truefromrenderTextContent, replace the bg sizing pass, and rewrite thetype === 'text'branch ofapplyTransformto scale_mdFontSize.Implementation Plan
Step 1: Add
autoWidthmode tomarkdown.jsvar autoWidth = !!opts.autoWidth;at the top ofrender.new Konva.Textaround line 479): whenautoWidth, omitwidth, setwrap: 'none'. Konva computes intrinsic width.autoWidth, size totextNode.width() + 4(no maxWidth cap).autoWidthmode: degrade to plain-text fallthrough (render as a single text node containing the source line) or skip; document the scope limitation in a code comment.y - startY) unchanged.Dependencies: none
Step 2:
renderTextContentswitches to autoWidth and computes bg from rendered childrenrenderTextContent(objects.js:360), dropmaxWidth: 400and addautoWidth: trueto the renderMarkdown opts. Keepx: 4, y: 4.maxWloop (lines 387-394) with: iteratemd-line-*children, takeMath.max(maxW, child.x() + child.width())for width; use the existingcontentHeightreturn value for height.Konva.Rect({ width: max(maxW + 8, 30), height: max(contentHeight + 8, 30) }). KeepmoveToBottom.Dependencies: Step 1
Step 3: Rewrite the
type === 'text'branch ofapplyTransformtransformstart/transformendhandlers intools.js(line 201, 260) already callWhiteboardHistory.snapshotBeforeandWhiteboardHistory.commitUpdate+WhiteboardSync.onUpdate. No tools.js changes.Dependencies: Steps 1 and 2
Step 4: Verify untouched call sites
renderStickyContent(objects.js:283),renderShapeContent(~953),renderDocumentContent(~1824) still pass their existingmaxWidthand NOautoWidthopt.autoWidthdefaults toundefined/falsy so the existing fixed-width path is taken.Dependencies: none
Acceptance Criteria
line one\nline tworenders two lines, bg sized to the wider one._mdFontSizeround-trip).Notes
autoWidthdefaults to false.factor = min(scaleX, scaleY)already enforces "text fits inside dragged box" — non-uniform drags simply won't stretch the text.sync.js/app.jschanges:_mdFontSizeis already indata.fontSizefor text type, and bg dims are read frombg.width()/bg.height()after re-render.Math.abs(scaleX - 1) < 0.01) handles the no-op case. Each resize multiplies font bymin(scaleX, scaleY)and the new bg matches the new font's natural size.Test Results
cargo test --workspace --lib— PASScargo fmt --check— PASScargo clippy --workspace --all-targets -- -D warnings— PASSNotes
This change is JS only (
crates/hero_whiteboard_ui/static/web/js/whiteboard/markdown.jsandcrates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js). The Rust workspace tests do not exercise this code path; they confirm the workspace still builds, formats, and lints cleanly. Manual UI verification: a new text object should hug its content (no 408px box); typing without newlines should grow horizontally; explicit newlines wrap; dragging a transformer corner should scale the font size proportionally; ctrl+Z restores prior font; reload preserves the resized size; sticky / shape / document text wrapping unchanged.Implementation Summary
Text objects now auto-fit to their natural content width and the transformer scales the font proportionally on resize, replacing the hardcoded 400px width and the no-op resize handler.
Changes
crates/hero_whiteboard_ui/static/web/js/whiteboard/markdown.js— added anautoWidthopt to therenderfunction. When set, plain text / heading / checkboxKonva.Textnodes are emitted without an explicitwidthand withwrap: 'none', so each line measures its natural intrinsic width. Inline-code background rects skip theMath.min(..., maxWidth)cap. Tables, fenced code-block start/end backgrounds, images, and horizontal rules degrade to a single plain-text fallthrough (those layouts intrinsically need a known column width and are out of scope for plain text objects).crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js:renderTextContentnow callsrenderMarkdownwithautoWidth: true(no more hardcodedmaxWidth: 400). The bg width is computed from the actual rendered children:max(child.x() + child.width()) + padding, with a 30px floor so empty text is still pickable.type === 'text'branch ofapplyTransformnow scales_mdFontSizebymin(scaleX, scaleY)(clamped to a floor of 8), resets node scale, and re-renders. The new font produces a new natural bg size matching the dragged-to box.No changes to
tools.js,sync.js,app.js, or any other file. The existing globaltransformstart/transformendhandlers intools.jsalready wireWhiteboardHistory.snapshotBeforeandWhiteboardHistory.commitUpdate+WhiteboardSync.onUpdate._mdFontSizeis already serialized asdata.fontSizefor text type, so the resized size persists across reload with no schema changes.Sticky note, shape, and document call sites of
renderMarkdownare byte-identical (noautoWidthopt passed → defaults to false → existing fixed-width path is taken).Test Results
cargo test --workspace --lib— PASScargo fmt --check— PASScargo clippy --workspace --all-targets -- -D warnings— PASSManual Verification Required
\nline breaks — each line is independent, bg sized to the wider one.Out of Scope (per the issue)
keepRatiotoggle on the global transformer (themin(scaleX, scaleY)factor already enforces "text fits inside the dragged box" naturally).