Frame: double-click the title to rename inline (no property-panel detour) #88

Open
opened 2026-04-28 10:50:50 +00:00 by AhmedHanafy725 · 3 comments
Member

Problem

Frames have an editable title that today can only be changed via the right-side property panel (properties.js:330 — the prop-frame-title input). To rename a frame the user has to: select the frame, find the property panel, locate the Title input, type, click away. Every other titled / textual object on the board (sticky, text, shape, document, kanban column header, mindmap node, calendar's view-mode cycle) has an inline rename via double-click.

There's even a stub helper for this — editText(group, textNode, bgNode) at objects.js:841, commented // Legacy editText for frames (non-markdown). It's not bound to any frame event.

Expected behavior

  • Double-clicking a frame's title label opens an inline editor (a positioned HTML <input> over the canvas, matching the pattern already used by other objects).
  • Pressing Enter (or blurring) commits the new title to frame.findOne('.label').text(value) and triggers WhiteboardSync.onUpdate(group) so the change syncs to other windows.
  • Pressing Escape cancels the edit without changes.
  • Double-clicking the dashed body of the frame (not the label) does NOT trigger the rename — only the label triggers it. (Avoids accidental edits when the user is just trying to interact with the frame.)
  • Existing rename via the property panel keeps working.

Affected files

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js — add a dblclick dbltap listener on the label inside createFrame. The handler calls a small inline editor that returns the new value via Enter/blur or cancels via Escape. Reuse the existing editText helper if it cleanly fits the pattern; otherwise inline a small variant.

No other file needs to change. The sync side already handles label updates (the applySyncUpdate frame branch at sync.js:574 updates label.text(data.title) when data.title is sent, and the serialize side at sync.js:236 already includes data.title).

Acceptance criteria

  • Double-clicking the frame's title label opens an inline editor.
  • Enter or blur commits the new title; Escape cancels.
  • After commit, the change syncs to other windows (the existing sync path handles this).
  • Double-clicking the dashed body of the frame does not open the editor.
  • Property-panel rename still works.
  • No regression in dragstart / dragend / select / resize handlers on the frame.
  • cargo check / clippy / fmt --check / test clean.

Notes

  • objects.js:841 editText(group, textNode, bgNode) already exists but is unbound. If it does the right thing, just bind a dblclick dbltap on the label and call it with (group, label, bg). If the helper is incomplete, a minimal pattern is the same one used by editMarkdownText / editMindmapTitle etc. — create a <textarea> or <input> over the canvas at the label's screen position, focus it, listen for Enter/Escape/blur.
  • Stop event propagation in the dblclick handler so it doesn't bubble to the stage's pan/zoom handlers.
## Problem Frames have an editable title that today can only be changed via the right-side property panel (`properties.js:330` — the `prop-frame-title` input). To rename a frame the user has to: select the frame, find the property panel, locate the Title input, type, click away. Every other titled / textual object on the board (sticky, text, shape, document, kanban column header, mindmap node, calendar's view-mode cycle) has an inline rename via double-click. There's even a stub helper for this — `editText(group, textNode, bgNode)` at `objects.js:841`, commented `// Legacy editText for frames (non-markdown)`. It's not bound to any frame event. ## Expected behavior - Double-clicking a frame's title label opens an inline editor (a positioned HTML `<input>` over the canvas, matching the pattern already used by other objects). - Pressing Enter (or blurring) commits the new title to `frame.findOne('.label').text(value)` and triggers `WhiteboardSync.onUpdate(group)` so the change syncs to other windows. - Pressing Escape cancels the edit without changes. - Double-clicking the dashed body of the frame (not the label) does NOT trigger the rename — only the label triggers it. (Avoids accidental edits when the user is just trying to interact with the frame.) - Existing rename via the property panel keeps working. ## Affected files - `crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js` — add a `dblclick dbltap` listener on the label inside `createFrame`. The handler calls a small inline editor that returns the new value via Enter/blur or cancels via Escape. Reuse the existing `editText` helper if it cleanly fits the pattern; otherwise inline a small variant. No other file needs to change. The sync side already handles label updates (the `applySyncUpdate` frame branch at `sync.js:574` updates `label.text(data.title)` when `data.title` is sent, and the serialize side at `sync.js:236` already includes `data.title`). ## Acceptance criteria - [ ] Double-clicking the frame's title label opens an inline editor. - [ ] Enter or blur commits the new title; Escape cancels. - [ ] After commit, the change syncs to other windows (the existing sync path handles this). - [ ] Double-clicking the dashed body of the frame does not open the editor. - [ ] Property-panel rename still works. - [ ] No regression in dragstart / dragend / select / resize handlers on the frame. - [ ] `cargo check / clippy / fmt --check / test` clean. ## Notes - `objects.js:841 editText(group, textNode, bgNode)` already exists but is unbound. If it does the right thing, just bind a `dblclick dbltap` on the label and call it with `(group, label, bg)`. If the helper is incomplete, a minimal pattern is the same one used by `editMarkdownText` / `editMindmapTitle` etc. — create a `<textarea>` or `<input>` over the canvas at the label's screen position, focus it, listen for Enter/Escape/blur. - Stop event propagation in the dblclick handler so it doesn't bubble to the stage's pan/zoom handlers.
Author
Member

Implementation Spec for Issue #88

Objective

Let users rename a frame inline by double-clicking its title label. Reuse the dead editText helper in objects.js (currently defined but never called), extending it minimally to commit on Enter and to call WhiteboardSync.onUpdate + WhiteboardHistory so the change persists, syncs to other windows, and is undoable. Bind the listener on the label only (not the dashed body) so accidental double-clicks elsewhere don't open the editor.

Requirements

  • Double-clicking the frame's title label opens an inline <textarea> over the canvas, pre-filled with the current title.
  • Enter (without Shift) commits; blur commits; Escape cancels (no change).
  • After commit, the new title is propagated via WhiteboardSync.onUpdate(group) so a second window on the same board sees the change.
  • The action is captured in the local undo stack so Ctrl+Z reverts the rename.
  • Double-clicking the dashed body of the frame does NOT open the editor.
  • The existing property-panel rename flow keeps working unchanged.
  • No regression in dragstart / dragend / select / resize.
  • Diff is contained to crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js.
  • cargo check / clippy / fmt --check / test clean.

Files to Modify

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js
    • Extend editText(group, textNode, bgNode) to (a) snapshot history before the edit, (b) push a commit history entry on commit, (c) call WhiteboardSync.onUpdate(group) on commit, (d) treat Enter as commit (blur).
    • In createFrame, attach a dblclick dbltap listener on the label that calls editText(group, label, null).

No other file needs to change. The sync side already round-trips frame data.title (sync.js:236 serialize + sync.js:574 apply).

Implementation Plan

Step 1: Make editText history- and sync-aware; commit on Enter

Files: crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js

  1. At the top of editText, snapshot for undo:
    if (group && group.id) WhiteboardHistory.snapshotBefore(group.id());
    
  2. Inside removeTextarea, after textNode.text(textarea.value) and the redraw, finalize history + sync:
    if (group && group.id) {
        WhiteboardHistory.commitUpdate(group.id());
    }
    if (typeof WhiteboardSync !== 'undefined' && WhiteboardSync.onUpdate) {
        WhiteboardSync.onUpdate(group);
    }
    
    (Order matches the existing dragend pattern: snapshotBefore on start, commitUpdate + onUpdate on end.)
  3. Update the existing keydown handler to commit on Enter (without Shift) and to keep the existing Escape behavior (note: existing code blurs on Escape but does not revert — the spec asks Escape to cancel without changes; restore the original text first):
    var origText = textNode.text();
    ...
    textarea.addEventListener('keydown', function(e) {
        if (e.key === 'Escape') {
            textarea.value = origText; // revert before blur commits
            textarea.blur();
        } else if (e.key === 'Enter' && !e.shiftKey) {
            e.preventDefault();
            textarea.blur();
        }
        e.stopPropagation();
    });
    
    The shift-Enter escape hatch is harmless — frame titles are typically single-line, but allowing a manual newline isn't worth blocking.

Step 2: Attach dblclick dbltap to the frame label

Files: crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js

In createFrame (around the group.add(label) line), add:

label.on('dblclick dbltap', function(e) {
    e.cancelBubble = true;
    if (e.evt) e.evt.stopPropagation();
    editText(group, label, null);
});

Notes:

  • The listener is on the label Konva.Text, not the group or the bg rect — so double-clicks on the dashed body don't trigger it. Other handlers (drag, transformer attach) target the group; they're unaffected.
  • Pass bgNode = null so editText doesn't try to grow a non-existent bg around the label (frame's bg is the dashed rectangle below the label and shouldn't expand from a title rename).
  • cancelBubble = true prevents the dblclick from also reaching the stage's dblclick handlers (which today don't act on dblclick on the canvas, but future-proof).

