merge development into main #124
No reviewers
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!124
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "development"
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?
- Switch self_start/self_stop to use hero_proc_factory() instead of manual lifecycle::restart_service/stop_service with raw HeroProcRPCAPIClient - Remove dirs dependency from CLI crate (replaced with std::env::var("HOME")) - Run cargo update: hero_proc_sdk v0.4.1→v0.4.4, tokio v1.50→v1.51, hyper v1.8→v1.9, and various other crate updates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>New emoji-smile toolbar button opens a picker popover with search, 9 category tabs (~300 curated emojis), and click-to-place. After picking an emoji the next canvas click drops it at that point as a first-class 'emoji' object (Konva.Text inside a Group with an invisible .bg hit rect so the generic transformer / rubber-band / eraser / applyTransform paths all work without special cases). - New module: emoji_picker.js (IIFE, no external deps). - Uniform resize: applyTransform takes min(scaleX, scaleY) to keep the glyph square. - Persistence: reuses the existing objects table — type='emoji', data={char, fontSize}. No server changes. - Keyboard: ':' opens the picker (when no input is focused); Escape closes it without falling through to deselect. - Styling: new .wb-emoji-picker CSS; theme vars re-used. #43Card text now word-wraps instead of truncating with an ellipsis, and cards auto-grow vertically to fit wrapped or explicitly multi-line content. Base cardHeight in state remains the slider/transformer- controlled minimum and is not affected by the new auto-grow; per-card actual heights are re-derived every render from the text, font size, and text width. renderKanban now does a two-pass layout: it measures every column's cards up-front, stores {heights[], offsets[]} per column in a module- level WeakMap, and computes the column-background height from the tallest stack. renderColumn and renderCard read the layout back, and the dragend slot calculation uses per-card midpoints so reordering works correctly when cards have different heights. editInline gains a multiline flag. When true it renders a <textarea> with auto-grow on input, Shift+Enter for newlines, Enter/blur to commit, and Escape to cancel. Single-line <input> path is preserved for board and column titles. Both card edit entry points (double-click and the 3-dots menu Edit item) now pass multiline: true. #52Konva's Transformer sets node.x / node.y each tick assuming the visible size equals oldSize * scale. When our applyTransform and live-redraw paths round or clamp the committed size to something other than oldSize * scale, left/top-anchored drags shift the pivot corner by the delta, producing visible drift (most obvious on calendar past its minimum). Introduce a shared correctResizeDrift helper in tools.js that shifts node.x by (expectedW - newW) for left anchors and node.y by (expectedH - newH) for top anchors. Wire it into the calendar and kanban live-redraw branches and into every applyTransform branch that mutates size: sticky, shape (Rect, Ellipse, RegularPolygon, Star, Path, Line), frame, document, image, webframe, emoji, calendar, kanban, and mindmap. Text and drawing branches are skipped because they do not resize. Mindmap is handled by reading the intrinsic bbox via getClientRect({skipTransform: true}) and comparing expectedW = intrinsic.width * scaleX against newW = intrinsic.width * finalClampedScale, since mindmap bakes a uniform scale onto the group instead of resetting scale to 1. The shape else-fallback is tightened to `else if (cls === 'Rect')` so unknown classes fall through without a bogus drift correction; all six currently used shape classes are explicitly handled. #58board.delete is a soft delete (sets deleted_at) but every other layer treated the board as live, so other windows kept editing it indefinitely after a delete. Server: - get_board now filters deleted_at IS NULL. - New is_board_live / is_comment_board_live / is_connector_board_live predicates. - object.{create,list,update,delete,batch_update}, comment.{create, list,update,resolve,delete}, connector.{create,list,update,delete} and share.create reject mutations against a soft-deleted parent board with "board has been deleted". - Added a regression test (in-memory SQLite + handler exercise) that asserts board.delete then object.create returns an error. WebSocket broadcast: - ws::broadcast_to_board helper sends a server-originated message (sender_id 0; real connection ids start at 1). - The UI's rpc_proxy sniffs successful board.delete responses and broadcasts {type:"board.deleted", board_id} to all subscribers of that board's channel. Client: - sync.js handles the broadcast: closes the WS, cancels the reconnect timer, drops pending updates, and short-circuits onCreate/onUpdate/ onDelete via a boardDeleted flag. connectWebSocket also bails when the flag is set, so the deletion is final. - board.html exposes window.showBoardDeletedNotice() and a centered themed overlay matching the rest of the page's modals, with a "Back to home" link. #83The home page had no way to delete a workspace, so the user had to go to the admin dashboard. The DB schema already declares ON DELETE CASCADE for boards.workspace_id, so the cascade hard-removes the boards — but no broadcast fired, so any open editor on one of those boards kept trying to sync until rpc.js's fallback caught the "board not found" error. Server: - queries::live_board_ids_in_workspace snapshots live board ids in a workspace. - workspace::delete now snapshots those ids before issuing the delete and returns {deleted: 1, board_ids: [...]} so the UI proxy can broadcast. - Regression test seeds two boards in a workspace, deletes the workspace, asserts both ids are returned in board_ids and that is_board_live is false for each. UI proxy: - rpc_proxy now also recognizes workspace.delete (in addition to board.delete) and broadcasts board.deleted to every channel listed in result.board_ids. The single-board path is unchanged. UI: - Trash button next to the workspace filter dropdown, visible only for real workspaces. - delete-workspace-modal matching the delete-board-modal styling: workspace name in the prompt, best-effort cascade count line, inline error region, red destructive Delete button, Cancel. - After success: filter resets to "All Workspaces", localStorage entry cleared, dropdown + board list refreshed. - loadWorkspaces falls back to "All Workspaces" if the cached currentWorkspaceId is no longer in the list. #85WhiteboardFrames was fully implemented but the Present button was hardcoded to alert("Presentation mode coming soon"). Wire it through to togglePresentation, hide the chrome via a body.wb-presenting class, and add a global keydown handler so Right/Space/PageDown advance, Left/PageUp rewind, and Escape exits. startPresentation now snapshots the current zoom + pan; stopPresentation restores it. The legacy alert(...) for the no-frames case is replaced with a themed toast that auto-dismisses. #87Falls back to document.execCommand('copy') via an off-screen textarea when navigator.clipboard is unavailable (any non-secure context, e.g. plain-HTTP IPv6) or when its writeText promise rejects. Surfaces a red error toast if both paths fail. #99connectors.from_id and to_id are FK ON DELETE CASCADE, so deleting an endpoint object removes its connectors at the SQL layer. The client kept the connector visible, and clicking delete on it called connector.delete on a row that was already gone. is_connector_board_live returned false (no row), so the handler bailed with "board has been deleted" — which the rpc client maps to the deletion overlay. The board was alive; the message was misleading. Server: new tri-state BoardLinkStatus (Missing / BoardDeleted / Live) plus connector_board_status / comment_board_status helpers. delete handlers now treat Missing as a successful no-op ({deleted: 0}) and keep the bail only for BoardDeleted. Same fix on comment.delete for cross-tab races. Client: WhiteboardObjects.deleteObject now walks the connector registry and deletes any connector whose endpoint matches the object being removed, so the orphan never appears in the first place. The existing connector.deleted broadcast still propagates to other tabs. #117The avatar count diverged between owner and shared-link tabs because existing tabs never replied to a new tab's `join` with their own identity, so the new tab only learned about other users when those users happened to broadcast a cursor or edit. Closing a tab also left stale entries because no `leave` was sent on unload. handleWsMessage's `join` arm now replies with our identity when we first see an unknown joiner, gated by an `alreadyKnown` check so the handshake stops after one round-trip. The `beforeunload` handler now emits `wsSend({ type: 'leave' })` (best-effort) so other tabs prune us via the existing `leave` receiver. Server-side disconnect leave remains a future improvement for crashes / network drops. #109