Shape stroke width is not persisted across reload #67

Open
opened 2026-04-23 10:23:22 +00:00 by eslamnawara · 4 comments
Member

Summary

Changing a shape's Stroke Width via the Properties panel updates the
rendering immediately, but the increased stroke width is lost after
reloading the board.

Steps to reproduce

  1. Create a shape on the whiteboard.
  2. Open the Properties panel and increase Stroke Width.
  3. Confirm the shape re-renders with the thicker stroke.
  4. Reload the page.

Expected

The shape renders after reload with the same stroke width that was set
before the reload.

Actual

After reload the stroke is rendered to maybe a default value.

## Summary Changing a shape's `Stroke Width` via the Properties panel updates the rendering immediately, but the increased stroke width is lost after reloading the board. ## Steps to reproduce 1. Create a shape on the whiteboard. 2. Open the Properties panel and increase `Stroke Width`. 3. Confirm the shape re-renders with the thicker stroke. 4. Reload the page. ## Expected The shape renders after reload with the same stroke width that was set before the reload. ## Actual After reload the stroke is rendered to maybe a default value.
Member

Implementation Spec for Issue #67

Objective

Make the shape's Stroke Width, Stroke color, and Fill color edits from the Properties panel survive a page reload. Today these edits re-render the shape immediately but are never sent to the server, so on reload the shape renders with the pre-edit style.

Requirements

  • Changing Stroke Width on a selected shape persists via the existing debounced object.update RPC; reload restores the new value.
  • Same guarantee for Stroke color and Fill color — same root cause, same panel, same pattern.
  • No regression to: immediate local re-render, the ~500ms debounce, WebSocket broadcast to peers, or undo/redo.
  • No schema or RPC changes — style.strokeWidth, style.stroke, style.fill are already in the wire format and correctly round-trip through the create/update/list/partial_update paths on the server and through applySyncUpdate / createObjectFromData on the client. The only missing link is the UI forgetting to enqueue the update.

Files to Modify

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/properties.js — add WhiteboardSync.onUpdate(currentNode) to the three shape style handlers.

No other files need changes.

Implementation Plan

Step 1 — Persist shape Stroke Width

Files: properties.js

  • Locate the prop-stroke-width input handler in attachEventListeners (around lines 783–795).
  • After bg.strokeWidth(parseInt(strokeWidthInput.value)) and batchDraw(), add WhiteboardSync.onUpdate(currentNode); — mirroring the pattern already used for prop-draw-width (freehand, lines 606–619) and the document color handlers (lines 642–667).

Dependencies: none.

Step 2 — Persist shape Stroke color

Files: properties.js

  • Locate the prop-stroke-input handler (around lines 757–767).
  • After bg.stroke(strokeInput.value) and batchDraw(), add WhiteboardSync.onUpdate(currentNode);.

Dependencies: none.

Step 3 — Persist shape Fill color

Files: properties.js

  • Locate the prop-fill-input handler (around lines 770–780).
  • After bg.fill(fillInput.value) and batchDraw(), add WhiteboardSync.onUpdate(currentNode);.

Dependencies: none.

Acceptance Criteria

  • After changing Stroke Width via the panel and reloading, the shape renders with the same stroke width.
  • After changing Stroke color via the panel and reloading, the shape renders with the same stroke color.
  • After changing Fill color via the panel and reloading, the shape renders with the same fill color.
  • Changes hit the server via the existing debounced object.update RPC (a single update ~500 ms after the last input, with the new style.* values).
  • A second browser session receiving the object.updated WebSocket broadcast continues to re-render the shape correctly (already handled by applySyncUpdate; behavior unchanged, just now actually triggered).
  • No regressions in shape type switcher, text-inside-shape editing, drag/resize, or other object-type panels.

Notes

Root cause. The three shape style inputs (prop-stroke-input, prop-fill-input, prop-stroke-width) in properties.js mutate the Konva node and redraw the layer but never enqueue a sync update. WhiteboardSync.onUpdate(node) is what adds the node to pendingUpdates, triggers the debounce timer, and ultimately calls serializeForServer(node) — which already reads bg.strokeWidth() / bg.stroke() / bg.fill(). Without the call, the server never hears the edit, so object.list on reload returns the original style.

Why shape-specific. Every other object-type handler that mutates visual state (freehand stroke/width, document bg/border, kanban/mindmap/calendar/group) already calls WhiteboardSync.onUpdate. The shape block is the one that forgot — likely because the bg mutation is an inline Konva call rather than going through a dedicated helper.