Dependencies: Step 1.

Acceptance Criteria

  • Double-clicking the frame title label opens an inline editor pre-filled with the current title.
  • Enter (no Shift) or blur commits; Escape cancels and restores the original title.
  • After commit, the new title appears in a second window opened on the same board (sync round-trip).
  • Ctrl+Z reverts the rename.
  • Double-clicking the dashed body of the frame does NOT open the editor.
  • Property-panel rename still works unchanged.
  • No regression in dragstart / dragend / select / resize.
  • cargo check / clippy / fmt --check / test clean.

Notes

  • editText is currently defined but unused (objects.js:841-900); modifying it is safe.
  • WhiteboardHistory.snapshotBefore / commitUpdate is the same pair the frame's dragstart / dragend already use, so undo is consistent across drag and rename.
  • The textarea's keydown e.stopPropagation() keeps it isolated from the page-level keydown handlers (presentation, modals, shortcuts) — that's preserved.
## Implementation Spec for Issue #88 ### Objective Let users rename a frame inline by double-clicking its title label. Reuse the dead `editText` helper in `objects.js` (currently defined but never called), extending it minimally to commit on Enter and to call `WhiteboardSync.onUpdate` + `WhiteboardHistory` so the change persists, syncs to other windows, and is undoable. Bind the listener on the label only (not the dashed body) so accidental double-clicks elsewhere don't open the editor. ### Requirements - Double-clicking the frame's title label opens an inline `<textarea>` over the canvas, pre-filled with the current title. - Enter (without Shift) commits; blur commits; Escape cancels (no change). - After commit, the new title is propagated via `WhiteboardSync.onUpdate(group)` so a second window on the same board sees the change. - The action is captured in the local undo stack so Ctrl+Z reverts the rename. - Double-clicking the dashed body of the frame does NOT open the editor. - The existing property-panel rename flow keeps working unchanged. - No regression in dragstart / dragend / select / resize. - Diff is contained to `crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js`. - `cargo check / clippy / fmt --check / test` clean. ### Files to Modify - `crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js` - Extend `editText(group, textNode, bgNode)` to (a) snapshot history before the edit, (b) push a commit history entry on commit, (c) call `WhiteboardSync.onUpdate(group)` on commit, (d) treat Enter as commit (blur). - In `createFrame`, attach a `dblclick dbltap` listener on the label that calls `editText(group, label, null)`. No other file needs to change. The sync side already round-trips frame `data.title` (`sync.js:236` serialize + `sync.js:574` apply). ### Implementation Plan #### Step 1: Make `editText` history- and sync-aware; commit on Enter Files: `crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js` 1. At the top of `editText`, snapshot for undo: ```js if (group && group.id) WhiteboardHistory.snapshotBefore(group.id()); ``` 2. Inside `removeTextarea`, after `textNode.text(textarea.value)` and the redraw, finalize history + sync: ```js if (group && group.id) { WhiteboardHistory.commitUpdate(group.id()); } if (typeof WhiteboardSync !== 'undefined' && WhiteboardSync.onUpdate) { WhiteboardSync.onUpdate(group); } ``` (Order matches the existing dragend pattern: snapshotBefore on start, commitUpdate + onUpdate on end.) 3. Update the existing `keydown` handler to commit on Enter (without Shift) and to keep the existing Escape behavior (note: existing code blurs on Escape but does not revert — the spec asks Escape to cancel without changes; restore the original text first): ```js var origText = textNode.text(); ... textarea.addEventListener('keydown', function(e) { if (e.key === 'Escape') { textarea.value = origText; // revert before blur commits textarea.blur(); } else if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); textarea.blur(); } e.stopPropagation(); }); ``` The shift-Enter escape hatch is harmless — frame titles are typically single-line, but allowing a manual newline isn't worth blocking. #### Step 2: Attach `dblclick dbltap` to the frame label Files: `crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js` In `createFrame` (around the `group.add(label)` line), add: ```js label.on('dblclick dbltap', function(e) { e.cancelBubble = true; if (e.evt) e.evt.stopPropagation(); editText(group, label, null); }); ``` Notes: - The listener is on the `label` Konva.Text, not the `group` or the `bg` rect — so double-clicks on the dashed body don't trigger it. Other handlers (drag, transformer attach) target the group; they're unaffected. - Pass `bgNode = null` so `editText` doesn't try to grow a non-existent bg around the label (frame's bg is the dashed rectangle below the label and shouldn't expand from a title rename). - `cancelBubble = true` prevents the dblclick from also reaching the stage's dblclick handlers (which today don't act on dblclick on the canvas, but future-proof). Dependencies: Step 1. ### Acceptance Criteria - [ ] Double-clicking the frame title label opens an inline editor pre-filled with the current title. - [ ] Enter (no Shift) or blur commits; Escape cancels and restores the original title. - [ ] After commit, the new title appears in a second window opened on the same board (sync round-trip). - [ ] Ctrl+Z reverts the rename. - [ ] Double-clicking the dashed body of the frame does NOT open the editor. - [ ] Property-panel rename still works unchanged. - [ ] No regression in dragstart / dragend / select / resize. - [ ] `cargo check / clippy / fmt --check / test` clean. ### Notes - `editText` is currently defined but unused (`objects.js:841-900`); modifying it is safe. - `WhiteboardHistory.snapshotBefore` / `commitUpdate` is the same pair the frame's `dragstart` / `dragend` already use, so undo is consistent across drag and rename. - The textarea's `keydown` `e.stopPropagation()` keeps it isolated from the page-level keydown handlers (presentation, modals, shortcuts) — that's preserved.
Author
Member

