Skip to content

feat: enhance whiteboard and dock - #25

Merged
daiv05 merged 14 commits into
mainfrom
feat-improvements-april
Jul 29, 2026
Merged

feat: enhance whiteboard and dock#25
daiv05 merged 14 commits into
mainfrom
feat-improvements-april

Conversation

@daiv05

@daiv05 daiv05 commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added dash (line) style presets, rounded corners, arrow styles, and richer text formatting (weight/style/underline/alignment), synced across modes.
    • Enhanced Select tool with multi-selection, copy/paste/duplicate, selection locking, grouping/ungrouping, plus new selection hotkeys.
    • Floating docks gained a “more options” panel with context-aware controls and improved positioning/tooltip behavior.
  • Bug Fixes

    • Improved visibility state syncing for overlay, spotlight, and cursor highlighting.
  • Documentation

    • Updated Live Draw, Whiteboard, settings, and shortcut guides with the new controls and workflows.

daiv05 added 12 commits April 8, 2026 09:36
…rovements

- Added text styling options including weight, style, decoration, and alignment to the whiteboard.
- Implemented a new composable `useFloatingDock` for managing dock position and drag functionality.
- Refactored dock position handling in `WhiteboardShell.vue` to utilize the new composable.
- Introduced `useDockModeConfig` to manage tool availability and controls based on dock mode.
- Updated canvas drawing utilities to support new text properties and arrow styles.
- Normalized drawing actions to include new properties for text and shapes.
- Enhanced renderer to handle rounded rectangles and styled arrows.
- Added local shortcuts for new tools and updated shortcut definitions.
- Updated localization files to include new text styling options and shapes.
- Refactored settings store to manage new text and shape properties.
@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vynta Ready Ready Preview, Comment Jul 29, 2026 3:15pm

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d9a71c29-123f-4850-9434-4767d92eebd1

📥 Commits

Reviewing files that changed from the base of the PR and between 44c6cf4 and 1b911d7.

📒 Files selected for processing (12)
  • .oxlintrc.json
  • src/App.vue
  • src/components/app/AppShell.vue
  • src/components/overlay/OverlayShell.vue
  • src/components/shared/CanvasStage.vue
  • src/components/shared/FloatingDock.vue
  • src/components/whiteboard/WhiteboardShell.vue
  • src/composables/useCanvasDrawing.ts
  • src/composables/useLineStyleControls.ts
  • src/composables/useShapeStyleControls.ts
  • src/locales/es.json
  • src/utils/canvas/renderer.ts
💤 Files with no reviewable changes (1)
  • .oxlintrc.json
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/components/app/AppShell.vue
  • src/components/whiteboard/WhiteboardShell.vue
  • src/locales/es.json
  • src/App.vue
  • src/components/shared/CanvasStage.vue
  • src/components/overlay/OverlayShell.vue
  • src/composables/useCanvasDrawing.ts
  • src/utils/canvas/renderer.ts
  • src/components/shared/FloatingDock.vue

📝 Walkthrough

Walkthrough

Changes

Canvas editing and dock expansion