Related round-trip gaps discovered during exploration, OUT OF SCOPE for this PR (surface in a follow-up issue):

  • Frame title (prop-frame-title): rename is lost on reload.
  • Mindmap root text (prop-mm-root-text) and root color (prop-mm-root-color): edits lost on reload.
    These are the same trivial one-line fix but belong in a separately reviewable PR since the issue title is specifically about stroke width. Will mention in the PR description.

No schema migration. style is already an opaque JSON blob server-side; strokeWidth is already sent and read back by the existing code. Fix is purely client-side in one file.

## Implementation Spec for Issue #67 ### Objective Make the shape's `Stroke Width`, `Stroke` color, and `Fill` color edits from the Properties panel survive a page reload. Today these edits re-render the shape immediately but are never sent to the server, so on reload the shape renders with the pre-edit style. ### Requirements - Changing `Stroke Width` on a selected shape persists via the existing debounced `object.update` RPC; reload restores the new value. - Same guarantee for `Stroke` color and `Fill` color — same root cause, same panel, same pattern. - No regression to: immediate local re-render, the ~500ms debounce, WebSocket broadcast to peers, or undo/redo. - No schema or RPC changes — `style.strokeWidth`, `style.stroke`, `style.fill` are already in the wire format and correctly round-trip through the create/update/list/partial_update paths on the server and through `applySyncUpdate` / `createObjectFromData` on the client. The only missing link is the UI forgetting to enqueue the update. ### Files to Modify - `crates/hero_whiteboard_ui/static/web/js/whiteboard/properties.js` — add `WhiteboardSync.onUpdate(currentNode)` to the three shape style handlers. No other files need changes. ### Implementation Plan #### Step 1 — Persist shape `Stroke Width` Files: `properties.js` - Locate the `prop-stroke-width` input handler in `attachEventListeners` (around lines 783–795). - After `bg.strokeWidth(parseInt(strokeWidthInput.value))` and `batchDraw()`, add `WhiteboardSync.onUpdate(currentNode);` — mirroring the pattern already used for `prop-draw-width` (freehand, lines 606–619) and the document color handlers (lines 642–667). Dependencies: none. #### Step 2 — Persist shape `Stroke` color Files: `properties.js` - Locate the `prop-stroke-input` handler (around lines 757–767). - After `bg.stroke(strokeInput.value)` and `batchDraw()`, add `WhiteboardSync.onUpdate(currentNode);`. Dependencies: none. #### Step 3 — Persist shape `Fill` color Files: `properties.js` - Locate the `prop-fill-input` handler (around lines 770–780). - After `bg.fill(fillInput.value)` and `batchDraw()`, add `WhiteboardSync.onUpdate(currentNode);`. Dependencies: none. ### Acceptance Criteria - [ ] After changing Stroke Width via the panel and reloading, the shape renders with the same stroke width. - [ ] After changing Stroke color via the panel and reloading, the shape renders with the same stroke color. - [ ] After changing Fill color via the panel and reloading, the shape renders with the same fill color. - [ ] Changes hit the server via the existing debounced `object.update` RPC (a single update ~500 ms after the last input, with the new `style.*` values). - [ ] A second browser session receiving the `object.updated` WebSocket broadcast continues to re-render the shape correctly (already handled by `applySyncUpdate`; behavior unchanged, just now actually triggered). - [ ] No regressions in shape type switcher, text-inside-shape editing, drag/resize, or other object-type panels. ### Notes **Root cause.** The three shape style inputs (`prop-stroke-input`, `prop-fill-input`, `prop-stroke-width`) in `properties.js` mutate the Konva node and redraw the layer but never enqueue a sync update. `WhiteboardSync.onUpdate(node)` is what adds the node to `pendingUpdates`, triggers the debounce timer, and ultimately calls `serializeForServer(node)` — which already reads `bg.strokeWidth()` / `bg.stroke()` / `bg.fill()`. Without the call, the server never hears the edit, so `object.list` on reload returns the original style. **Why shape-specific.** Every other object-type handler that mutates visual state (freehand stroke/width, document bg/border, kanban/mindmap/calendar/group) already calls `WhiteboardSync.onUpdate`. The shape block is the one that forgot — likely because the bg mutation is an inline Konva call rather than going through a dedicated helper. **Related round-trip gaps discovered during exploration, OUT OF SCOPE for this PR** (surface in a follow-up issue): - Frame title (`prop-frame-title`): rename is lost on reload. - Mindmap root text (`prop-mm-root-text`) and root color (`prop-mm-root-color`): edits lost on reload. These are the same trivial one-line fix but belong in a separately reviewable PR since the issue title is specifically about stroke width. Will mention in the PR description. **No schema migration.** `style` is already an opaque JSON blob server-side; `strokeWidth` is already sent and read back by the existing code. Fix is purely client-side in one file.
Member