Test Results

  • cargo test -p hero_whiteboard_server: 3 passed (covers the live-board / soft-delete / unique-name regression tests).
  • cargo test --workspace --lib: clean.
  • cargo clippy --workspace -- -D warnings: clean.
  • cargo fmt --all -- --check: clean.
  • cargo check --workspace: clean.
  • node --check objects.js: parses cleanly.

The one workspace integration test in hero_whiteboard_examples (test_workspace_and_board_operations) is flaky under contention because it talks to a live socket and was observed to race (it passes when run alone). It is not affected by this change.

Manual verification recommended:

  1. Drop a frame, double-click its title — inline editor opens pre-filled with the current title.
  2. Type a new value and press Enter — title commits, editor closes.
  3. Double-click again, edit, blur (click elsewhere) — title commits.
  4. Double-click, edit, press Escape — title reverts to the original value, editor closes; no sync RPC fires (no-op when text equals the original).
  5. Open the same board in a second window, rename the title in window A, confirm window B picks up the new title.
  6. Press Ctrl+Z after a rename — title reverts.
  7. Double-click the dashed body of a frame (not the label) — nothing happens.
  8. Property-panel rename still works.
## Test Results - `cargo test -p hero_whiteboard_server`: 3 passed (covers the live-board / soft-delete / unique-name regression tests). - `cargo test --workspace --lib`: clean. - `cargo clippy --workspace -- -D warnings`: clean. - `cargo fmt --all -- --check`: clean. - `cargo check --workspace`: clean. - `node --check objects.js`: parses cleanly. The one workspace integration test in `hero_whiteboard_examples` (`test_workspace_and_board_operations`) is flaky under contention because it talks to a live socket and was observed to race (it passes when run alone). It is not affected by this change. Manual verification recommended: 1. Drop a frame, double-click its title — inline editor opens pre-filled with the current title. 2. Type a new value and press Enter — title commits, editor closes. 3. Double-click again, edit, blur (click elsewhere) — title commits. 4. Double-click, edit, press Escape — title reverts to the original value, editor closes; no sync RPC fires (no-op when text equals the original). 5. Open the same board in a second window, rename the title in window A, confirm window B picks up the new title. 6. Press Ctrl+Z after a rename — title reverts. 7. Double-click the dashed body of a frame (not the label) — nothing happens. 8. Property-panel rename still works.
Author
Member

