Text objects start at hardcoded 400px width and can't be resized — should auto-fit content and scale font on resize #149

Open
opened 2026-05-05 13:29:18 +00:00 by AhmedHanafy725 · 3 comments
Member

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)

  • No max width. Text auto-sizes to its natural content width. Short text → small box. Long text → wide box. Multi-line text wraps only on explicit newlines (no automatic word-wrap at a fixed width).
  • Resize updates font size. Dragging a corner / side handle scales the font proportionally so the same text fits the new bounding box. (Like PowerPoint / Keynote text boxes when you grab a corner with the proportion lock on.)

Root cause

A) Hardcoded maxWidth

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js:380renderTextContent passes maxWidth: 400 to WhiteboardMarkdown.renderMarkdown.
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/markdown.js:483 — every Konva.Text line is created with explicit width: maxWidth - (line.indent || 0) - (inCodeBlock ? 16 : 0). So textNode.width() is always ~400 regardless of actual content width.
  • objects.js:387-394 then computes the bg's width from c.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 resets scaleX/scaleY to 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

  • Add an autoWidth boolean option to WhiteboardMarkdown.renderMarkdown (or a sentinel maxWidth: 0 or null). When set, lines are emitted as Konva.Text WITHOUT an explicit width so each line auto-sizes to its content. Word-wrap happens only at explicit \n in the source. All paths that depend on maxWidth (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.
  • Sticky / shape / document keep using fixed maxWidth (their containers already have a known width).

Text object

  • createTextBox no longer needs _mdMaxWidth. Default font size 18 is unchanged. Initial render uses the auto-width markdown mode.
  • renderTextContent calls renderMarkdown(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.
  • Transform-end (objects.js:1427) for text:
    • Compute 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.
    • Reset group.scaleX(1) / scaleY(1).
    • Re-render — the new font size produces a new natural bg size that matches (or is the largest fit of) the dragged box.
    • Snapshot history, sync.
  • Optional polish: lock the text transformer to keepRatio (only corner anchors) so the user can't create awkward non-uniform stretches. Or leave free-resize and let min(scaleX, scaleY) naturally enforce fit.

Sync

  • _mdFontSize is already serialized for text (sync.js:289-293), so font-size changes from resize round-trip automatically. No new fields needed.
  • payload.width / payload.height are computed from the bg in serializeForServer and will reflect the new natural size — fine.
  • createTextBox in app.js:363-374 does not need a new opt; existing fontSize is enough.

Affected files

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/markdown.js — add autoWidth mode that omits width on Konva.Text lines. 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.jsrenderTextContent switches to autoWidth and computes bg from actual line widths. Text branch of the transform-end handler scales _mdFontSize and re-renders.

Acceptance criteria

  • A new text object with default content "Text" renders with a bg width that snugly fits the word (~50–80px), not ~408px.
  • Typing a long line in inline edit produces a single line that grows horizontally as the user types; closing the editor leaves the bg at the natural rendered width.
  • Pressing Enter in inline edit creates explicit line breaks; each line's bg width is independent.
  • No automatic word-wrap at a fixed maximum — long text without newlines becomes a single wide line.
  • Dragging a corner handle of a selected text object scales the bounding box; on release the font size has updated proportionally so the same text fits the new box. The new size persists across reload.
  • Side / edge handles also scale font (using the smaller axis factor) so text never overflows the dragged box.
  • Font size has a sensible floor (e.g. 8px) so the user can't drag text down to invisibility.
  • Text font-family, fill color, alignment, and Esc-to-cancel inline editing behavior are unchanged.
  • Sticky / shape / document objects (which currently use the same markdown renderer with a real maxWidth) are unaffected — they continue to wrap at their bg width.

Notes

  • The new text-object semantics match PowerPoint / Keynote / Figma text boxes: by default they auto-size to content; explicit resize scales the font; explicit text-frame width adjustments require entering edit mode and adding line breaks.
  • Tables / code blocks / images inside plain text objects are out of scope. Users who need rich layout should use Document objects.
  • Existing text objects in saved boards: their _mdSource round-trips fine; on next load they re-render with auto-width and may visually shrink. That's the intended behavior.
## 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) - **No max width.** Text auto-sizes to its natural content width. Short text → small box. Long text → wide box. Multi-line text wraps only on explicit newlines (no automatic word-wrap at a fixed width). - **Resize updates font size.** Dragging a corner / side handle scales the font proportionally so the same text fits the new bounding box. (Like PowerPoint / Keynote text boxes when you grab a corner with the proportion lock on.) ## Root cause ### A) Hardcoded maxWidth - `crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js:380` — `renderTextContent` passes `maxWidth: 400` to `WhiteboardMarkdown.renderMarkdown`. - `crates/hero_whiteboard_ui/static/web/js/whiteboard/markdown.js:483` — every `Konva.Text` line is created with explicit `width: maxWidth - (line.indent || 0) - (inCodeBlock ? 16 : 0)`. So `textNode.width()` is always ~400 regardless of actual content width. - `objects.js:387-394` then computes the bg's width from `c.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 resets `scaleX/scaleY` to 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 - Add an `autoWidth` boolean option to `WhiteboardMarkdown.renderMarkdown` (or a sentinel `maxWidth: 0` or `null`). When set, lines are emitted as `Konva.Text` WITHOUT an explicit `width` so each line auto-sizes to its content. Word-wrap happens only at explicit `\n` in the source. All paths that depend on `maxWidth` (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. - Sticky / shape / document keep using fixed `maxWidth` (their containers already have a known width). ### Text object - `createTextBox` no longer needs `_mdMaxWidth`. Default font size 18 is unchanged. Initial render uses the auto-width markdown mode. - `renderTextContent` calls `renderMarkdown(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`. - Transform-end (`objects.js:1427`) for text: - Compute `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. - Reset `group.scaleX(1) / scaleY(1)`. - Re-render — the new font size produces a new natural bg size that matches (or is the largest fit of) the dragged box. - Snapshot history, sync. - Optional polish: lock the text transformer to keepRatio (only corner anchors) so the user can't create awkward non-uniform stretches. Or leave free-resize and let `min(scaleX, scaleY)` naturally enforce fit. ### Sync - `_mdFontSize` is already serialized for text (`sync.js:289-293`), so font-size changes from resize round-trip automatically. No new fields needed. - `payload.width` / `payload.height` are computed from the bg in `serializeForServer` and will reflect the new natural size — fine. - `createTextBox` in `app.js:363-374` does not need a new opt; existing `fontSize` is enough. ## Affected files - `crates/hero_whiteboard_ui/static/web/js/whiteboard/markdown.js` — add `autoWidth` mode that omits `width` on `Konva.Text` lines. 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` — `renderTextContent` switches to autoWidth and computes bg from actual line widths. Text branch of the transform-end handler scales `_mdFontSize` and re-renders. ## Acceptance criteria - [ ] A new text object with default content "Text" renders with a bg width that snugly fits the word (~50–80px), not ~408px. - [ ] Typing a long line in inline edit produces a single line that grows horizontally as the user types; closing the editor leaves the bg at the natural rendered width. - [ ] Pressing Enter in inline edit creates explicit line breaks; each line's bg width is independent. - [ ] No automatic word-wrap at a fixed maximum — long text without newlines becomes a single wide line. - [ ] Dragging a corner handle of a selected text object scales the bounding box; on release the font size has updated proportionally so the same text fits the new box. The new size persists across reload. - [ ] Side / edge handles also scale font (using the smaller axis factor) so text never overflows the dragged box. - [ ] Font size has a sensible floor (e.g. 8px) so the user can't drag text down to invisibility. - [ ] Text font-family, fill color, alignment, and Esc-to-cancel inline editing behavior are unchanged. - [ ] Sticky / shape / document objects (which currently use the same markdown renderer with a real maxWidth) are unaffected — they continue to wrap at their bg width. ## Notes - The new text-object semantics match PowerPoint / Keynote / Figma text boxes: by default they auto-size to content; explicit resize scales the font; explicit text-frame width adjustments require entering edit mode and adding line breaks. - Tables / code blocks / images inside plain text objects are out of scope. Users who need rich layout should use Document objects. - Existing text objects in saved boards: their `_mdSource` round-trips fine; on next load they re-render with auto-width and may visually shrink. That's the intended behavior.
Author
Member

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 autoWidth option on WhiteboardMarkdown.renderMarkdown.

Requirements

  • Default "Text" object renders with a background that hugs the rendered text, not 408px wide.
  • Multi-line wrapping happens only on explicit \n in the source — no soft-wrapping in autoWidth mode.
  • Transformer drag on a text object scales _mdFontSize by min(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.
  • Resize is undoable: history snapshot before, commit after (already wired through the global transformstart / transformend handlers).
  • New font size persists across reload via the existing _mdFontSize round-trip in sync.js (no changes to sync.js or app.js).
  • Sticky / shape / document call sites of renderMarkdown are byte-identical (same maxWidth value, no new opt passed).

Files to Modify/Create

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/markdown.js — add autoWidth option; when true, omit explicit width on plain text/heading/checkbox Konva.Text nodes, force wrap: 'none', and degrade heavyweight elements (tables, code blocks, images, hr) gracefully.
  • crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js — pass autoWidth: true from renderTextContent, replace the bg sizing pass, and rewrite the type === 'text' branch of applyTransform to scale _mdFontSize.

Implementation Plan

Step 1: Add autoWidth mode to markdown.js

  • Read var autoWidth = !!opts.autoWidth; at the top of render.
  • Plain text / headings (the new Konva.Text around line 479): when autoWidth, omit width, set wrap: 'none'. Konva computes intrinsic width.
  • Checkbox text (around line 428): same treatment for the text portion; checkbox box stays fixed.
  • Inline code background rect (line 497): when autoWidth, size to textNode.width() + 4 (no maxWidth cap).
  • Tables / code blocks / images / hr in autoWidth mode: 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.
  • Return value (y - startY) unchanged.
    Dependencies: none

Step 2: renderTextContent switches to autoWidth and computes bg from rendered children

  • In renderTextContent (objects.js:360), drop maxWidth: 400 and add autoWidth: true to the renderMarkdown opts. Keep x: 4, y: 4.
  • Replace the maxW loop (lines 387-394) with: iterate md-line-* children, take Math.max(maxW, child.x() + child.width()) for width; use the existing contentHeight return value for height.
  • Bg = Konva.Rect({ width: max(maxW + 8, 30), height: max(contentHeight + 8, 30) }). Keep moveToBottom.
    Dependencies: Step 1

Step 3: Rewrite the type === 'text' branch of applyTransform

  • Replace lines 1427-1432 with:
    } else if (type === 'text') {
        var factor = Math.min(scaleX, scaleY);
        var prev = node._mdFontSize || 18;
        var next = Math.max(8, Math.round(prev * factor));
        node._mdFontSize = next;
        node.scaleX(1);
        node.scaleY(1);
        renderTextContent(node);
        WhiteboardCanvas.getObjectLayer().batchDraw();
    }
    
  • The global transformstart / transformend handlers in tools.js (line 201, 260) already call WhiteboardHistory.snapshotBefore and WhiteboardHistory.commitUpdate + WhiteboardSync.onUpdate. No tools.js changes.
    Dependencies: Steps 1 and 2

Step 4: Verify untouched call sites

  • Confirm renderStickyContent (objects.js:283), renderShapeContent (~953), renderDocumentContent (~1824) still pass their existing maxWidth and NO autoWidth opt. autoWidth defaults to undefined/falsy so the existing fixed-width path is taken.
    Dependencies: none

Acceptance Criteria

  • New text object with default "Text" yields a bg ~30-40px wide (not 408px).
  • Long single-line typing grows the bg horizontally, no wrapping.
  • line one\nline two renders two lines, bg sized to the wider one.
  • Dragging a corner outward roughly doubles the font size; bg matches the dragged-to box.
  • Dragging inward to tiny size clamps font at 8px.
  • Ctrl+Z restores prior font size + bg.
  • Reload restores the resized font size (verifies _mdFontSize round-trip).
  • Sticky / shape / document text wrapping and resize unchanged.

Notes

  • autoWidth scope. Supports paragraphs, headings, bold/italic/strikethrough, inline code, links, blockquotes, checkboxes, lists. Tables / fenced code blocks / images / hr render in degraded "as-is text" form because they intrinsically need a column width. Users wanting rich block layout should use Document objects.
  • Sticky / shape / document untouched. Step 4 explicitly verifies — autoWidth defaults to false.
  • Keep-ratio. The global transformer is shared across all object types; per-selection keepRatio toggling is out of scope. factor = min(scaleX, scaleY) already enforces "text fits inside dragged box" — non-uniform drags simply won't stretch the text.
  • Sync round-trip. No sync.js / app.js changes: _mdFontSize is already in data.fontSize for text type, and bg dims are read from bg.width()/bg.height() after re-render.
  • Font-size floor. 8px. No high-end cap. Round to integer pixels.
  • Repeated resize convergence. The handler's existing early-return at line 1408 (Math.abs(scaleX - 1) < 0.01) handles the no-op case. Each resize multiplies font by min(scaleX, scaleY) and the new bg matches the new font's natural size.
## 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 `autoWidth` option on `WhiteboardMarkdown.renderMarkdown`. ### Requirements - Default "Text" object renders with a background that hugs the rendered text, not 408px wide. - Multi-line wrapping happens only on explicit `\n` in the source — no soft-wrapping in autoWidth mode. - Transformer drag on a text object scales `_mdFontSize` by `min(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. - Resize is undoable: history snapshot before, commit after (already wired through the global `transformstart` / `transformend` handlers). - New font size persists across reload via the existing `_mdFontSize` round-trip in `sync.js` (no changes to `sync.js` or `app.js`). - Sticky / shape / document call sites of `renderMarkdown` are byte-identical (same `maxWidth` value, no new opt passed). ### Files to Modify/Create - `crates/hero_whiteboard_ui/static/web/js/whiteboard/markdown.js` — add `autoWidth` option; when true, omit explicit `width` on plain text/heading/checkbox `Konva.Text` nodes, force `wrap: 'none'`, and degrade heavyweight elements (tables, code blocks, images, hr) gracefully. - `crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js` — pass `autoWidth: true` from `renderTextContent`, replace the bg sizing pass, and rewrite the `type === 'text'` branch of `applyTransform` to scale `_mdFontSize`. ### Implementation Plan #### Step 1: Add `autoWidth` mode to `markdown.js` - Read `var autoWidth = !!opts.autoWidth;` at the top of `render`. - Plain text / headings (the `new Konva.Text` around line 479): when `autoWidth`, omit `width`, set `wrap: 'none'`. Konva computes intrinsic width. - Checkbox text (around line 428): same treatment for the text portion; checkbox box stays fixed. - Inline code background rect (line 497): when `autoWidth`, size to `textNode.width() + 4` (no maxWidth cap). - Tables / code blocks / images / hr in `autoWidth` mode: 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. - Return value (`y - startY`) unchanged. Dependencies: none #### Step 2: `renderTextContent` switches to autoWidth and computes bg from rendered children - In `renderTextContent` (objects.js:360), drop `maxWidth: 400` and add `autoWidth: true` to the renderMarkdown opts. Keep `x: 4, y: 4`. - Replace the `maxW` loop (lines 387-394) with: iterate `md-line-*` children, take `Math.max(maxW, child.x() + child.width())` for width; use the existing `contentHeight` return value for height. - Bg = `Konva.Rect({ width: max(maxW + 8, 30), height: max(contentHeight + 8, 30) })`. Keep `moveToBottom`. Dependencies: Step 1 #### Step 3: Rewrite the `type === 'text'` branch of `applyTransform` - Replace lines 1427-1432 with: ```js } else if (type === 'text') { var factor = Math.min(scaleX, scaleY); var prev = node._mdFontSize || 18; var next = Math.max(8, Math.round(prev * factor)); node._mdFontSize = next; node.scaleX(1); node.scaleY(1); renderTextContent(node); WhiteboardCanvas.getObjectLayer().batchDraw(); } ``` - The global `transformstart` / `transformend` handlers in `tools.js` (line 201, 260) already call `WhiteboardHistory.snapshotBefore` and `WhiteboardHistory.commitUpdate` + `WhiteboardSync.onUpdate`. No tools.js changes. Dependencies: Steps 1 and 2 #### Step 4: Verify untouched call sites - Confirm `renderStickyContent` (objects.js:283), `renderShapeContent` (~953), `renderDocumentContent` (~1824) still pass their existing `maxWidth` and NO `autoWidth` opt. `autoWidth` defaults to `undefined`/falsy so the existing fixed-width path is taken. Dependencies: none ### Acceptance Criteria - [ ] New text object with default "Text" yields a bg ~30-40px wide (not 408px). - [ ] Long single-line typing grows the bg horizontally, no wrapping. - [ ] `line one\nline two` renders two lines, bg sized to the wider one. - [ ] Dragging a corner outward roughly doubles the font size; bg matches the dragged-to box. - [ ] Dragging inward to tiny size clamps font at 8px. - [ ] Ctrl+Z restores prior font size + bg. - [ ] Reload restores the resized font size (verifies `_mdFontSize` round-trip). - [ ] Sticky / shape / document text wrapping and resize unchanged. ### Notes - **autoWidth scope.** Supports paragraphs, headings, bold/italic/strikethrough, inline code, links, blockquotes, checkboxes, lists. Tables / fenced code blocks / images / hr render in degraded "as-is text" form because they intrinsically need a column width. Users wanting rich block layout should use Document objects. - **Sticky / shape / document untouched.** Step 4 explicitly verifies — `autoWidth` defaults to false. - **Keep-ratio.** The global transformer is shared across all object types; per-selection keepRatio toggling is out of scope. `factor = min(scaleX, scaleY)` already enforces "text fits inside dragged box" — non-uniform drags simply won't stretch the text. - **Sync round-trip.** No `sync.js` / `app.js` changes: `_mdFontSize` is already in `data.fontSize` for text type, and bg dims are read from `bg.width()`/`bg.height()` after re-render. - **Font-size floor.** 8px. No high-end cap. Round to integer pixels. - **Repeated resize convergence.** The handler's existing early-return at line 1408 (`Math.abs(scaleX - 1) < 0.01`) handles the no-op case. Each resize multiplies font by `min(scaleX, scaleY)` and the new bg matches the new font's natural size.
Author
Member

Test Results

  • cargo test --workspace --lib — PASS
  • cargo fmt --check — PASS
  • cargo clippy --workspace --all-targets -- -D warnings — PASS

Notes

This change is JS only (crates/hero_whiteboard_ui/static/web/js/whiteboard/markdown.js and crates/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.

## Test Results - `cargo test --workspace --lib` — PASS - `cargo fmt --check` — PASS - `cargo clippy --workspace --all-targets -- -D warnings` — PASS ### Notes This change is JS only (`crates/hero_whiteboard_ui/static/web/js/whiteboard/markdown.js` and `crates/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.
Author
Member

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 an autoWidth opt to the render function. When set, plain text / heading / checkbox Konva.Text nodes are emitted without an explicit width and with wrap: 'none', so each line measures its natural intrinsic width. Inline-code background rects skip the Math.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:
    • renderTextContent now calls renderMarkdown with autoWidth: true (no more hardcoded maxWidth: 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.
    • The type === 'text' branch of applyTransform now scales _mdFontSize by min(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 global transformstart / transformend handlers in tools.js already wire WhiteboardHistory.snapshotBefore and WhiteboardHistory.commitUpdate + WhiteboardSync.onUpdate. _mdFontSize is already serialized as data.fontSize for text type, so the resized size persists across reload with no schema changes.

Sticky note, shape, and document call sites of renderMarkdown are byte-identical (no autoWidth opt passed → defaults to false → existing fixed-width path is taken).

Test Results

  • cargo test --workspace --lib — PASS
  • cargo fmt --check — PASS
  • cargo clippy --workspace --all-targets -- -D warnings — PASS

Manual Verification Required

  1. Create a new text object — bg hugs the default "Text" word, not a 408px-wide box.
  2. Inline-edit and type a long line without newlines — bg grows horizontally; no soft-wrap.
  3. Type with explicit \n line breaks — each line is independent, bg sized to the wider one.
  4. Select a text object and drag a corner of the transformer — font size scales proportionally; on release the bg matches the dragged-to box.
  5. Drag inward to a tiny size — font clamps at 8px and stops shrinking.
  6. Ctrl+Z — restores prior font size and bg dimensions.
  7. Reload the board — resized text retains its new font size.
  8. Sticky notes, shapes, and documents continue to wrap text at their bg width — visually unchanged.

Out of Scope (per the issue)

  • Tables / fenced code blocks / images / horizontal rules in plain text objects (they degrade to plain-text fallthrough — users wanting rich block layout should use Document objects).
  • Per-selection keepRatio toggle on the global transformer (the min(scaleX, scaleY) factor already enforces "text fits inside the dragged box" naturally).
## 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 an `autoWidth` opt to the `render` function. When set, plain text / heading / checkbox `Konva.Text` nodes are emitted without an explicit `width` and with `wrap: 'none'`, so each line measures its natural intrinsic width. Inline-code background rects skip the `Math.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`**: - `renderTextContent` now calls `renderMarkdown` with `autoWidth: true` (no more hardcoded `maxWidth: 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. - The `type === 'text'` branch of `applyTransform` now scales `_mdFontSize` by `min(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 global `transformstart` / `transformend` handlers in `tools.js` already wire `WhiteboardHistory.snapshotBefore` and `WhiteboardHistory.commitUpdate` + `WhiteboardSync.onUpdate`. `_mdFontSize` is already serialized as `data.fontSize` for text type, so the resized size persists across reload with no schema changes. Sticky note, shape, and document call sites of `renderMarkdown` are byte-identical (no `autoWidth` opt passed → defaults to false → existing fixed-width path is taken). ### Test Results - `cargo test --workspace --lib` — PASS - `cargo fmt --check` — PASS - `cargo clippy --workspace --all-targets -- -D warnings` — PASS ### Manual Verification Required 1. Create a new text object — bg hugs the default "Text" word, not a 408px-wide box. 2. Inline-edit and type a long line without newlines — bg grows horizontally; no soft-wrap. 3. Type with explicit `\n` line breaks — each line is independent, bg sized to the wider one. 4. Select a text object and drag a corner of the transformer — font size scales proportionally; on release the bg matches the dragged-to box. 5. Drag inward to a tiny size — font clamps at 8px and stops shrinking. 6. Ctrl+Z — restores prior font size and bg dimensions. 7. Reload the board — resized text retains its new font size. 8. Sticky notes, shapes, and documents continue to wrap text at their bg width — visually unchanged. ### Out of Scope (per the issue) - Tables / fenced code blocks / images / horizontal rules in plain text objects (they degrade to plain-text fallthrough — users wanting rich block layout should use Document objects). - Per-selection `keepRatio` toggle on the global transformer (the `min(scaleX, scaleY)` factor already enforces "text fits inside the dragged box" naturally).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_whiteboard#149
No description provided.