Layer / File(s) Summary
Drawing contracts and normalization
src/types/*, src/stores/settings.ts, src/utils/canvas/normalizer.ts
Drawing actions, payloads, and persisted settings now support line, text, shape, locking, and grouping properties with validation and normalization.
Canvas rendering and selection engine
src/composables/useCanvasDrawing.ts, src/utils/canvas/renderer.ts
Canvas rendering adds dash patterns, rounded rectangles, arrow variants, text formatting, and multi-selection with grouping and locking operations.
Floating dock controls and positioning
src/components/shared/FloatingDock.vue, src/composables/useDockModeConfig.ts, src/composables/useFloatingDock.ts
The dock adds mode-specific editing controls, selection actions, formatting controls, tooltip placement, and reusable drag behavior.
Canvas and dock wiring
src/components/shared/CanvasStage.vue, src/components/overlay/OverlayShell.vue, src/components/whiteboard/WhiteboardShell.vue
Overlay and whiteboard shells pass expanded styling settings and selection events through CanvasStage and FloatingDock.
Shortcuts, synchronization, and documentation
src/App.vue, src/components/app/AppShell.vue, src/composables/useLocalShortcuts.ts, src/locales/*, docs/pages/*
Startup hydration, expanded overlay synchronization, line-tool shortcuts, translations, and feature documentation are updated.

Project metadata and build configuration

Layer / File(s) Summary
Project configuration updates
LICENSE.md, .oxlintrc.json, package.json, tsconfig.json
The MIT license, lint configuration, runtime package versions, and TypeScript target are updated.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant FloatingDock
  participant CanvasStage
  participant useCanvasDrawing
  participant CanvasRenderer
  User->>FloatingDock: choose style or selection action
  FloatingDock->>CanvasStage: emit control event
  CanvasStage->>useCanvasDrawing: update selection or drawing action
  useCanvasDrawing->>CanvasRenderer: redraw normalized action
Loading

Poem

A rabbit taps the dock with glee,
Dashed lines hop across the tree.
Groups lock tight, then spring apart,
Bold little words gain shape and heart.
“Merge and draw!” the bunny sings—
New canvas magic grows bright wings.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main changes to the whiteboard and dock features.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-improvements-april

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/utils/canvas/renderer.ts (1)

533-553: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Text bounds ignore wrapping, so selection/hit-testing drifts from what is rendered.

drawAction lays text out with wrapText(ctx, text, maxWidth), but these bounds measure raw \n-split lines. Any text that wraps renders taller (and narrower) than the box returned here, so hitTestText, hitTestAction, eraseAtPoint, and the selection rectangle all miss the wrapped continuation lines. Reuse the same wrap computation for both paths — extracting a shared layoutText(ctx, action) helper would keep the two in sync.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/canvas/renderer.ts` around lines 533 - 553, The text-bounds
calculation near drawAction must use the same wrapping layout as rendering
instead of measuring raw newline-separated lines. Extract or reuse a shared
layoutText helper that applies wrapText with the action’s max width, then use
its wrapped lines for width and height while preserving text alignment; update
drawAction and the bounds path to consume this shared result.
🧹 Nitpick comments (11)
src/composables/useCanvasDrawing.ts (2)

906-933: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Redundant group expansion, and lastPoint goes stale when everything is locked.

getSelectedActions() already runs expandSelectionWithGroups, so the moveIds loop on Lines 911-920 re-expands groups a second time — it can be dropped. Separately, the early return on Line 925 (all selected items locked) skips the dragState.lastPoint = point update, so the accumulated delta is applied in one jump if the pointer keeps moving and an item later becomes movable.

♻️ Proposed fix
-    const moveIds = new Set<string>();
-    selected.forEach((entry) => {
-      moveIds.add(entry.id);
-      if (!entry.groupId) return;
-      actions.value.forEach((candidate) => {
-        if (candidate.groupId === entry.groupId) {
-          moveIds.add(candidate.id);
-        }
-      });
-    });
-
-    const movable = actions.value.filter(
-      (entry) => moveIds.has(entry.id) && !entry.locked,
-    );
-    if (movable.length === 0) return;
-
     const lastPoint = dragState.value.lastPoint;
     if (!lastPoint) return;
+    const movable = selected.filter((entry) => !entry.locked);
     const deltaX = point.x - lastPoint.x;
     const deltaY = point.y - lastPoint.y;
     movable.forEach((entry) => {
       entry.points = translatePoints(entry.points, deltaX, deltaY);
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/composables/useCanvasDrawing.ts` around lines 906 - 933, Update
updateSelection to use the IDs returned by getSelectedActions directly, removing
the redundant group expansion loop. Ensure dragState.value.lastPoint is updated
to the current point before returning when movable is empty, so pointer movement
over fully locked selections does not accumulate stale deltas.

625-667: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

Lock/group/ungroup mutate actions without a history entry.

toggleSelectedLock, groupSelectedActions, and ungroupSelectedActions mutate locked/groupId in place and never push to historyStack, so Ctrl+Z cannot revert them. Worse, undoing an earlier add after a group operation restores stale membership, since group ids were rewritten outside the history model. Consider a third history entry type (e.g. { type: "mutate"; before: Array<{id, locked, groupId}> }).

Also, groupSelectedActions reuses the first pre-existing group id found (Line 646), which silently merges two distinct existing groups into one; a fresh id would be less surprising.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/composables/useCanvasDrawing.ts` around lines 625 - 667, Update
toggleSelectedLock, groupSelectedActions, and ungroupSelectedActions to record
their in-place locked/groupId changes as a dedicated mutate history entry
containing the affected actions’ prior state, so Ctrl+Z restores those values
and earlier add undo cannot resurrect stale group membership. Only append
history when a mutation actually occurs, preserving existing no-op returns. In
groupSelectedActions, always assign a fresh group id instead of reusing any
pre-existing selected group id.
src/components/shared/CanvasStage.vue (1)

318-321: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Font string duplicates buildFont in src/utils/canvas/renderer.ts.

The mirror/measurement font here must match the canvas font used for rendering; keeping two literal templates invites drift. Export buildFont (or a small formatCanvasFont(style, weight, size, family) helper in src/utils/canvas/) and call it from both.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/shared/CanvasStage.vue` around lines 318 - 321, Update the
font construction in CanvasStage’s mirror/measurement logic to reuse the shared
buildFont or formatting helper from the canvas renderer utilities instead of
maintaining a duplicate template. Export the shared formatter as needed, then
call it from both CanvasStage and the renderer so measurement and rendering
always use identical font strings.
src/types/drawing.ts (1)

46-51: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider exporting alias types for the repeated unions.

"normal" | "bold", "none" | "underline", "left" | "center" | "right", and the arrow-style union are re-declared verbatim in src/utils/canvas/normalizer.ts, src/composables/useCanvasDrawing.ts, src/components/shared/CanvasStage.vue, and both shells. Exporting TextWeight, TextStyle, TextDecoration, TextAlign, and ArrowStyle from here keeps them in sync.

♻️ Proposed alias types
+export type TextWeight = "normal" | "bold";
+export type TextStyle = "normal" | "italic";
+export type TextDecoration = "none" | "underline";
+export type TextAlign = "left" | "center" | "right";
+export type ArrowStyle = "simple" | "filled" | "double" | "thick" | "stealth";
+
 export type DrawAction = {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/drawing.ts` around lines 46 - 51, Define and export shared alias
types named TextWeight, TextStyle, TextDecoration, TextAlign, and ArrowStyle in
the drawing types module, then update the repeated union declarations in the
normalizer, useCanvasDrawing, CanvasStage, and both shells to import and reuse
these aliases.
src/utils/canvas/normalizer.ts (1)

55-86: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Normalization skips the fields most likely to be malformed.

The helpers defensively accept unknown, which implies this runs on untrusted/persisted data (replaceActions feeds it straight into the render loop), yet id, tool, color, and composite are spread through unvalidated. An unknown tool silently falls through drawAction to getPointsBounds, and a bogus composite string assigned to ctx.globalCompositeOperation is ignored by the browser but leaves the action permanently mis-typed. Also consider typing the parameter as unknown (or Partial<DrawAction>) so callers with raw JSON don't have to cast.

Minor side note: text-only fields (fontSize, fontWeight, textAlign, …) and arrowStyle are now stamped onto every pen/rect/ellipse action, which inflates persisted payloads. Gating them on action.tool would keep the model lean.

♻️ Sketch
-export function normalizeDrawAction(action: DrawAction): DrawAction {
+const TOOL_IDS = new Set<ToolId>([...]);
+
+export function normalizeDrawAction(input: unknown): DrawAction {
+  const action = (input ?? {}) as DrawAction;
+  const tool = TOOL_IDS.has(action.tool) ? action.tool : "pen";
   const points = normalizePoints(action.points);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/canvas/normalizer.ts` around lines 55 - 86, Update
normalizeDrawAction to accept raw or partial input and validate/default id,
tool, color, and composite alongside the existing normalized fields, ensuring
unknown tool and composite values cannot reach rendering. Preserve a valid
DrawAction shape for untrusted persisted data, reusing existing normalization
conventions where applicable. Only include text-specific fields and arrowStyle
when action.tool supports them so unrelated actions are not inflated.
src/components/overlay/OverlayShell.vue (1)

96-106: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Position ownership is now split between the composable and this component.

useFloatingDock owns dockPosition and watches sourcePosition (overlayDockPosition), yet calculateInitialPosition (Line 256-264) writes dockPosition.value without persisting it — the next store write to overlayDockPosition reverts that value through the composable's watcher. recalculateDockPosition/resetAndRecalculate also re-implement clamping that already exists inside handleDockDrag. Moving the viewport-resize recalibration into useFloatingDock would give both shells the same behaviour and remove the dual ownership.

As per coding guidelines: "Prefer composables for reusable logic in the frontend".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/overlay/OverlayShell.vue` around lines 96 - 106, Move
viewport-resize recalibration from OverlayShell’s calculateInitialPosition,
recalculateDockPosition, and resetAndRecalculate into useFloatingDock, making
the composable the sole owner of dockPosition and its persistence. Reuse
handleDockDrag’s existing clamping and ensure recalculated positions are
persisted so sourcePosition updates cannot revert them. Update both dock
consumers to use the shared composable behavior and remove the component-level
duplicate logic.

Source: Coding guidelines

src/components/shared/FloatingDock.vue (1)

44-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Import DockMode instead of re-declaring the union.

mode?: "overlay" | "whiteboard" duplicates useDockModeConfig.ts's exported DockMode type. If a mode is ever added there, this prop type won't be updated automatically and the mismatch would only surface indirectly.

♻️ Reuse the shared type
-import { useDockModeConfig } from "../../composables/useDockModeConfig";
+import { useDockModeConfig } from "../../composables/useDockModeConfig";
+import type { DockMode } from "../../composables/useDockModeConfig";
...
 const props = defineProps<{
-  mode?: "overlay" | "whiteboard";
+  mode?: DockMode;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/shared/FloatingDock.vue` around lines 44 - 46, Update
FloatingDock’s props definition to import and use the exported DockMode type
from useDockModeConfig.ts instead of re-declaring the "overlay" | "whiteboard"
union for mode, so it stays synchronized with the shared mode definition.
src/composables/useFloatingDock.ts (1)

25-45: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No resize listener — dock position/tooltip placement can go stale after a window resize.

dockTooltipPlacement and applyPositionForOrientation both read globalThis.innerWidth/innerHeight, but nothing recomputes them when the window resizes (only drag/orientation changes trigger updates). After a resize, the dock can remain positioned for the old viewport size, and the tooltip placement heuristic can be wrong until the next drag.

♻️ Add a resize listener
   onBeforeUnmount(() => {
     globalThis.removeEventListener("pointermove", handleDockDrag);
     globalThis.removeEventListener("pointerup", stopDockDrag);
+    globalThis.removeEventListener("resize", applyPositionForOrientation);
   });
+
+  globalThis.addEventListener("resize", applyPositionForOrientation);

Also applies to: 47-71, 121-124

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/composables/useFloatingDock.ts` around lines 25 - 45, Add a window resize
listener in the useFloatingDock lifecycle so it invokes
applyPositionForOrientation and refreshes dockTooltipPlacement after viewport
dimensions change. Register the listener on mount and remove the same handler on
unmount, preserving existing drag and orientation behavior.
src/stores/settings.ts (2)

277-281: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Dash-pattern normalization (filter finite/non-negative values, then round) is implemented independently in three places. Extract one shared helper so the algorithm can't drift between the store and the dock component.

  • src/stores/settings.ts#L277-L281: move the filter/round logic in setDashPattern into a shared normalizeDashPattern(pattern: number[]): number[] helper (exported from this file or a shared utils module).
  • src/stores/settings.ts#L424-L428: call the same shared helper instead of re-implementing the filter/round inline in applySettings.
  • src/components/shared/FloatingDock.vue#L175-L179: drop the local normalizeDashPattern and import the shared helper (e.g. from the settings store or a canvas normalizer utility) instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/stores/settings.ts` around lines 277 - 281, Extract the shared
normalizeDashPattern(pattern: number[]): number[] helper from the inline logic
in src/stores/settings.ts lines 277-281, preserving finite/non-negative
filtering and rounding. Update src/stores/settings.ts lines 424-428 to call it,
and remove the local helper in src/components/shared/FloatingDock.vue lines
175-179 so that component imports and uses the shared implementation.

18-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider centralizing the text/arrow-style literal unions.

"normal" | "bold", "left" | "center" | "right", "simple" | "filled" | "double" | "thick" | "stealth", etc. are declared repeatedly (this file's Settings type, refs, setter params, guards) and again in src/types/overlay.ts's OverlayPayload and FloatingDock.vue. A shared exported type (e.g. in src/types/drawing.ts) would avoid drift if a new style value is ever added.

src/types/drawing.ts isn't included in this review batch — please confirm whether it already exports these unions (it's listed as part of the "Drawing contracts and normalization" layer) before extracting a new one.

Also applies to: 74-84

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/stores/settings.ts` around lines 18 - 23, Check src/types/drawing.ts
first and reuse any existing exported text and arrow-style unions; only define
shared exports there if they are absent. Update Settings, its refs, setter
parameters, and guards to consume those types, then replace duplicate literals
in OverlayPayload and FloatingDock.vue with the same shared definitions so all
style values remain synchronized.
src/composables/useDockModeConfig.ts (1)

8-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

overlay and whiteboard configs are currently identical.

Both branches of toolsByMode and controlsByMode list the exact same tools/flags. If this is intentional scaffolding for future divergence, fine — but as written the two branches must be manually kept in sync, and nothing currently enforces that. Consider a single shared config object (with per-mode overrides only where they actually differ) until real per-mode differences exist.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/composables/useDockModeConfig.ts` around lines 8 - 52, Consolidate the
identical overlay and whiteboard entries in toolsByMode and controlsByMode into
shared configuration data, avoiding duplicate tool lists and control flags that
can drift out of sync. Preserve both DockMode keys and the current behavior,
while leaving a clear path for future per-mode overrides.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.oxlintrc.json:
- Line 16: Remove the trailing // comment and any associated empty override
block from .oxlintrc.json so it remains valid JSON; only rename the
configuration to .oxlintrc.jsonc if comments are intentionally required.

In `@src/App.vue`:
- Line 63: Update the conditional rendering around ZoomShell and the v-else
AppShell in App.vue so zoom mode does not fall through to AppShell while
settingsReady is false. Add an explicit zoom loading/empty branch or exclude
isZoom from the AppShell fallback, while preserving ZoomShell rendering once
settingsReady and zoomMotor === 'dxgi'.

In `@src/components/app/AppShell.vue`:
- Around line 355-363: Normalize the dashPattern value before spreading it in
the overlay-toolbar update handler, using an empty array when
payload.dashPattern is missing. Update the setDashPattern call in the
AppShell.vue flow and match the existing handling in WhiteboardShell.vue; leave
the other setting defaults unchanged.

In `@src/components/overlay/OverlayShell.vue`:
- Around line 108-122: Guard the optional dashPattern field before spreading it
in both applyPayload implementations: update
src/components/overlay/OverlayShell.vue lines 108-122 and
src/components/whiteboard/WhiteboardShell.vue lines 176-184 to use an
empty-array fallback when payload.dashPattern is absent, preserving
synchronization for older or partial payloads.
- Around line 216-224: The group toggle handlers incorrectly use
groupSelection()'s return value to decide whether to ungroup. In
src/components/overlay/OverlayShell.vue lines 216-224 and
src/components/whiteboard/WhiteboardShell.vue lines 220-228, use the current
fully-grouped selection state exposed by useCanvasDrawing: call
ungroupSelection() when already grouped, otherwise call groupSelection(),
keeping both handlers identical or sharing the implementation.

In `@src/components/shared/CanvasStage.vue`:
- Around line 95-142: Update handleSharedEditHotkeys to return early when the
keyboard event target is an input, textarea, select, or contenteditable element,
in addition to the existing textInput.visible guard. Keep canvas hotkeys active
for other targets while preventing Ctrl+C/V/D/L/G from hijacking focused
form-field editing.

In `@src/components/shared/FloatingDock.vue`:
- Around line 415-419: Add outside-interaction handling for the more-options
menu controlled by moreOptionsOpen: register a document-level pointerdown
handler that closes the menu when the target is outside the menu and its toggle
button, while preserving the existing `@pointerdown.stop` behavior inside
more-options-overlay. Remove the handler during component cleanup and keep
toggleMoreOptions responsible for button toggling.
- Around line 173-230: Extract the line-style and shape-style business logic
from FloatingDock into a dedicated composable such as useLineStyleControls or
useShapeStyleControls. Move dash preset normalization, width-based pattern
generation and inference, nextDashPattern, border-radius cycling, and
arrow-style cycling into the composable, while keeping store access and reactive
state behavior intact. Update FloatingDock to consume the composable’s returned
state and handlers so the component remains focused on UI rendering and events.
- Around line 415-552: Update every icon-only button in the moreOptionsOpen
overlay, including the dash-style, lock, group, smoothing, auto-erase,
corner-radius, arrow-style, and text formatting/alignment controls, to provide
an accessible aria-label using the corresponding translated label. Extend the
tooltip styling or behavior so tooltip text is also shown when a button receives
:focus-visible, preserving the existing hover behavior.

In `@src/locales/es.json`:
- Around line 16-41: Correct the Spanish translations in the locale entries
around moreOptions, strokeDash, lockSelection, toggleGroupSelection, and
dashOptions.solid by adding the appropriate accents and using natural Spanish
terminology, including “Más”, “línea”, “selección”, and “Sólida”.

In `@src/utils/canvas/renderer.ts`:
- Around line 439-453: Update the underline rendering in the adjustedAction
text-decoration path to temporarily clear the line dash inherited from
applyStyle and restore the prior lineWidth after stroking, preserving
surrounding canvas state. Also correct the maxWidth calculation in the nearby
text wrapping logic to account for the canvas transform’s pan translation as
well as its scale, so wrapping remains aligned after panning.
- Around line 201-206: Update drawTailHead so its neighbor-point selection is
intentional rather than using identical ternary branches: mirror the existing
head-side smoothing behavior for arrows with more than two points, while
retaining the appropriate fallback for shorter arrows. Use the selected neighbor
when calculating tailAngle and preserve the existing missing-point guard.

---

Outside diff comments:
In `@src/utils/canvas/renderer.ts`:
- Around line 533-553: The text-bounds calculation near drawAction must use the
same wrapping layout as rendering instead of measuring raw newline-separated
lines. Extract or reuse a shared layoutText helper that applies wrapText with
the action’s max width, then use its wrapped lines for width and height while
preserving text alignment; update drawAction and the bounds path to consume this
shared result.

---

Nitpick comments:
In `@src/components/overlay/OverlayShell.vue`:
- Around line 96-106: Move viewport-resize recalibration from OverlayShell’s
calculateInitialPosition, recalculateDockPosition, and resetAndRecalculate into
useFloatingDock, making the composable the sole owner of dockPosition and its
persistence. Reuse handleDockDrag’s existing clamping and ensure recalculated
positions are persisted so sourcePosition updates cannot revert them. Update
both dock consumers to use the shared composable behavior and remove the
component-level duplicate logic.

In `@src/components/shared/CanvasStage.vue`:
- Around line 318-321: Update the font construction in CanvasStage’s
mirror/measurement logic to reuse the shared buildFont or formatting helper from
the canvas renderer utilities instead of maintaining a duplicate template.
Export the shared formatter as needed, then call it from both CanvasStage and
the renderer so measurement and rendering always use identical font strings.

In `@src/components/shared/FloatingDock.vue`:
- Around line 44-46: Update FloatingDock’s props definition to import and use
the exported DockMode type from useDockModeConfig.ts instead of re-declaring the
"overlay" | "whiteboard" union for mode, so it stays synchronized with the
shared mode definition.

In `@src/composables/useCanvasDrawing.ts`:
- Around line 906-933: Update updateSelection to use the IDs returned by
getSelectedActions directly, removing the redundant group expansion loop. Ensure
dragState.value.lastPoint is updated to the current point before returning when
movable is empty, so pointer movement over fully locked selections does not
accumulate stale deltas.
- Around line 625-667: Update toggleSelectedLock, groupSelectedActions, and
ungroupSelectedActions to record their in-place locked/groupId changes as a
dedicated mutate history entry containing the affected actions’ prior state, so
Ctrl+Z restores those values and earlier add undo cannot resurrect stale group
membership. Only append history when a mutation actually occurs, preserving
existing no-op returns. In groupSelectedActions, always assign a fresh group id
instead of reusing any pre-existing selected group id.

In `@src/composables/useDockModeConfig.ts`:
- Around line 8-52: Consolidate the identical overlay and whiteboard entries in
toolsByMode and controlsByMode into shared configuration data, avoiding
duplicate tool lists and control flags that can drift out of sync. Preserve both
DockMode keys and the current behavior, while leaving a clear path for future
per-mode overrides.

In `@src/composables/useFloatingDock.ts`:
- Around line 25-45: Add a window resize listener in the useFloatingDock
lifecycle so it invokes applyPositionForOrientation and refreshes
dockTooltipPlacement after viewport dimensions change. Register the listener on
mount and remove the same handler on unmount, preserving existing drag and
orientation behavior.

In `@src/stores/settings.ts`:
- Around line 277-281: Extract the shared normalizeDashPattern(pattern:
number[]): number[] helper from the inline logic in src/stores/settings.ts lines
277-281, preserving finite/non-negative filtering and rounding. Update
src/stores/settings.ts lines 424-428 to call it, and remove the local helper in
src/components/shared/FloatingDock.vue lines 175-179 so that component imports
and uses the shared implementation.
- Around line 18-23: Check src/types/drawing.ts first and reuse any existing
exported text and arrow-style unions; only define shared exports there if they
are absent. Update Settings, its refs, setter parameters, and guards to consume
those types, then replace duplicate literals in OverlayPayload and
FloatingDock.vue with the same shared definitions so all style values remain
synchronized.

In `@src/types/drawing.ts`:
- Around line 46-51: Define and export shared alias types named TextWeight,
TextStyle, TextDecoration, TextAlign, and ArrowStyle in the drawing types
module, then update the repeated union declarations in the normalizer,
useCanvasDrawing, CanvasStage, and both shells to import and reuse these
aliases.

In `@src/utils/canvas/normalizer.ts`:
- Around line 55-86: Update normalizeDrawAction to accept raw or partial input
and validate/default id, tool, color, and composite alongside the existing
normalized fields, ensuring unknown tool and composite values cannot reach
rendering. Preserve a valid DrawAction shape for untrusted persisted data,
reusing existing normalization conventions where applicable. Only include
text-specific fields and arrowStyle when action.tool supports them so unrelated
actions are not inflated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5df6802f-733d-4ede-986b-cb9beaf7a008

📥 Commits

Reviewing files that changed from the base of the PR and between 4e33953 and 44c6cf4.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (32)
  • .oxlintrc.json
  • LICENSE.md
  • docs/pages/features/live-draw.md
  • docs/pages/features/whiteboard.md
  • docs/pages/reference/settings.md
  • docs/pages/reference/shortcuts.md
  • package.json
  • src/App.vue
  • src/components/app/AppShell.vue
  • src/components/app/panels/HotkeysPanel.vue
  • src/components/overlay/OverlayShell.vue
  • src/components/shared/CanvasStage.vue
  • src/components/shared/FloatingDock.vue
  • src/components/whiteboard/WhiteboardShell.vue
  • src/composables/useCanvasDrawing.ts
  • src/composables/useCursorHighlightWindow.ts
  • src/composables/useDockModeConfig.ts
  • src/composables/useFloatingDock.ts
  • src/composables/useLocalShortcuts.ts
  • src/composables/useOverlayWindow.ts
  • src/composables/useSpotlightWindow.ts
  • src/constants/shortcuts-local.ts
  • src/locales/en.json
  • src/locales/es.json
  • src/stores/settings.ts
  • src/types/drawing.ts
  • src/types/overlay.ts
  • src/types/settings.ts
  • src/types/tools.ts
  • src/utils/canvas/normalizer.ts
  • src/utils/canvas/renderer.ts
  • tsconfig.json

Comment thread .oxlintrc.json Outdated
Comment thread src/App.vue
Comment thread src/components/app/AppShell.vue Outdated
Comment thread src/components/overlay/OverlayShell.vue
Comment thread src/components/overlay/OverlayShell.vue
Comment thread src/components/shared/FloatingDock.vue
Comment thread src/components/shared/FloatingDock.vue
Comment thread src/locales/es.json Outdated
Comment on lines +16 to +41
"moreOptions": "Mas opciones",
"strokeDash": "Estilo de linea",
"lockSelection": "Bloquear/Desbloquear seleccion",
"toggleGroupSelection": "Agrupar/Desagrupar seleccion",
"dashOptions": {
"solid": "Solida",
"dashed": "Guiones",
"dotted": "Punteada"
},
"text": {
"bold": "Negrita",
"italic": "Cursiva",
"underline": "Subrayado",
"alignLeft": "Alinear a la izquierda",
"alignCenter": "Centrar",
"alignRight": "Alinear a la derecha"
},
"shapes": {
"cornerRadius": "Radio de esquina",
"arrowStyle": "Estilo de flecha",
"arrowStyles": {
"simple": "Simple",
"filled": "Rellena",
"double": "Doble",
"thick": "Gruesa",
"stealth": "Sigilo"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix missing Spanish accents and terminology in the new labels.

Several user-facing translations are missing diacritics or use awkward terminology, including Mas, linea, seleccion, and Solida. Please correct these before release.

Proposed fix
-      "moreOptions": "Mas opciones",
-      "strokeDash": "Estilo de linea",
-      "lockSelection": "Bloquear/Desbloquear seleccion",
-      "toggleGroupSelection": "Agrupar/Desagrupar seleccion",
+      "moreOptions": "Más opciones",
+      "strokeDash": "Estilo de línea",
+      "lockSelection": "Bloquear/desbloquear selección",
+      "toggleGroupSelection": "Agrupar/desagrupar selección",
...
-        "solid": "Solida",
-        "dashed": "Guiones",
+        "solid": "Sólida",
+        "dashed": "Discontinua",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"moreOptions": "Mas opciones",
"strokeDash": "Estilo de linea",
"lockSelection": "Bloquear/Desbloquear seleccion",
"toggleGroupSelection": "Agrupar/Desagrupar seleccion",
"dashOptions": {
"solid": "Solida",
"dashed": "Guiones",
"dotted": "Punteada"
},
"text": {
"bold": "Negrita",
"italic": "Cursiva",
"underline": "Subrayado",
"alignLeft": "Alinear a la izquierda",
"alignCenter": "Centrar",
"alignRight": "Alinear a la derecha"
},
"shapes": {
"cornerRadius": "Radio de esquina",
"arrowStyle": "Estilo de flecha",
"arrowStyles": {
"simple": "Simple",
"filled": "Rellena",
"double": "Doble",
"thick": "Gruesa",
"stealth": "Sigilo"
"moreOptions": "Más opciones",
"strokeDash": "Estilo de línea",
"lockSelection": "Bloquear/desbloquear selección",
"toggleGroupSelection": "Agrupar/desagrupar selección",
"dashOptions": {
"solid": "Sólida",
"dashed": "Discontinua",
"dotted": "Punteada"
},
"text": {
"bold": "Negrita",
"italic": "Cursiva",
"underline": "Subrayado",
"alignLeft": "Alinear a la izquierda",
"alignCenter": "Centrar",
"alignRight": "Alinear a la derecha"
},
"shapes": {
"cornerRadius": "Radio de esquina",
"arrowStyle": "Estilo de flecha",
"arrowStyles": {
"simple": "Simple",
"filled": "Rellena",
"double": "Doble",
"thick": "Gruesa",
"stealth": "Sigilo"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/locales/es.json` around lines 16 - 41, Correct the Spanish translations
in the locale entries around moreOptions, strokeDash, lockSelection,
toggleGroupSelection, and dashOptions.solid by adding the appropriate accents
and using natural Spanish terminology, including “Más”, “línea”, “selección”,
and “Sólida”.

Comment thread src/utils/canvas/renderer.ts
Comment thread src/utils/canvas/renderer.ts
@daiv05
daiv05 merged commit c3d26d4 into main Jul 29, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant