-
Notifications
You must be signed in to change notification settings - Fork 118
Daily RC - Main Menu & Actions #485
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughIntroduces a comprehensive keyboard shortcut system with platform-aware keybinding infrastructure, extends the editor API with text formatting and fill/stroke operations, adds UI components for keyboard shortcut discovery and settings, and updates menus and toolbars to dynamically render shortcuts based on action definitions. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Settings as Settings Dialog
participant KS as KeyboardShortcuts Component
participant Actions as Actions Registry
participant Renderer as Shortcut Renderer
participant UI as Kbd UI Components
User->>Settings: Open Settings
Settings->>KS: Render KeyboardShortcuts
User->>KS: Enter search query
KS->>Actions: Filter actions by search
Actions-->>KS: Filtered actions
User->>KS: Select platform (macOS/Windows/Linux)
KS->>Renderer: Resolve keybindings for each action
loop For each filtered action
Renderer->>Actions: Get action keybindings
Actions-->>Renderer: Keybindings (platform-specific)
Renderer->>Renderer: Resolve platform-specific binding
Renderer->>UI: Render Kbd components with keys
UI-->>KS: Display formatted shortcuts
end
KS-->>User: Show filtered, platform-specific shortcuts
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
…manipulation functions
…ocumentation and UI elements
…and documentation
…nsistency across operations
…nodes, enhancing layout control
… and adjust documentation accordingly
…date shortcuts in documentation
…form-specific bindings and UI integration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
editor/grida-canvas-hosted/playground/playground.tsx (1)
380-397: Fix mismatch between “Open Library” tooltip and actual hotkey
useHotkeysstill binds the Library window to"shift+i", but the tooltip now rendersCtrl/Cmd + Iviauikbdk(M.CtrlCmd)anduikbdk(KeyCode.KeyI). That will confuse users and is hard to discover.Either change the hotkey to match the new chord (e.g.
meta+i, ctrl+iusing the keybinding system) or revert the tooltip to show Shift+I via the same helper.Also applies to: 651-653
editor/grida-canvas-react/viewport/hotkeys.tsx (1)
241-249: Avoid overlappingctrl+cbindings between color picker and copy on macOSColor picker now uses:
useHotkeys( keyboardOS === "mac" ? "i, ctrl+c" : "i", () => editor.surface.surfacePickColor(), ... );while copy still binds
"copy, meta+c, ctrl+c".On macOS,
Ctrl+Cisn’t the typical copy chord, but registering both handlers on the same combination means pressingCtrl+Cwill trigger both copy and color picking in the canvas, which is surprising and hard to reason about.Consider either:
- Dropping
ctrl+cfrom one of the handlers (keep color picker oni, and copy oncopy, meta+c), or- Splitting the bindings so
ctrl+cis only associated with one semantic action.This keeps the mental model simple and avoids accidental multi‑action triggers.
Also applies to: 542-546
🧹 Nitpick comments (17)
editor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsx (1)
43-43: Dynamic shortcut wiring for toolbar tools looks consistentUsing
keyboardShortcutText(...)for pencil/path/brush/eraser, cursor tools, container/text, and basic shapes is consistent with the rest of the UX host shortcut system, and the tooltip / menu rendering already avoids displaying empty strings when no binding exists. I don’t see functional issues here.If you want to DRY things up a bit, you could centralize the action IDs in a small mapping (e.g.
toolActionIdByToolbarValue) so both hotkeys and UI hints share a single source, but that’s purely optional.Also applies to: 116-123, 149-151, 164-165, 172-173, 181-184
editor/grida-canvas-react/use-context-menu-actions.ts (1)
8-8: Context menu shortcuts correctly hooked into keyboardShortcutTextThe new
keyboardShortcutText(...)usage for PNG copy, z-order, group/ungroup, auto layout, flatten, active/locked toggles, zoom-to-fit, and delete is consistent with the UX host action namespace and remains safely optional (empty string → no label).You also still compute
targetSingleOrSelectionabove but re-inline the same ternary inflatten. If you want to simplify, you could calleditor.commands.flatten(targetSingleOrSelection)and drop the duplication.Also applies to: 108-205
editor/grida-canvas/editor.i.ts (2)
3328-3347: Stroke paint helpers and swapFillAndStroke API are well‑structuredThe new
swapFillAndStroke(target, ensureStroke?)command plus the extendedchangeNodePropertyStrokes(..., ensureStrokeWidth?)andaddNodeStroke(..., at?, ensureStrokeWidth?)overloads give you a single, well‑documented path for stroke updates and stroke‑visibility guarantees.The signatures are backward compatible (new args are optional and appended), and the docs make clear that only paint arrays are swapped while other stroke props are preserved, which matches how the hotkeys use these operations.
Implementation-wise, just ensure all internal callers of
changeNodePropertyStrokesare updated to pass theensureStrokeWidthflag where behavior should change; the type-level surface looks correct.Also applies to: 3489-3541, 3585-3639
3819-4044: New a11y text, paint, and aspect‑ratio APIs line up with viewport hotkeysThe additions to
IEditorA11yActions:
a11yTextAlign/a11yTextVerticalAligna11yChangeTextFontSize/LineHeight/LetterSpacing/FontWeighta11yClearFill,a11yClearStroke,a11ySwapFillAndStrokea11yLockAspectRatio,a11yUnlockAspectRatioare all consistent with how
useEditorHotKeysinvokes them and with the underlying lower‑level command set (changeTextNode*,changeNodePropertyStrokes,lockAspectRatio/unlockAspectRatio). The JSDoc examples also match the actual keybindings you wired up (e.g. ⌘+⇧+>/< for font size).From a public API standpoint this is a clean, semantic layer over the existing commands. Just be sure the implementations enforce the “text nodes only” and aspect‑ratio preconditions described in the comments.
editor/grida-canvas-react/viewport/hotkeys.tsx (1)
367-455: Text alignment and rich text formatting hotkeys are wired correctlyThe meta/ctrl+alt alignment shortcuts and the period/comma‑based text formatting hotkeys correctly route through the new
a11yTextAlign,a11yChangeTextFontSize/LineHeight/LetterSpacing/FontWeightsurface methods and use sharedtextFormattingOptionswithsplitKey: "|", which works correctly in react-hotkeys-hook v4.6.1 to avoid conflicts with the default comma separator.To tidy things up, remove the redundant hotkey patterns where both sides of the pipe are identical: at lines 433, 438, 447, and 452, simplify
"alt+shift+period | alt+shift+period"to"alt+shift+period","alt+shift+comma | alt+shift+comma"to"alt+shift+comma","alt+period | alt+period"to"alt+period", and"alt+comma | alt+comma"to"alt+comma".editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx (1)
822-825: Documented stroke-width tech debt is clear and accurateThe TODO clearly describes the current ad‑hoc
stroke_widthhandling and the plan to migrate to the centralizedensureStrokeWidthpath inaddNodeStroke/changeNodePropertyStrokes. This is a good marker for a future cleanup when you align mixed-selection strokes withSectionStrokes.editor/grida-canvas-hosted/playground/uxhost-menu.md (1)
5-58: Keep shortcut docs in sync with dynamic keybindings; minor wording nitThis markdown gives a solid overview of menus/shortcuts, but since the runtime now resolves shortcuts dynamically, these static glyphs can drift over time (especially on non‑macOS platforms). Consider either:
- Generating this table from the central keybinding registry, or
- Adding a note that the listed shortcuts reflect the default macOS layout and may vary by platform/config.
Also, in the “Copy as PNG” row you can drop “image” (“Copies the selected items as PNG”) to avoid the “PNG image” tautology.
Also applies to: 61-148, 151-154
editor/grida-canvas-react/provider.tsx (1)
146-151: Extended addStroke API cleanly exposes ensureStrokeWidth optionUpdating
addStroketo accept an optionalensureStrokeWidth?: booleanand forwarding it toaddNodeStrokeis a straightforward way to surface the new behavior without breaking existing call sites. Callers that don’t care about auto‑width can continue using the 1–2 argument form.editor/grida-canvas-hosted/playground/uxhost-settings-keyboardshortcuts.tsx (1)
42-55: Platform fallback order could be more intuitive.The fallback chain (
mac→linux→windows) may produce unexpected results when viewing Windows shortcuts but no Windows-specific binding exists. Consider using the selected platform first, then falling back to a more common default:const platformBinding = keybindings[selectedPlatform] || - keybindings.mac || - keybindings.linux || - keybindings.windows; + keybindings.windows || + keybindings.mac || + keybindings.linux;Alternatively, document why
macis preferred as the primary fallback.editor/grida-canvas-hosted/playground/uxhost-shortcut-renderer.tsx (1)
86-111: Consider extracting shared sequence-to-string logic.
keyboardShortcutTextFormattedduplicates the mapping logic fromsequenceToString(lines 33-46). Consider reusingsequenceToStringor extracting a shared helper:export function keyboardShortcutTextFormatted( actionId: UXHostActionId, platform?: "mac" | "windows" | "linux", separator: string = " " ): string { const sequences = getResolvedSequences(actionId, platform); if (sequences.length === 0) { return ""; } const targetPlatform = platform || getKeyboardOS(); - const sequence = sequences[0]; - - return sequence - .map((chunk) => { - const parts: string[] = []; - chunk.mods.forEach((mod) => { - parts.push(keycodeToPlatformUILabel(mod, targetPlatform)); - }); - chunk.keys.forEach((key) => { - parts.push(keycodeToPlatformUILabel(key, targetPlatform)); - }); - return parts.join(""); - }) - .join(separator); + return sequenceToString(sequences[0], targetPlatform).replace(/ /g, separator); }Note: This assumes the default separator in
sequenceToStringis a space.editor/grida-canvas-hosted/playground/uxhost-menu.tsx (1)
619-634: Consider movingapplyToTextNodeshelper to shared scope.The
applyToTextNodeshelper is useful but defined insideTextMenuContent. If similar iteration patterns are needed elsewhere (e.g., ObjectMenuContent for multi-selection operations), consider extracting it to a shared utility or the component's parent scope.editor/grida-canvas-hosted/playground/uxhost-actions.ts (1)
26-35: Minor: Unused import.The
Keybindingtype is imported but doesn't appear to be used in this file. The file usesKeybindings(plural) for the action keybindings field.import { - type Keybinding, type Keybindings, kb, c, seq, M, platformKb, } from "@/grida-canvas/keybinding";editor/components/ui/field.tsx (1)
10-244: Field composition API and slot structure look solidThe overall Field/FieldSet/FieldGroup/FieldLabel/... design is cohesive, variant handling via
cvais clean, andFieldError’s memoized rendering correctly handles single vs multiple error messages. From a correctness and accessibility standpoint this is good to ship.If you later need ref access for these wrappers (e.g., for programmatic focus), consider wrapping the main primitives (
Field,FieldSet,FieldGroup,FieldContent, etc.) withforwardRef, but that can be deferred.editor/grida-canvas/keycode.ts (1)
1-10: Avoid local tweaks that drift from upstream VS Code sourceThis file is explicitly marked as a direct copy of VS Code’s
keyCodes.tsand meant to be kept in sync. The Biome warning abouttoString(onScanCodeUtils.toString/KeyCodeUtils.toString) is coming from lint rules, not a runtime issue.Rather than renaming these APIs (which would diverge from upstream and complicate future syncs), consider disabling
noShadowRestrictedNamesfor this file or for these specific exports in your lint configuration.Also applies to: 474-478, 778-822
editor/grida-canvas/editor.ts (3)
1752-1782: Stroke helpers are functionally correct; be aware of undo granularity and non‑stroke nodesThe new stroke helpers (
changeNodePropertyStrokes,addNodeStroke,swapFillAndStroke) behave correctly:
ensureStrokeWidthonly applies when needed and uses the existingchangeNodePropertyStrokeWidthpath.swapFillAndStrokecorrectly pulls paints viaeditor.resolvePaintsand routes stroke updates back throughchangeNodePropertyStrokesso width is normalized when requested.Two small considerations:
Undo granularity:
changeNodePropertyStrokesdispatches once per node to updatestroke_paints, then may dispatch again per node to updatestroke_width. That means a single user action (e.g. “swap fill & stroke with ensure stroke”) can produce multiple history entries. If you want a single undo step for this UX operation, you might later introduce a dedicated reducer action that applies both changes in one dispatch.Nodes without
stroke_width:
The guardconst currentStrokeWidth = "stroke_width" in node ? node.stroke_width : undefined;avoids reading a missing property, and the
setchange type meansresolveNumberChangeValuewon’t touch the node in that case, so this is safe even for nodes that conceptually don’t support strokes.Given the current goals, this is fine to ship, but keep the undo behavior in mind if users expect a single-step history operation for swap/ensure‑stroke flows.
Also applies to: 1838-1863, 1784-1808
3692-3728: Font‑weight navigation helper is well‑structured; watch repeated family parsing
Editor.getFontWeightsForFontFamilyandEditorSurface.a11yChangeTextFontWeightnicely encapsulate “next/previous weight within a family” behavior:
- Weights are derived from
font.stylesfiltered by italic state and deduplicated, then sorted.- The a11y helper wraps around when there’s no heavier/lighter weight, and falls back to
changeTextNodeFontWeightifselectFontStylecan’t find a matching face.The only minor concern is potential repeated work when many nodes share the same family: each call to
a11yChangeTextFontWeightawaitsgetFontWeightsForFontFamily, which in turn callsgetFontFamilyDetailsSync. If_fontManager.parseFontFamilyisn’t already caching by family, you may want to add an internal cache in the font manager so repeated weight queries are effectively free.Also applies to: 5223-5288
5301-5317: Color picker flow is UX‑friendly; ensure it only runs on the clientThe
promptColorPicker/surfacePickColorduo is well‑designed:
promptColorPickerisolates the EyeDropper API and returnssRGBHex | undefinedwhile gracefully handling cancellation.surfacePickColorapplies a solid fill to the current selection when present, or copies the hex to clipboard and shows a toast when there’s no selection, with error handling around both EyeDropper and clipboard failures.Two small environment notes:
- These methods assume a browser context (
window.EyeDropper,navigator.clipboard,toast); ensure they’re only called from client‑side code paths (which is already implied by their UX nature).- If you ever need to support non‑Chrome browsers that don’t expose
EyeDropper, you may want to downgrade the thrown error inpromptColorPickerto a more generic feature‑detection path (e.g., returnundefinedand let the caller show a UI hint), but the current toast‑based error reporting is acceptable for now.Also applies to: 5380-5445
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (28)
docs/editor/shortcuts/index.mdeditor/components/ui/field.tsxeditor/components/ui/label.tsxeditor/components/ui/separator.tsxeditor/grida-canvas-hosted/playground/playground.tsxeditor/grida-canvas-hosted/playground/uxhost-actions.tseditor/grida-canvas-hosted/playground/uxhost-menu.mdeditor/grida-canvas-hosted/playground/uxhost-menu.tsxeditor/grida-canvas-hosted/playground/uxhost-settings-keyboardshortcuts.tsxeditor/grida-canvas-hosted/playground/uxhost-settings.tsxeditor/grida-canvas-hosted/playground/uxhost-shortcut-renderer.tsxeditor/grida-canvas-hosted/playground/uxhost-toolbar.tsxeditor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsxeditor/grida-canvas-react-starter-kit/starterkit-toolbar/path-toolbar.tsxeditor/grida-canvas-react/devtools/keysymbols.tseditor/grida-canvas-react/provider.tsxeditor/grida-canvas-react/use-context-menu-actions.tseditor/grida-canvas-react/viewport/hotkeys.tsxeditor/grida-canvas-react/viewport/surface.tsxeditor/grida-canvas/editor.i.tseditor/grida-canvas/editor.tseditor/grida-canvas/keybinding.tseditor/grida-canvas/keycode.tseditor/package.jsoneditor/scaffolds/sidecontrol/chunks/section-strokes.tsxeditor/scaffolds/sidecontrol/controls/ext-align.tsxeditor/scaffolds/sidecontrol/controls/ext-zoom.tsxeditor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
💤 Files with no reviewable changes (2)
- editor/grida-canvas-react/devtools/keysymbols.ts
- editor/scaffolds/sidecontrol/controls/ext-align.tsx
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use TypeScript 5 as the main language for most apps
Use Lucide or Radix Icons for icons
Files:
editor/grida-canvas-react/viewport/surface.tsxeditor/scaffolds/sidecontrol/controls/ext-zoom.tsxeditor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsxeditor/grida-canvas-hosted/playground/uxhost-settings-keyboardshortcuts.tsxeditor/scaffolds/sidecontrol/sidecontrol-node-selection.tsxeditor/grida-canvas-hosted/playground/uxhost-shortcut-renderer.tsxeditor/grida-canvas-react/use-context-menu-actions.tseditor/components/ui/field.tsxeditor/grida-canvas-react/provider.tsxeditor/grida-canvas-hosted/playground/playground.tsxeditor/grida-canvas-react-starter-kit/starterkit-toolbar/path-toolbar.tsxeditor/grida-canvas-hosted/playground/uxhost-menu.tsxeditor/components/ui/separator.tsxeditor/grida-canvas-hosted/playground/uxhost-toolbar.tsxeditor/grida-canvas/keybinding.tseditor/grida-canvas-react/viewport/hotkeys.tsxeditor/grida-canvas-hosted/playground/uxhost-settings.tsxeditor/components/ui/label.tsxeditor/grida-canvas-hosted/playground/uxhost-actions.tseditor/grida-canvas/keycode.tseditor/scaffolds/sidecontrol/chunks/section-strokes.tsxeditor/grida-canvas/editor.i.tseditor/grida-canvas/editor.ts
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use React.js 19 for web applications
Files:
editor/grida-canvas-react/viewport/surface.tsxeditor/scaffolds/sidecontrol/controls/ext-zoom.tsxeditor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsxeditor/grida-canvas-hosted/playground/uxhost-settings-keyboardshortcuts.tsxeditor/scaffolds/sidecontrol/sidecontrol-node-selection.tsxeditor/grida-canvas-hosted/playground/uxhost-shortcut-renderer.tsxeditor/components/ui/field.tsxeditor/grida-canvas-react/provider.tsxeditor/grida-canvas-hosted/playground/playground.tsxeditor/grida-canvas-react-starter-kit/starterkit-toolbar/path-toolbar.tsxeditor/grida-canvas-hosted/playground/uxhost-menu.tsxeditor/components/ui/separator.tsxeditor/grida-canvas-hosted/playground/uxhost-toolbar.tsxeditor/grida-canvas-react/viewport/hotkeys.tsxeditor/grida-canvas-hosted/playground/uxhost-settings.tsxeditor/components/ui/label.tsxeditor/scaffolds/sidecontrol/chunks/section-strokes.tsx
{editor/**/*.{ts,tsx},packages/grida-canvas-*/**/*.{ts,tsx}}
📄 CodeRabbit inference engine (AGENTS.md)
Use DOM (plain DOM as canvas) for website builder canvas, bound with React
Files:
editor/grida-canvas-react/viewport/surface.tsxeditor/scaffolds/sidecontrol/controls/ext-zoom.tsxeditor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsxeditor/grida-canvas-hosted/playground/uxhost-settings-keyboardshortcuts.tsxeditor/scaffolds/sidecontrol/sidecontrol-node-selection.tsxeditor/grida-canvas-hosted/playground/uxhost-shortcut-renderer.tsxeditor/grida-canvas-react/use-context-menu-actions.tseditor/components/ui/field.tsxeditor/grida-canvas-react/provider.tsxeditor/grida-canvas-hosted/playground/playground.tsxeditor/grida-canvas-react-starter-kit/starterkit-toolbar/path-toolbar.tsxeditor/grida-canvas-hosted/playground/uxhost-menu.tsxeditor/components/ui/separator.tsxeditor/grida-canvas-hosted/playground/uxhost-toolbar.tsxeditor/grida-canvas/keybinding.tseditor/grida-canvas-react/viewport/hotkeys.tsxeditor/grida-canvas-hosted/playground/uxhost-settings.tsxeditor/components/ui/label.tsxeditor/grida-canvas-hosted/playground/uxhost-actions.tseditor/grida-canvas/keycode.tseditor/scaffolds/sidecontrol/chunks/section-strokes.tsxeditor/grida-canvas/editor.i.tseditor/grida-canvas/editor.ts
editor/grida-*/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use /editor/grida-* directories to isolate domain-specific modules; promote well-defined modules to /packages
Files:
editor/grida-canvas-react/viewport/surface.tsxeditor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsxeditor/grida-canvas-hosted/playground/uxhost-settings-keyboardshortcuts.tsxeditor/grida-canvas-hosted/playground/uxhost-shortcut-renderer.tsxeditor/grida-canvas-react/use-context-menu-actions.tseditor/grida-canvas-react/provider.tsxeditor/grida-canvas-hosted/playground/playground.tsxeditor/grida-canvas-react-starter-kit/starterkit-toolbar/path-toolbar.tsxeditor/grida-canvas-hosted/playground/uxhost-menu.tsxeditor/grida-canvas-hosted/playground/uxhost-toolbar.tsxeditor/grida-canvas/keybinding.tseditor/grida-canvas-react/viewport/hotkeys.tsxeditor/grida-canvas-hosted/playground/uxhost-settings.tsxeditor/grida-canvas-hosted/playground/uxhost-actions.tseditor/grida-canvas/keycode.tseditor/grida-canvas/editor.i.tseditor/grida-canvas/editor.ts
editor/scaffolds/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use /editor/scaffolds for feature-specific larger components, pages, and editors
Files:
editor/scaffolds/sidecontrol/controls/ext-zoom.tsxeditor/scaffolds/sidecontrol/sidecontrol-node-selection.tsxeditor/scaffolds/sidecontrol/chunks/section-strokes.tsx
**/components/ui/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Shadcn UI for UI component library
Files:
editor/components/ui/field.tsxeditor/components/ui/separator.tsxeditor/components/ui/label.tsx
editor/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use /editor/components for generally reusable components
Files:
editor/components/ui/field.tsxeditor/components/ui/separator.tsxeditor/components/ui/label.tsx
editor/components/ui/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use /editor/components/ui for shadcn UI components
Files:
editor/components/ui/field.tsxeditor/components/ui/separator.tsxeditor/components/ui/label.tsx
docs/**/*.md
📄 CodeRabbit inference engine (AGENTS.md)
Documentation source of truth is in the ./docs directory; deployment is handled by apps/docs
Files:
docs/editor/shortcuts/index.md
🧠 Learnings (16)
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to packages/grida-canvas-*/**/*.{ts,tsx,js,jsx} : Packages under /packages/grida-canvas-* power the canvas; some are published to npm, refer to individual package README
Applied to files:
editor/grida-canvas-react/viewport/surface.tsxeditor/grida-canvas-hosted/playground/playground.tsx
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to **/components/ui/**/*.{ts,tsx} : Use Shadcn UI for UI component library
Applied to files:
editor/package.jsoneditor/components/ui/field.tsxeditor/grida-canvas-hosted/playground/playground.tsxeditor/components/ui/separator.tsxeditor/grida-canvas-hosted/playground/uxhost-settings.tsx
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to **/*.{ts,tsx} : Use Lucide or Radix Icons for icons
Applied to files:
editor/package.jsoneditor/grida-canvas-hosted/playground/uxhost-menu.tsx
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to **/*.{jsx,tsx} : Use React.js 19 for web applications
Applied to files:
editor/package.json
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to {editor/**/*.{ts,tsx},packages/grida-canvas-*/**/*.{ts,tsx}} : Use DOM (plain DOM as canvas) for website builder canvas, bound with React
Applied to files:
editor/scaffolds/sidecontrol/controls/ext-zoom.tsxeditor/grida-canvas-hosted/playground/playground.tsxeditor/grida-canvas-hosted/playground/uxhost-toolbar.tsx
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : Use React hooks for state management (imageSrc, shape, grid, maxRadius, gamma, jitter, opacity, color, customShapeImage, imageDataRef, sizeRef)
Applied to files:
editor/scaffolds/sidecontrol/controls/ext-zoom.tsxeditor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsxeditor/grida-canvas-hosted/playground/uxhost-toolbar.tsxeditor/grida-canvas-hosted/playground/uxhost-settings.tsx
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : When adding new shape types, update the Shape type union, add cases in drawShape() function, add cases in shapeToSVG() function, and add SelectItem in UI
Applied to files:
editor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsxeditor/scaffolds/sidecontrol/sidecontrol-node-selection.tsxeditor/grida-canvas-react/provider.tsxeditor/grida-canvas-hosted/playground/uxhost-toolbar.tsxeditor/scaffolds/sidecontrol/chunks/section-strokes.tsxeditor/grida-canvas/editor.i.tseditor/grida-canvas/editor.ts
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to editor/components/ui/**/*.{ts,tsx} : Use /editor/components/ui for shadcn UI components
Applied to files:
editor/grida-canvas-hosted/playground/uxhost-settings-keyboardshortcuts.tsxeditor/components/ui/field.tsxeditor/grida-canvas-hosted/playground/playground.tsxeditor/grida-canvas-hosted/playground/uxhost-menu.tsxeditor/grida-canvas-hosted/playground/uxhost-settings.tsx
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : When adding new parameters to the halftone tool, add state with useState, include in useEffect dependency array, pass to renderHalftone() function, use in rendering logic, and add UI control
Applied to files:
editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsxeditor/scaffolds/sidecontrol/chunks/section-strokes.tsx
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to editor/scaffolds/**/*.{ts,tsx} : Use /editor/scaffolds for feature-specific larger components, pages, and editors
Applied to files:
editor/scaffolds/sidecontrol/sidecontrol-node-selection.tsx
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to editor/components/**/*.{ts,tsx} : Use /editor/components for generally reusable components
Applied to files:
editor/components/ui/field.tsx
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to editor/lib/**/*.{ts,tsx} : Use /editor/lib for core, strictly designed modules with non-opinionated, reusable, and stable implementations
Applied to files:
editor/components/ui/field.tsxeditor/grida-canvas/keybinding.ts
📚 Learning: 2025-12-14T23:30:42.112Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T23:30:42.112Z
Learning: Applies to editor/grida-*/**/*.{ts,tsx} : Use /editor/grida-* directories to isolate domain-specific modules; promote well-defined modules to <root>/packages
Applied to files:
editor/components/ui/field.tsxeditor/grida-canvas/keybinding.tseditor/grida-canvas/keycode.ts
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : Use Canvas 2D API with path commands for rendering geometric shapes (circle, square, triangle, etc.)
Applied to files:
editor/grida-canvas-react/provider.tsxeditor/grida-canvas-react-starter-kit/starterkit-toolbar/path-toolbar.tsxeditor/grida-canvas-react/viewport/hotkeys.tsxeditor/scaffolds/sidecontrol/chunks/section-strokes.tsxeditor/grida-canvas/editor.i.tseditor/grida-canvas/editor.ts
📚 Learning: 2025-12-01T00:22:19.083Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: crates/grida-canvas-wasm/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:19.083Z
Learning: Applies to crates/grida-canvas-wasm/**/main.rs : Update `grida-canvas-wasm.d.ts` TypeScript definitions file when new APIs are introduced via `main.rs`
Applied to files:
editor/grida-canvas/keybinding.tseditor/grida-canvas/keycode.ts
📚 Learning: 2025-12-01T00:22:56.899Z
Learnt from: CR
Repo: gridaco/grida PR: 0
File: editor/app/(tools)/tools/halftone/AGENTS.md:0-0
Timestamp: 2025-12-01T00:22:56.899Z
Learning: Applies to editor/app/(tools)/tools/halftone/app/(tools)/tools/halftone/_page.tsx : For SVG export, convert circles to <circle> elements, rectangles to <rect> elements, and polygons to <polygon> elements with calculated points
Applied to files:
editor/grida-canvas/editor.ts
🧬 Code graph analysis (13)
editor/grida-canvas-react/viewport/surface.tsx (1)
editor/grida-canvas-hosted/playground/uxhost-shortcut-renderer.tsx (1)
keyboardShortcutText(55-66)
editor/scaffolds/sidecontrol/controls/ext-zoom.tsx (2)
editor/components/ui/dropdown-menu.tsx (2)
DropdownMenuShortcut(253-253)DropdownMenuCheckboxItem(249-249)editor/grida-canvas-hosted/playground/uxhost-shortcut-renderer.tsx (1)
keyboardShortcutText(55-66)
editor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsx (2)
editor/grida-canvas-hosted/playground/uxhost-shortcut-renderer.tsx (1)
keyboardShortcutText(55-66)editor/grida-canvas-react-starter-kit/starterkit-toolbar/utils.ts (1)
ToolbarToolType(3-22)
editor/grida-canvas-hosted/playground/uxhost-settings-keyboardshortcuts.tsx (5)
editor/grida-canvas/keybinding.ts (1)
Keybindings(55-61)editor/components/ui/kbd.tsx (2)
KbdGroup(28-28)Kbd(28-28)editor/grida-canvas-hosted/playground/uxhost-actions.ts (1)
actions(50-899)editor/components/ui/input-group.tsx (3)
InputGroup(164-164)InputGroupAddon(165-165)InputGroupInput(168-168)editor/components/ui/label.tsx (1)
Label(26-26)
editor/grida-canvas-react/use-context-menu-actions.ts (1)
editor/grida-canvas-hosted/playground/uxhost-shortcut-renderer.tsx (1)
keyboardShortcutText(55-66)
editor/components/ui/field.tsx (2)
editor/components/ui/label.tsx (1)
Label(26-26)editor/components/ui/separator.tsx (1)
Separator(31-31)
editor/grida-canvas-react-starter-kit/starterkit-toolbar/path-toolbar.tsx (2)
editor/grida-canvas-hosted/playground/uxhost-shortcut-renderer.tsx (1)
keyboardShortcutText(55-66)editor/grida-canvas/keybinding.ts (1)
uikbdk(382-404)
editor/grida-canvas-hosted/playground/uxhost-toolbar.tsx (3)
editor/grida-canvas-hosted/playground/uxhost-shortcut-renderer.tsx (1)
keyboardShortcutText(55-66)editor/grida-canvas-react-starter-kit/starterkit-toolbar/index.tsx (1)
ToolGroupItem(45-83)editor/grida-canvas-react-starter-kit/starterkit-toolbar/utils.ts (1)
ToolbarToolType(3-22)
editor/grida-canvas-react/viewport/hotkeys.tsx (2)
editor/grida-canvas/keybinding.ts (1)
getKeyboardOS(202-207)editor/grida-canvas-hosted/playground/uxhost-shortcut-renderer.tsx (1)
keyboardShortcutText(55-66)
editor/grida-canvas-hosted/playground/uxhost-settings.tsx (4)
editor/components/ui/dialog.tsx (4)
Dialog(133-133)DialogContent(135-135)DialogHeader(138-138)DialogTitle(141-141)editor/components/sidebar/index.tsx (1)
SidebarMenuItem(179-275)editor/components/ui/field.tsx (5)
FieldGroup(238-238)Field(234-234)FieldLabel(235-235)FieldDescription(236-236)FieldSeparator(240-240)editor/grida-canvas-hosted/playground/uxhost-settings-keyboardshortcuts.tsx (1)
KeyboardShortcuts(115-190)
editor/grida-canvas-hosted/playground/uxhost-actions.ts (1)
editor/grida-canvas/keybinding.ts (5)
Keybindings(55-61)kb(99-101)platformKb(106-112)seq(89-91)c(81-83)
editor/grida-canvas/editor.i.ts (5)
packages/grida-canvas-schema/grida.ts (1)
NodeID(1182-1182)editor/grida-canvas/utils/supports.ts (2)
strokes(322-329)stroke(314-321)packages/grida-canvas-cg/lib.ts (3)
Paint(699-705)TextAlign(263-263)TextAlignVertical(273-273)editor/grida-canvas/utils/properties.ts (1)
stroke(87-87)packages/grida-canvas-io-svg/lib.ts (1)
stroke(147-178)
editor/grida-canvas/editor.ts (3)
editor/grida-canvas/utils/supports.ts (2)
strokes(322-329)stroke(314-321)packages/grida-canvas-cg/lib.ts (2)
Paint(699-705)SolidPaint(737-742)editor/grida-canvas/editor.i.ts (2)
FontStyleInstance(656-687)NodeID(136-136)
🪛 Biome (2.1.2)
editor/grida-canvas/keycode.ts
[error] 779-779: Do not shadow the global "toString" property.
Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.
(lint/suspicious/noShadowRestrictedNames)
🪛 LanguageTool
editor/grida-canvas-hosted/playground/uxhost-menu.md
[style] ~21-~21: This phrase is redundant (‘G’ stands for ‘graphic’). Use simply “PNG”.
Context: ...y as PNG | Copies the selected items as PNG image (canvas backend only) | ⇧⌘C | ✅ ...
(ACRONYM_TAUTOLOGY)
🔇 Additional comments (23)
editor/grida-canvas-hosted/playground/playground.tsx (1)
662-670: Confirm non‑modal dropdown behavior is desiredSetting
<DropdownMenu modal={false}>changes focus trapping and allows interaction with other parts of the UI while the menu is open. That’s often desired for app menus but does alter Radix’s default behavior; worth double‑checking that it matches your UX expectations for this header menu.editor/grida-canvas-react/viewport/hotkeys.tsx (2)
467-505: Fill/stroke manipulation and stacking hotkeys align with new surface APIs
alt+slash,shift+slash, andshift+xcorrectly calla11yClearFill,a11yClearStroke, anda11ySwapFillAndStroke("selection")and guard withpreventDefaultand form/contentEditable disables, so they won’t interfere with typing or browser shortcuts.- The new
meta+]/[,ctrl+]/[bindings for"forward"/"backward"stack moves nicely complement the existing]/[front/back behavior and route througheditor.surface.order, matching the documentation ineditor.api.order.I don’t see functional issues here; the mappings are coherent with the rest of the ordering and paint‑editing semantics.
Also applies to: 797-811
848-857: Toast message now correctly reflects configured group‑with‑container shortcutUsing
keyboardShortcutText("workbench.surface.object.group-with-container")in the error toast keeps the “use … for grouping” hint in sync with the actual keymap, including OS‑specific symbols. This avoids drift when shortcuts change elsewhere.editor/grida-canvas-react/viewport/surface.tsx (1)
76-76: Dynamic shortcut rendering for ruler context menu looks correctImporting
keyboardShortcutTextand using the"workbench.surface.view.hide-show-ruler"action inContextMenuShortcutkeeps the context menu in sync with platform-aware keybindings. No issues spotted here.Also applies to: 1985-1988
editor/scaffolds/sidecontrol/controls/ext-zoom.tsx (1)
20-20: Zoom dropdown now correctly uses centralized, platform-aware shortcutsWiring all relevant zoom/pixel grid/ruler items through
keyboardShortcutText(...)aligns this menu with the centralized keybinding system and avoids hardcoded glyphs. The action IDs look consistent with the rest of the editor.Also applies to: 56-58, 66-68, 76-78, 86-88, 103-105, 123-127, 137-139
editor/package.json (1)
75-75: Radix Label minor version bump – verify against upstream notesThe
@radix-ui/react-labeldependency is bumped to^2.1.7. This looks fine, but please double-check the Radix changelog to ensure there are no breaking changes affecting yourLabelwrapper and related form components.editor/grida-canvas-hosted/playground/uxhost-toolbar.tsx (1)
38-38: Toolbar now correctly delegates all tool shortcuts to the central resolverUsing
keyboardShortcutTextacross the cursor/shape/draw tool groups and the bitmap “Paint Bucket” tooltip removes hardcoded key labels and keeps the toolbar aligned with the keybinding registry. The action IDs look consistent and existing selection behavior remains unchanged.Also applies to: 69-92, 101-107, 111-114, 121-147, 162-187, 228-247
editor/grida-canvas-react-starter-kit/starterkit-toolbar/path-toolbar.tsx (1)
12-14: Path toolbar tooltips now correctly use centralized shortcut helpersSwitching these tooltips to
keyboardShortcutText(anduikbdk(M.CtrlCmd)for the bend modifier) makes the path tools’ shortcuts platform-aware and consistent with the rest of the editor. The IDs look coherent and no functional regressions are apparent.Also applies to: 46-48, 61-64, 77-77, 90-93, 110-113
editor/scaffolds/sidecontrol/chunks/section-strokes.tsx (1)
147-153: LGTM! Clean refactor of stroke addition logic.The change correctly delegates stroke width normalization to
addStrokevia theensureStrokeWidthparameter, removing the need for a separate stroke width mutation. The dependency array is properly updated to excludestroke_widthsince it's no longer used in the callback body.editor/grida-canvas-hosted/playground/uxhost-settings-keyboardshortcuts.tsx (1)
115-133: LGTM! Well-structured search and filtering logic.The component correctly initializes platform from
getKeyboardOS(), memoizes filtered actions, and handles the empty search case efficiently. The search covers name, description, and command fields appropriately.docs/editor/shortcuts/index.md (2)
91-92: LGTM! New layer navigation shortcuts documented.Move forward (
⌘ + ]) and Move backward (⌘ + [) are correctly documented with platform-appropriate mappings, matching the definitions inuxhost-actions.ts.
118-135: LGTM! Comprehensive text formatting shortcuts.The expanded text formatting section covers all major typography controls with correct platform-specific mappings. The shortcuts align with the action definitions in
uxhost-actions.ts.editor/grida-canvas-hosted/playground/uxhost-settings.tsx (3)
79-105: LGTM! Well-structured sidebar navigation.The sidebar layout with SidebarProvider properly scopes the sidebar state. Menu items are cleanly defined as a data structure, and the active tab state management is straightforward.
107-145: LGTM! Theme selector implementation.The theme selector correctly integrates with
next-themesand provides light/dark/system options with appropriate icons. The Field-based layout is clean and accessible.
154-160: No action required—editor.debug assignment is properly reactive.The
editor.debugproperty uses a reactive getter/setter pattern (editor/grida-canvas/editor.ts lines 2672–2681). The setter callsthis.doc.reduce()to update state, which triggers subscriptions that React syncs viauseSyncExternalStorein theuseEditorhook. The direct assignmenteditor.debug = vcorrectly invokes the setter and maintains sync between the Switch component and the editor state.Likely an incorrect or invalid review comment.
editor/components/ui/separator.tsx (1)
8-31: LGTM! Clean forwardRef refactor.The Separator component now correctly forwards refs to the underlying Radix primitive. The simplified className logic improves readability while maintaining the same visual behavior. This follows shadcn UI conventions.
editor/grida-canvas-hosted/playground/uxhost-menu.tsx (2)
207-254: LGTM! Well-organized menu structure.The refactor cleanly separates menu content into dedicated sub-components (FileMenuContent, EditMenuContent, ViewMenuContent, etc.), improving maintainability and readability. The dynamic
keyboardShortcutTextintegration ensures shortcuts stay in sync with the action registry.
1012-1125: LGTM! Comprehensive arrange menu with proper alignment controls.The ArrangeMenuContent component provides a complete set of alignment and distribution options with correct editor action bindings. Selection state is properly checked before enabling operations.
editor/grida-canvas-hosted/playground/uxhost-actions.ts (3)
1-45: LGTM! Excellent documentation and type-safe design.The fileoverview clearly explains the current state and future vision for this action registry. The
UXHostActioninterface is well-defined, and the deprecated marker oncommandappropriately signals that the command execution system isn't implemented yet.
714-723: LGTM! Good use of platform-specific keybindings.The eye dropper action correctly uses
platformKbto define different keybindings for macOS (I or Ctrl+C) vs Windows/Linux (just I). This is a clean pattern for platform-specific shortcuts.
901-912: LGTM! Type-safe action retrieval.The
UXHostActionIdtype derived fromkeyof typeof actionsensures compile-time safety when referencing actions. ThegetActionhelper provides a clean API for runtime retrieval.editor/components/ui/label.tsx (1)
9-24: Label refactor toforwardRef+ CVA is cleanThe new
Labelimplementation correctly wrapsLabelPrimitive.RootwithforwardRef, centralizes styling vialabelVariants, and keeps the displayName intact. No issues from a typing or runtime perspective.editor/grida-canvas/editor.ts (1)
3613-3618: ExposeswapFillAndStrokeconsistently via Editor and a11y surfaceThe new
Editor.swapFillAndStrokeandEditorSurface.a11ySwapFillAndStrokewrappers correctly delegate down toEditorDocumentStore.swapFillAndStrokeand respect theensureStrokeflag. This gives both programmatic and a11y layers a consistent API for fill/stroke swapping without duplicating paint resolution logic.Also applies to: 5319-5325
| filteredActions.map((action) => ( | ||
| <KeyboardShortcutRow | ||
| key={action.command} | ||
| action={action} | ||
| selectedPlatform={selectedPlatform} | ||
| /> | ||
| )) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using action ID instead of command as key.
Using action.command as the React key may cause issues since multiple actions have command: "todo!()" for unimplemented features (visible in uxhost-actions.ts). This would result in duplicate keys.
Consider using the action's unique identifier or index instead:
filteredActions.map((action) => (
<KeyboardShortcutRow
- key={action.command}
+ key={action.name}
action={action}
selectedPlatform={selectedPlatform}
/>Or better, iterate with action ID from the original actions object.
📝 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.
| filteredActions.map((action) => ( | |
| <KeyboardShortcutRow | |
| key={action.command} | |
| action={action} | |
| selectedPlatform={selectedPlatform} | |
| /> | |
| )) | |
| filteredActions.map((action) => ( | |
| <KeyboardShortcutRow | |
| key={action.name} | |
| action={action} | |
| selectedPlatform={selectedPlatform} | |
| /> | |
| )) |
🤖 Prompt for AI Agents
In editor/grida-canvas-hosted/playground/uxhost-settings-keyboardshortcuts.tsx
around lines 178 to 184, using action.command as the React key can produce
duplicates because many unimplemented actions share command "todo!()"; replace
it with a stable unique identifier (preferably action.id from the original
actions collection) so each KeyboardShortcutRow gets a unique, stable key—if
action.id is not present, fall back to a composed unique string (e.g., index or
a combination of action properties) but prefer the original action ID for
stability.
| /** | ||
| * Get the first keybinding as a string for an action | ||
| * Returns empty string if not found | ||
| * | ||
| * @throws {Error} If actionId is not a valid action ID | ||
| */ | ||
| export function keyboardShortcutText( | ||
| actionId: UXHostActionId, | ||
| platform?: "mac" | "windows" | "linux" | ||
| ): string { | ||
| const sequences = getResolvedSequences(actionId, platform); | ||
| if (sequences.length === 0) { | ||
| return ""; | ||
| } | ||
|
|
||
| const targetPlatform = platform || getKeyboardOS(); | ||
| return sequenceToString(sequences[0], targetPlatform); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect JSDoc: function doesn't throw.
The JSDoc states @throws {Error} If actionId is not a valid action ID, but the function actually returns an empty string when the action is not found (via getResolvedSequences returning []). Either update the JSDoc or add the throw behavior.
/**
* Get the first keybinding as a string for an action
* Returns empty string if not found
- *
- * @throws {Error} If actionId is not a valid action ID
*/
export function keyboardShortcutText(🤖 Prompt for AI Agents
In editor/grida-canvas-hosted/playground/uxhost-shortcut-renderer.tsx around
lines 49 to 66, the JSDoc incorrectly states the function throws an Error for
invalid actionId while the implementation returns an empty string when no
sequences are found; update the JSDoc to remove the @throws tag and document
that the function returns an empty string if the actionId has no keybinding, or
alternatively change the implementation to throw an Error when sequences.length
=== 0 (choose one approach and apply consistently across similar functions), and
ensure the JSDoc matches the chosen behavior.
| function isApplePlatform(): boolean { | ||
| const platform = typeof navigator === "object" ? navigator.platform : ""; | ||
| return /Mac|iPod|iPhone|iPad/.test(platform); | ||
| } | ||
|
|
||
| /** | ||
| * Detect the current keyboard OS | ||
| * this is not reliable for general platform/os detection, but good enough for keyboard OS detection | ||
| * | ||
| * mostly to determine cmdctrl key (and for cases that keybindings are fundamentally different, e.g. ctrl+c being color picker on mac, but copy on windows/linux) | ||
| */ | ||
| export function getKeyboardOS(): "mac" | "windows" | "linux" { | ||
| if (isApplePlatform()) return "mac"; | ||
| const platform = navigator.platform.toLowerCase(); | ||
| if (platform.includes("win")) return "windows"; | ||
| return "linux"; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make getKeyboardOS safe in non‑browser / SSR environments
getKeyboardOS() unconditionally accesses navigator.platform, which will throw (ReferenceError: navigator is not defined) if this code is ever executed during SSR or in a non‑DOM environment. Since keybindingsToKeyCodes() defaults to calling getKeyboardOS() when platform is omitted, any server‑side usage will break.
Consider short‑circuiting for non‑browser contexts:
export function getKeyboardOS(): "mac" | "windows" | "linux" {
if (typeof navigator === "undefined" || !navigator.platform) {
// Reasonable default for headless/SSR; pick whichever fits your UX best
return "linux";
}
if (isApplePlatform()) return "mac";
const platform = navigator.platform.toLowerCase();
if (platform.includes("win")) return "windows";
return "linux";
}and keep keybindingsToKeyCodes callers passing an explicit platform where possible.
Also applies to: 216-267
| export function uikbdk( | ||
| key: M | KeyCode, | ||
| platform?: "mac" | "windows" | "linux" | ||
| ): string { | ||
| const targetPlatform = platform || getKeyboardOS(); | ||
|
|
||
| // Check if it's a modifier bitmask (M enum values are small bitmasks: 1, 2, 4, 8, 16) | ||
| // KeyCodes are enum values that start from a much higher number | ||
| // We check if it's a valid modifier by trying to resolve it | ||
| // If it resolves to modifier keys, it's a modifier; otherwise treat as KeyCode | ||
| const resolvedMods = resolveMods(key, targetPlatform); | ||
| if ( | ||
| resolvedMods.length > 0 && | ||
| key & (M.Ctrl | M.Shift | M.Alt | M.Meta | M.CtrlCmd) | ||
| ) { | ||
| // It's a modifier bitmask - return the first resolved key's label | ||
| // In practice, most single modifiers resolve to a single key | ||
| return keycodeToPlatformUILabel(resolvedMods[0], targetPlatform); | ||
| } | ||
|
|
||
| // It's a KeyCode | ||
| return keycodeToPlatformUILabel(key as KeyCode, targetPlatform); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uikbdk misclassifies normal KeyCode values as modifiers
uikbdk currently determines whether key is a modifier mask or a KeyCode by:
const resolvedMods = resolveMods(key, targetPlatform);
if (
resolvedMods.length > 0 &&
key & (M.Ctrl | M.Shift | M.Alt | M.Meta | M.CtrlCmd)
) {
// treat as modifier
}This assumes KeyCode values are “much higher numbers” than the small bitmasks in M, but your KeyCode enum starts at 0 and increments (e.g. Backspace = 1, Tab = 2, etc.). As a result, many normal keys (letters, digits, etc.) will share low bits with M.*, resolveMods will return non‑empty, and uikbdk(KeyCode.KeyI) will incorrectly be treated as a modifier and rendered as something like ⌘/Ctrl instead of "I".
You should detect modifier masks explicitly rather than by bit overlap. For example:
export function uikbdk(
key: M | KeyCode,
platform?: "mac" | "windows" | "linux"
): string {
const targetPlatform = platform || getKeyboardOS();
const isSingleModifier =
key === M.Ctrl ||
key === M.Shift ||
key === M.Alt ||
key === M.Meta ||
key === M.CtrlCmd;
if (isSingleModifier) {
const resolvedMods = resolveMods(key, targetPlatform);
if (resolvedMods.length > 0) {
return keycodeToPlatformUILabel(resolvedMods[0], targetPlatform);
}
}
// Treat everything else as a KeyCode
return keycodeToPlatformUILabel(key as KeyCode, targetPlatform);
}This keeps uikbdk(M.CtrlCmd) working while ensuring uikbdk(KeyCode.KeyI) returns the correct per‑platform label.
🤖 Prompt for AI Agents
In editor/grida-canvas/keybinding.ts around lines 382 to 404, uikbdk
misclassifies normal KeyCode values as modifier masks by checking bit overlap;
change the logic to explicitly detect single modifier constants (M.Ctrl,
M.Shift, M.Alt, M.Meta, M.CtrlCmd) and only call resolveMods/return a modifier
label for those exact matches, otherwise always treat the input as a KeyCode and
return keycodeToPlatformUILabel(key as KeyCode, targetPlatform).
Main Menu
File
File > Open .gridaFile > Save as .gridaFile > Import ImageFile > Import FigmaEdit
Edit > UndoEdit > RedoEdit > CutEdit > CopyEdit > PasteEdit > Copy as PNGEdit > Copy as SVGEdit > Pick colorEdit > DuplicateEdit > DeleteText
Text > BoldText > ItalicText > UnderlineText > StrikethroughText > Create linkText > Bulleted listText > Numbered listText > Alignment > Text align leftText > Alignment > Text align centerText > Alignment > Text align rightText > Alignment > Text align justifiedText > Alignment > Text align topText > Alignment > Text align middleText > Alignment > Text align bottomText > Adjust > Increase indentationText > Adjust > Decrease indentationText > Adjust > Increase font sizeText > Adjust > Decrease font sizeText > Adjust > Increase font weightText > Adjust > Decrease font weightText > Adjust > Increase line heightText > Adjust > Decrease line heightText > Adjust > Increase letter spacingText > Adjust > Decrease letter spacingText > Case > Original caseText > Case > UppercaseText > Case > LowercaseObject
Object > Container selectionObject > Group selectionObject > Ungroup selectionObject > Wrap in new sectionObject > Convert to sectionObject > Convert to containerObject > Use as maskObject > Restore default thumbnailObject > Add layoutObject > Create arcObject > More layout options > Suggest layoutObject > More layout options > Remove all layoutObject > More layout options > Lock aspect ratioObject > More layout options > Unlock aspect ratioObject > More layout options > Resize to fitObject > More layout options > Set width to hug contentsObject > More layout options > Set height to hug contentsObject > More layout options > Set width to fill containerObject > More layout options > Set height to fill containerObject > Create componentObject > Reset instanceObject > Detach instanceObject > Main component > Go to main componentObject > Main component > Push changes to main componentObject > Main component > Restore main componentObject > Bring to frontObject > Bring forwardObject > Send backwardObject > Send to backObject > Flip horizontalObject > Flip verticalObject > Rotate 180°Object > Rotate 90° leftObject > Rotate 90° rightObject > FlattenObject > Outline strokeObject > Boolean groups > UnionObject > Boolean groups > SubtractObject > Boolean groups > IntersectObject > Boolean groups > ExcludeObject > Rasterize selectionObject > Show/Hide selectionObject > Lock/Unlock selectionObject > Hide other layersObject > Collapse layersObject > Remove fillObject > Remove strokeObject > Swap fill and strokeArrange
Arrange > Round to pixelArrange > Align leftArrange > Align horizontal centersArrange > Align rightArrange > Align topArrange > Align vertical centersArrange > Align bottomArrange > Tidy upArrange > Pack horizontalArrange > Pack verticalArrange > Distribute horizontal spacingArrange > Distribute vertical spacingArrange > Distribute leftArrange > Distribute horizontal centersArrange > Distribute rightArrange > Distribute topArrange > Distribute vertical centersArrange > Distribute bottomView
View > Zoom inView > Zoom outView > Zoom to 100%View > Zoom to fitView > Zoom to selectionView > Pixel gridView > RulerView > Show/Hide UIView > Minimize UISettings
Settings > GeneralSettings > Keyboard shortcutsSummary by CodeRabbit
Release Notes
New Features
Documentation
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.