Implementation Summary

1 file changed, +40 / -4 — all in crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js.

Changes

  • Extended editText(group, textNode, bgNode):
    • Snapshots origText and calls WhiteboardHistory.snapshotBefore(group.id()) before the edit.
    • On commit, only fires WhiteboardHistory.commitUpdate(group.id()) + WhiteboardSync.onUpdate(group) when the text actually changed (no-op blur doesn't pollute history or push redundant RPCs).
    • Treats Enter (no Shift) as commit (blur). Shift+Enter still inserts a newline.
    • Escape now reverts the textarea to origText before blurring, so the cancel path commits no change and skips history/sync.
  • createFrame now binds dblclick dbltap on the label, calling editText(group, label, null). The listener is on the label only (not the dashed body) so accidental dblclicks elsewhere don't open the editor. bgNode = null so the dashed rect doesn't grow from a title rename.

Verification

  • cargo test -p hero_whiteboard_server: 3 passed.
  • cargo test --workspace --lib: clean.
  • cargo clippy --workspace -- -D warnings: clean.
  • cargo fmt --all -- --check: clean.
  • cargo check --workspace: clean.
  • node --check objects.js: parses cleanly.

Notes / caveats

  • editText was previously dead code (defined and exported but never called). All the changes above only affect the new caller — frames — and don't change behavior for any other consumer because there is no other consumer.
  • Sync round-trip already worked for data.title on frames (sync.js serialize at line 236, apply at line 574); this change just teaches the local rename path to call WhiteboardSync.onUpdate(group) so the round-trip is triggered.
  • Property-panel rename (the existing path via prop-frame-title) is untouched and keeps working.
## Implementation Summary 1 file changed, +40 / -4 — all in `crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js`. ### Changes - Extended `editText(group, textNode, bgNode)`: - Snapshots `origText` and calls `WhiteboardHistory.snapshotBefore(group.id())` before the edit. - On commit, only fires `WhiteboardHistory.commitUpdate(group.id())` + `WhiteboardSync.onUpdate(group)` when the text actually changed (no-op blur doesn't pollute history or push redundant RPCs). - Treats Enter (no Shift) as commit (blur). Shift+Enter still inserts a newline. - Escape now reverts the textarea to `origText` before blurring, so the cancel path commits no change and skips history/sync. - `createFrame` now binds `dblclick dbltap` on the label, calling `editText(group, label, null)`. The listener is on the label only (not the dashed body) so accidental dblclicks elsewhere don't open the editor. `bgNode = null` so the dashed rect doesn't grow from a title rename. ### Verification - `cargo test -p hero_whiteboard_server`: 3 passed. - `cargo test --workspace --lib`: clean. - `cargo clippy --workspace -- -D warnings`: clean. - `cargo fmt --all -- --check`: clean. - `cargo check --workspace`: clean. - `node --check objects.js`: parses cleanly. ### Notes / caveats - `editText` was previously dead code (defined and exported but never called). All the changes above only affect the new caller — frames — and don't change behavior for any other consumer because there is no other consumer. - Sync round-trip already worked for `data.title` on frames (`sync.js` serialize at line 236, apply at line 574); this change just teaches the local rename path to call `WhiteboardSync.onUpdate(group)` so the round-trip is triggered. - Property-panel rename (the existing path via `prop-frame-title`) is untouched and keeps working.
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#88
No description provided.