Test Results

JavaScript-only change in properties.js; Rust workspace validated for regressions.

Check Result
cargo check --workspace pass
cargo clippy --workspace -- -D warnings pass
cargo fmt --check pass

Manual verification is required: create a shape, change Stroke Width / Stroke / Fill via the Properties panel, reload, confirm the new values are rendered back.

## Test Results JavaScript-only change in `properties.js`; Rust workspace validated for regressions. | Check | Result | |---|---| | `cargo check --workspace` | pass | | `cargo clippy --workspace -- -D warnings` | pass | | `cargo fmt --check` | pass | Manual verification is required: create a shape, change Stroke Width / Stroke / Fill via the Properties panel, reload, confirm the new values are rendered back.
Member

Implementation Summary

Shape style edits (Stroke Width, Stroke color, Fill color) from the Properties panel now persist across reload.

Files changed

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/properties.js (+3 / -0)

Root cause

The three shape style inputs (prop-stroke-input, prop-fill-input, prop-stroke-width) mutated the Konva node and redrew the layer but never called WhiteboardSync.onUpdate(currentNode). That call is what adds the node to pendingUpdates, triggers the debounce timer, and ultimately sends the object.update RPC. Without it the server never heard the edit, so on reload object.list returned the original style.

Fix

Added WhiteboardSync.onUpdate(currentNode); inside each of the three if (bg) { ... } blocks, right after batchDraw(), mirroring the pattern already used for every other object-type style handler in the same file.

Preserved

  • Immediate local re-render and the ~500 ms debounce.
  • WebSocket broadcast to peers (already handled by applySyncUpdate; this fix merely ensures it is actually triggered).
  • Undo/redo.
  • No server schema or RPC changes — style.strokeWidth, style.stroke, style.fill were already in the wire format and already round-tripped through the reload path.

Out of scope

Two related persistence gaps were discovered during exploration and are NOT fixed here (to keep the PR narrowly reviewable against this issue):

  • Frame title edit (prop-frame-title): rename lost on reload.
  • Mindmap root text and color (prop-mm-root-text, prop-mm-root-color): edits lost on reload.
    These deserve a separate issue and PR.

Test results

  • cargo check --workspace: pass
  • cargo clippy --workspace -- -D warnings: pass
  • cargo fmt --check: pass

Manual QA required: set Stroke Width / Stroke / Fill on a shape, reload, confirm the new values are rendered back.

## Implementation Summary Shape style edits (Stroke Width, Stroke color, Fill color) from the Properties panel now persist across reload. ### Files changed - `crates/hero_whiteboard_ui/static/web/js/whiteboard/properties.js` (+3 / -0) ### Root cause The three shape style inputs (`prop-stroke-input`, `prop-fill-input`, `prop-stroke-width`) mutated the Konva node and redrew the layer but never called `WhiteboardSync.onUpdate(currentNode)`. That call is what adds the node to `pendingUpdates`, triggers the debounce timer, and ultimately sends the `object.update` RPC. Without it the server never heard the edit, so on reload `object.list` returned the original style. ### Fix Added `WhiteboardSync.onUpdate(currentNode);` inside each of the three `if (bg) { ... }` blocks, right after `batchDraw()`, mirroring the pattern already used for every other object-type style handler in the same file. ### Preserved - Immediate local re-render and the ~500 ms debounce. - WebSocket broadcast to peers (already handled by `applySyncUpdate`; this fix merely ensures it is actually triggered). - Undo/redo. - No server schema or RPC changes — `style.strokeWidth`, `style.stroke`, `style.fill` were already in the wire format and already round-tripped through the reload path. ### Out of scope Two related persistence gaps were discovered during exploration and are NOT fixed here (to keep the PR narrowly reviewable against this issue): - Frame title edit (`prop-frame-title`): rename lost on reload. - Mindmap root text and color (`prop-mm-root-text`, `prop-mm-root-color`): edits lost on reload. These deserve a separate issue and PR. ### Test results - `cargo check --workspace`: pass - `cargo clippy --workspace -- -D warnings`: pass - `cargo fmt --check`: pass Manual QA required: set Stroke Width / Stroke / Fill on a shape, reload, confirm the new values are rendered back.
Member

Pull request opened: #70

This PR implements the changes discussed in this issue.

Pull request opened: https://forge.ourworld.tf/lhumina_code/hero_whiteboard/pulls/70 This PR implements the changes discussed in this issue.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#67
No description provided.