From 19bb3430e5a4a668f12a1d390f3428917d4b100c Mon Sep 17 00:00:00 2001 From: Molecule AI Core-FE Date: Sat, 9 May 2026 22:52:33 +0000 Subject: [PATCH 1/2] feat(canvas): keyboard-accessible edge anchors via Enter/Space MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Target handle (top of card): Enter/Space extracts this node from its parent, moving it to the root level. Source handle (bottom of card): Enter/Space nests the currently selected node as a child of this node (requires another node to be selected first). Both handles gain tabIndex=0, role="button", a descriptive aria-label, and a blue focus ring so keyboard-only users can navigate the workspace hierarchy without a mouse. Uses the existing nestNode store action — no new API surface needed. Updates the canvas audit doc to mark the LOW edge-anchor item done. Co-Authored-By: Claude Opus 4.7 --- canvas/src/components/WorkspaceNode.tsx | 38 ++++++++++++++++++++++-- docs/design-system/canvas-audit-items.md | 8 ++--- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/canvas/src/components/WorkspaceNode.tsx b/canvas/src/components/WorkspaceNode.tsx index 2579d8fb..d20b8bbd 100644 --- a/canvas/src/components/WorkspaceNode.tsx +++ b/canvas/src/components/WorkspaceNode.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useMemo } from "react"; +import { useCallback, useMemo, type KeyboardEvent } from "react"; import { Handle, NodeResizer, Position, type NodeProps, type Node } from "@xyflow/react"; import { useCanvasStore, type WorkspaceNodeData } from "@/store/canvas"; import { getConfigurationError, getConfigurationStatus } from "@/store/canvas-topology"; @@ -191,7 +191,23 @@ export function WorkspaceNode({ id, data }: NodeProps>) ) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + e.stopPropagation(); + // Keyboard accessibility for edge anchors: pressing Enter/Space on + // the top handle extracts this node from its current parent, + // moving it to the root level. Mirrors the Figma/Excalidraw + // pattern of using the connector dot as a keyboard affordance. + if (data.parentId) { + void nestNode(id, null); + } + } + }} + className="!w-2.5 !h-1 !rounded-full !bg-surface-card/80 !border-0 !-top-0.5 hover:!bg-blue-400 hover:!h-1.5 focus-visible:!bg-blue-400 focus-visible:!h-1.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-400/60 focus-visible:ring-offset-1 focus-visible:ring-offset-zinc-950 transition-all" />
@@ -358,7 +374,23 @@ export function WorkspaceNode({ id, data }: NodeProps>) ) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + e.stopPropagation(); + // Keyboard accessibility for edge anchors: pressing Enter/Space on + // the bottom handle nests the currently-selected node as a child + // of this node. Requires another node to be selected first. + const selected = selectedNodeId; + if (selected && selected !== id) { + void nestNode(selected, id); + } + } + }} + className="!w-2.5 !h-1 !rounded-full !bg-surface-card/80 !border-0 !-bottom-0.5 hover:!bg-blue-400 hover:!h-1.5 focus-visible:!bg-blue-400 focus-visible:!h-1.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-400/60 focus-visible:ring-offset-1 focus-visible:ring-offset-zinc-950 transition-all" />
diff --git a/docs/design-system/canvas-audit-items.md b/docs/design-system/canvas-audit-items.md index 70641468..42414146 100644 --- a/docs/design-system/canvas-audit-items.md +++ b/docs/design-system/canvas-audit-items.md @@ -62,9 +62,9 @@ canvas/src/ ### Edge Wiring ✅ - **Edge rendering:** React Flow SVG paths - **Edge click target:** 1.5px stroke (CSS `stroke-width: 1.5 !important` in globals.css) -- **Edge creation:** React Flow drag-from-handle -- **Edge anchors:** Visible on hover (`hover:!bg-blue-400`), not keyboard accessible -- **Status:** Partial — mouse users only +- **Edge creation:** React Flow drag-from-handle (mouse); keyboard via handle Enter/Space +- **Edge anchors:** Target handle (top): `Enter/Space` extracts node from parent. Source handle (bottom): `Enter/Space` nests selected node into this node. Both have `tabIndex=0`, `role="button"`, descriptive `aria-label`, and a blue focus ring ✅ +- **Status:** Mouse + keyboard — keyboard users can nest and un-nest without a mouse ### Canvas Controls ✅ - **Zoom:** React Flow Controls component (verify if keyboard accessible) @@ -111,7 +111,7 @@ canvas/src/ | ~~HIGH~~ | ~~Screen reader announcements for canvas state changes~~ | ~~Canvas.tsx, canvas-events.ts, canvas.ts~~ | ✅ Done — PR #172 | | MEDIUM | Keyboard shortcut help dialog | useKeyboardShortcuts.ts | ✅ Done (PR #175) | | MEDIUM | Keyboard-accessible node drag | WorkspaceNode.tsx, useDragHandlers.ts | ✅ Done (this PR) | -| LOW | Edge anchor keyboard accessibility | A2AEdge.tsx | Not started | +| LOW | Keyboard-accessible edge anchors | A2AEdge.tsx, WorkspaceNode.tsx | ✅ Done | | LOW | Node resize keyboard accessibility | WorkspaceNode.tsx (NodeResizer) | Not started | --- From 4e69b88d8275c271fa6fb69bb1bf583561ed4407 Mon Sep 17 00:00:00 2001 From: Molecule AI Core Platform Lead Date: Sat, 9 May 2026 22:58:09 +0000 Subject: [PATCH 2/2] trigger: re-run sop-tier-check after core-lead approval + main sync