Skip to content

Ci/add dependabot config - #90

Open
Myparadox-creator wants to merge 2 commits into
itzzavdhesh:mainfrom
Myparadox-creator:ci/add-dependabot-config
Open

Ci/add dependabot config#90
Myparadox-creator wants to merge 2 commits into
itzzavdhesh:mainfrom
Myparadox-creator:ci/add-dependabot-config

Conversation

@Myparadox-creator

@Myparadox-creator Myparadox-creator commented Aug 3, 2026

Copy link
Copy Markdown

Description

This PR adds a .github/dependabot.yml configuration file to enable automated weekly dependency updates across the repository.

Why this change?
Keeping dependencies up to date manually can lead to missed security patches and technical debt over time. Adding Dependabot ensures that both npm packages and github-actions workflow versions are routinely monitored and updated with minimal maintenance overhead.

Summary of changes:

  • Added .github/dependabot.yml v2 configuration.
  • Configured weekly update schedules for npm packages and github-actions.
  • Set automated labeling (dependencies, javascript, github-actions) and a maximum limit of 10 open PRs.

Related Issue

Closes #

Type of Change

  • 🐛 Bug fix (non-breaking change that resolves an issue)
  • 🚀 New feature (non-breaking change that adds functionality)
  • ⚠️ Breaking change (fix or feature that changes existing behavior)
  • 📝 Documentation update
  • 🎨 Style / UI improvement (no logic changes)
  • ♻️ Refactor (code restructure with no behavior change)
  • ✅ Test addition or improvement
  • 🔧 Build / config / tooling change

Testing Performed

  • npm install && npm run dev starts without errors
  • npm run lint passes with no TypeScript errors
  • Manual testing steps performed:

Summary by cubic

Adds Dependabot for weekly dependency updates and introduces keyboard shortcuts with an in‑app help modal and a unified Export menu to speed up flow editing. Also tightens toolbar overflow and layering to prevent UI overlap.

  • New Features

    • Added useKeyboardShortcuts (Delete/Backspace, Ctrl/Cmd+A, Ctrl/Cmd+D, Ctrl/Cmd+S, Tab/Shift+Tab, Arrow keys, Escape) with a "Shortcuts" button and Shift+? help modal.
    • Unified Export dropdown for PNG, PDF, PPTX, and JSON; minor toolbar/overflow tweaks for a cleaner header.
  • Dependencies

    • Added .github/dependabot.yml to run weekly updates for npm and github-actions, apply labels, and cap to 10 open PRs.

Written for commit b06d6a6. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added keyboard shortcuts for common editing, navigation, selection, saving, and deletion actions.
    • Added block duplication with automatic placement, selection, and connection updates.
    • Added a keyboard-shortcuts reference modal.
    • Consolidated PNG, PDF, PPTX, and JSON export options into a single menu.
    • Improved toolbar responsiveness with horizontal scrolling and compact controls.
  • Improvements
    • Added clearer success notifications and more reliable canvas interactions.

@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

@Myparadox-creator is attempting to deploy a commit to the itzzavdhesh's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

⚠️ This PR doesn't seem to reference an issue yet. Please edit the description to add a line like:

Closes #12

This links your work to the issue and auto-closes it once merged — it's also how ELUSOC tracks contributions.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds global keyboard shortcuts, graph-aware navigation, block duplication, shortcut help, and a consolidated export menu. It also updates canvas layout styling, sidebar stacking, and adds weekly Dependabot updates for npm packages and GitHub Actions.

Changes

Editor interactions

Layer / File(s) Summary
Keyboard shortcut engine
src/hooks/useKeyboardShortcuts.ts
Adds global handling for editing, navigation, saving, help, input suppression, and listener cleanup.
Application integration and duplication
src/App.tsx
Connects shortcut callbacks and duplicates blocks with generated IDs, copied labels, target rewiring, selection, and activation.
Canvas toolbar, exports, and shortcut help
src/components/CenterCanvas.tsx, src/components/RightSidebar.tsx
Adds shortcut-help controls and modal content, consolidates export actions into a dropdown, updates responsive toolbar layout, sets minimum canvas dimensions, and raises sidebar stacking.

Dependency update automation

Layer / File(s) Summary
Dependabot update configuration
.github/dependabot.yml
Configures weekly npm and GitHub Actions updates with a maximum of 10 open pull requests per ecosystem and ecosystem-specific labels.

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

Possibly related issues

  • itzzavdhesh/FlowCraft#82 — Adds the Dependabot configuration described by the issue.

Possibly related PRs

  • itzzavdhesh/FlowCraft#88 — Contains the same keyboard shortcut, duplication, toolbar, export, sidebar, and shortcut-help changes.

Suggested labels: enhancement, ELUSOC, VETERAN, status: in-progress

Suggested reviewers: dev1822

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers Dependabot but omits the keyboard shortcut and toolbar changes, leaves manual testing incomplete, and omits the required checklist. Update the description to cover all changes, complete the manual testing steps, add applicable screenshots, and complete the required checklist.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title identifies the Dependabot configuration, which is a real pull request objective, but it omits the keyboard shortcut and toolbar changes.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/App.tsx (1)

320-330: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Memoize the handlers passed into useKeyboardShortcuts.

handleDeleteBlock, handleDuplicateBlock, handleSaveWorkspace, and the inline onToggleShortcutsHelp arrow function are all re-created on every render of App. Since useKeyboardShortcuts lists these in its useEffect dependency array, the hook's window listener is removed and re-attached on every App render, not just when the underlying logic changes.

Line 523 also creates a second, separate inline function with the same body as line 328. Wrap the handlers in useCallback and share a single toggle function between both call sites.

const toggleShortcutsHelp = useCallback(() => setShowShortcutsHelp((prev) => !prev), []);

Reuse toggleShortcutsHelp at both line 328 and line 523, and wrap handleDeleteBlock, handleDuplicateBlock, and handleSaveWorkspace similarly.

Also applies to: 522-523

🤖 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/App.tsx` around lines 320 - 330, Memoize the handlers used by
useKeyboardShortcuts to prevent unnecessary listener re-registration. Wrap
handleDeleteBlock, handleDuplicateBlock, and handleSaveWorkspace in useCallback,
create one shared toggleShortcutsHelp callback using the existing state updater,
and reuse it both in the hook options and the second call site around the other
referenced location.

Source: Linters/SAST tools

🤖 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 `@src/App.tsx`:
- Around line 256-286: Update handleDuplicateBlock so the setBlocks updater
derives the source block from prev and constructs a fresh duplicatedBlock
entirely inside the updater. Avoid mutating the block created outside the
callback or relying on the outer original value; preserve insertion after the
source block, targetId handling, and existing selection/toast behavior.

In `@src/hooks/useKeyboardShortcuts.ts`:
- Around line 30-47: The useKeyboardShortcuts handler must account for open
modals: pass an isModalOpen state derived from showConfirmModal or
showShortcutsHelp, route Escape to close the active modal before clearing
selection, and short-circuit destructive or duplication shortcuts such as
Delete/Backspace and Ctrl+D while a modal is open. Update the
useKeyboardShortcuts API and its App.tsx/CenterCanvas.tsx callers while
preserving normal shortcut behavior when no modal is active.
- Around line 93-167: Broaden the focus guard in isInput so keyboard
interception is skipped for all interactive controls, including buttons, links,
and other natively focusable elements, not only form fields and contentEditable
nodes. Apply this guard before both the Tab cycling block and the Arrow-key
navigation block, preserving custom navigation only when focus is outside
interactive controls.

---

Nitpick comments:
In `@src/App.tsx`:
- Around line 320-330: Memoize the handlers used by useKeyboardShortcuts to
prevent unnecessary listener re-registration. Wrap handleDeleteBlock,
handleDuplicateBlock, and handleSaveWorkspace in useCallback, create one shared
toggleShortcutsHelp callback using the existing state updater, and reuse it both
in the hook options and the second call site around the other referenced
location.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d36aac87-b9ba-4644-9aee-422f4443db51

📥 Commits

Reviewing files that changed from the base of the PR and between dba65fd and b06d6a6.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • .github/dependabot.yml
  • src/App.tsx
  • src/components/CenterCanvas.tsx
  • src/components/RightSidebar.tsx
  • src/hooks/useKeyboardShortcuts.ts

Comment thread src/App.tsx
Comment on lines +256 to +286
// Duplicate block (Ctrl+D / Cmd+D)
const handleDuplicateBlock = (id: string) => {
const original = blocks.find((b) => b.id === id);
if (!original) return;

const newId = `block-${Math.random().toString(36).substring(2, 9)}`;
const duplicatedBlock: Block = {
...original,
id: newId,
label: `${original.label} (Copy)`,
};

setBlocks((prev) => {
const idx = prev.findIndex((b) => b.id === id);
if (idx === -1) return [...prev, duplicatedBlock];

const updated = [...prev];
if (original.type !== 'decision') {
duplicatedBlock.targetId = original.targetId;
updated[idx] = { ...original, targetId: newId };
}

updated.splice(idx + 1, 0, duplicatedBlock);
return updated;
});

setSelectedBlockId(newId);
setActiveParentId(newId);
showToast(`Duplicated "${original.label}"`, 'success');
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Compute the duplicate entirely from prev inside the updater.

duplicatedBlock is created outside setBlocks and then mutated at line 274 inside the updater callback. React may invoke a state updater more than once (for example under Strict Mode double-invocation or concurrent re-renders), and updaters should be pure functions of prev. Reading original from the outer closure also means the block could be stale if state changed between the initial blocks.find and the update.

🔧 Proposed fix
     const newId = `block-${Math.random().toString(36).substring(2, 9)}`;
-    const duplicatedBlock: Block = {
-      ...original,
-      id: newId,
-      label: `${original.label} (Copy)`,
-    };
 
     setBlocks((prev) => {
       const idx = prev.findIndex((b) => b.id === id);
-      if (idx === -1) return [...prev, duplicatedBlock];
+      if (idx === -1) return prev;
 
+      const original = prev[idx];
+      const duplicatedBlock: Block = {
+        ...original,
+        id: newId,
+        label: `${original.label} (Copy)`,
+      };
       const updated = [...prev];
       if (original.type !== 'decision') {
-        duplicatedBlock.targetId = original.targetId;
         updated[idx] = { ...original, targetId: newId };
       }
 
       updated.splice(idx + 1, 0, duplicatedBlock);
       return updated;
     });
📝 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
// Duplicate block (Ctrl+D / Cmd+D)
const handleDuplicateBlock = (id: string) => {
const original = blocks.find((b) => b.id === id);
if (!original) return;
const newId = `block-${Math.random().toString(36).substring(2, 9)}`;
const duplicatedBlock: Block = {
...original,
id: newId,
label: `${original.label} (Copy)`,
};
setBlocks((prev) => {
const idx = prev.findIndex((b) => b.id === id);
if (idx === -1) return [...prev, duplicatedBlock];
const updated = [...prev];
if (original.type !== 'decision') {
duplicatedBlock.targetId = original.targetId;
updated[idx] = { ...original, targetId: newId };
}
updated.splice(idx + 1, 0, duplicatedBlock);
return updated;
});
setSelectedBlockId(newId);
setActiveParentId(newId);
showToast(`Duplicated "${original.label}"`, 'success');
};
// Duplicate block (Ctrl+D / Cmd+D)
const handleDuplicateBlock = (id: string) => {
const original = blocks.find((b) => b.id === id);
if (!original) return;
const newId = `block-${Math.random().toString(36).substring(2, 9)}`;
setBlocks((prev) => {
const idx = prev.findIndex((b) => b.id === id);
if (idx === -1) return prev;
const original = prev[idx];
const duplicatedBlock: Block = {
...original,
id: newId,
label: `${original.label} (Copy)`,
};
const updated = [...prev];
if (original.type !== 'decision') {
updated[idx] = { ...original, targetId: newId };
}
updated.splice(idx + 1, 0, duplicatedBlock);
return updated;
});
setSelectedBlockId(newId);
setActiveParentId(newId);
showToast(`Duplicated "${original.label}"`, 'success');
};
🧰 Tools
🪛 React Doctor (0.9.1)

[error] 268-268: This state updater performs the captured value "duplicatedBlock". React may run updater functions more than once, so side effects here can repeat or observe inconsistent external state.

Keep state updater callbacks pure and return only the next state. Move notifications, storage, timers, ref writes, and other external work into the event or effect that queues the update.

(no-impure-state-updater)


[error] 274-274: This side-effecting call runs inside a state updater, which React may invoke more than once. Move it outside the setter after computing the next state.

React may replay a state updater, so callbacks, analytics, and persistence inside it can run more than once. Compute state purely, then perform the side effect outside the setter.

(no-side-effect-in-state-updater-function)

🤖 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/App.tsx` around lines 256 - 286, Update handleDuplicateBlock so the
setBlocks updater derives the source block from prev and constructs a fresh
duplicatedBlock entirely inside the updater. Avoid mutating the block created
outside the callback or relying on the outer original value; preserve insertion
after the source block, targetId handling, and existing selection/toast
behavior.

Source: Linters/SAST tools

Comment on lines +30 to +47
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
const target = e.target as HTMLElement | null;
const isInput =
target &&
(target.tagName === 'INPUT' ||
target.tagName === 'TEXTAREA' ||
target.tagName === 'SELECT' ||
target.isContentEditable);

// Escape works everywhere to clear selection or blur input
if (e.key === 'Escape') {
if (isInput) {
target.blur();
}
onSelectBlock(null);
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Give the handler awareness of open modals.

handleKeyDown has no knowledge of showConfirmModal or showShortcutsHelp from App.tsx/CenterCanvas.tsx. Two consequences follow:

  • Escape (lines 41-47) only calls onSelectBlock(null). It never closes the Keyboard Shortcuts modal or the New Flowchart confirmation dialog. A user who presses Escape expecting to dismiss the open modal instead silently clears the block selection.
  • Delete/Backspace (lines 85-91) and Ctrl+D duplicate (lines 76-82) still fire while a modal is open. A user can delete or duplicate the currently selected block while only intending to interact with the confirmation dialog behind it.

Pass the relevant modal-open state (for example isModalOpen: showConfirmModal || showShortcutsHelp) into useKeyboardShortcuts and short-circuit most shortcuts when a modal is open, while routing Escape to close the active modal instead of clearing selection.

Also applies to: 75-91

🤖 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/hooks/useKeyboardShortcuts.ts` around lines 30 - 47, The
useKeyboardShortcuts handler must account for open modals: pass an isModalOpen
state derived from showConfirmModal or showShortcutsHelp, route Escape to close
the active modal before clearing selection, and short-circuit destructive or
duplication shortcuts such as Delete/Backspace and Ctrl+D while a modal is open.
Update the useKeyboardShortcuts API and its App.tsx/CenterCanvas.tsx callers
while preserving normal shortcut behavior when no modal is active.

Comment on lines +93 to +167
// Tab / Shift+Tab: Cycle through blocks
if (e.key === 'Tab') {
if (blocks.length === 0) return;
e.preventDefault();

const currentIndex = blocks.findIndex((b) => b.id === selectedBlockId);
if (currentIndex === -1) {
onSelectBlock(blocks[0].id);
} else {
let nextIndex: number;
if (e.shiftKey) {
nextIndex = (currentIndex - 1 + blocks.length) % blocks.length;
} else {
nextIndex = (currentIndex + 1) % blocks.length;
}
onSelectBlock(blocks[nextIndex].id);
}
return;
}

// Arrow Keys: Smart Graph & Diagram Navigation
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
if (blocks.length === 0) return;
e.preventDefault();

if (!selectedBlockId) {
onSelectBlock(blocks[0].id);
return;
}

const currentBlock = blocks.find((b) => b.id === selectedBlockId);
if (!currentBlock) return;

if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
// Move to downstream target
if (currentBlock.type === 'decision') {
const nextId =
e.key === 'ArrowRight'
? currentBlock.yesTargetId || currentBlock.noTargetId
: currentBlock.noTargetId || currentBlock.yesTargetId;
if (nextId) {
onSelectBlock(nextId);
return;
}
} else if (currentBlock.targetId) {
onSelectBlock(currentBlock.targetId);
return;
}

// Fallback to next block in array if no explicit graph link
const idx = blocks.findIndex((b) => b.id === selectedBlockId);
if (idx !== -1 && idx < blocks.length - 1) {
onSelectBlock(blocks[idx + 1].id);
}
} else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
// Move to upstream parent node (node that points to currentBlock.id)
const parent = blocks.find(
(b) =>
b.targetId === currentBlock.id ||
b.yesTargetId === currentBlock.id ||
b.noTargetId === currentBlock.id
);

if (parent) {
onSelectBlock(parent.id);
return;
}

// Fallback to previous block in array
const idx = blocks.findIndex((b) => b.id === selectedBlockId);
if (idx > 0) {
onSelectBlock(blocks[idx - 1].id);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Tab (and Arrow) key hijack breaks native focus navigation.

isInput only recognizes INPUT, TEXTAREA, SELECT, and contentEditable elements. Any other focused element, including a <button>, is treated as "not typing" and falls through to the Tab handler at line 94, which calls e.preventDefault() whenever blocks.length > 0.

Native Tab-out-of-a-field works only while focus is still inside a form field, because the isInput check at line 64 returns before reaching this block. Once a user tabs from a field onto any <button> or other non-form element, every subsequent Tab press is intercepted by this handler instead of moving focus to the next control. Keyboard-only and screen-reader users then cannot reach the toolbar, the export dropdown, or the sidebar controls in CenterCanvas.tsx and RightSidebar.tsx — this is a partial keyboard trap.

The same gap affects Arrow keys (lines 113-167).

Scope the interception so it does not apply when focus is on an interactive control:

🔧 Proposed fix
       // Tab / Shift+Tab: Cycle through blocks
       if (e.key === 'Tab') {
         if (blocks.length === 0) return;
+        // Do not hijack Tab when focus is on an interactive control;
+        // otherwise keyboard users cannot Tab past the first button.
+        const isFocusableControl =
+          target &&
+          (target.tagName === 'BUTTON' ||
+            target.tagName === 'A' ||
+            target.hasAttribute('tabindex'));
+        if (isFocusableControl) return;
         e.preventDefault();
🤖 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/hooks/useKeyboardShortcuts.ts` around lines 93 - 167, Broaden the focus
guard in isInput so keyboard interception is skipped for all interactive
controls, including buttons, links, and other natively focusable elements, not
only form fields and contentEditable nodes. Apply this guard before both the Tab
cycling block and the Arrow-key navigation block, preserving custom navigation
only when focus is outside interactive controls.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

7 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/components/CenterCanvas.tsx">

<violation number="1" location="src/components/CenterCanvas.tsx:408">
P2: Export dropdown is clipped below the 64px toolbar, so its PNG/PDF/PPTX/JSON choices cannot be clicked. Render the menu outside the horizontally scrolling header (or use a portal) so horizontal scrolling does not create a vertical clipping container.</violation>

<violation number="2" location="src/components/CenterCanvas.tsx:928">
P2: Opening Shortcuts Help leaves diagram keyboard commands active and prevents Tab from reaching its close controls; Delete/arrow keys can modify or navigate the underlying diagram. Suspend diagram shortcuts while this dialog is open, handle Escape to close it, and place focus inside the dialog.</violation>
</file>

<file name="src/hooks/useKeyboardShortcuts.ts">

<violation number="1" location="src/hooks/useKeyboardShortcuts.ts:76">
P2: Duplicating a selected block also triggers the browser bookmark shortcut because this handler never cancels the key event. Prevent the default before duplicating so Ctrl/Cmd+D remains an app-only shortcut.</violation>

<violation number="2" location="src/hooks/useKeyboardShortcuts.ts:94">
P2: Once any block exists (blocks.length > 0, which is almost always), this global window keydown handler calls e.preventDefault() on every Tab / Shift+Tab keystroke anywhere in the document and uses it to cycle block selection instead. The isInput guard only exempts INPUT/TEXTAREA/SELECT/contentEditable, so focus can never be moved between the toolbar/sidebar buttons and links with the keyboard, breaking keyboard operability (WCAG) across the whole app. The arrow-key handler similarly hijacks all arrow keys globally. Consider restricting these navigation shortcuts to when focus is actually on the canvas (e.g. canvas wrapper focused) and not over any focusable interactive element, or skip them when the event target is a button/link.</violation>
</file>

<file name=".github/dependabot.yml">

<violation number="1" location=".github/dependabot.yml:13">
P3: The automated labeling this config claims to set up won't take effect: Dependabot only applies labels that already exist in the repository, and none of `dependencies`, `javascript`, or `github-actions` are defined in `.github/labels.yml` (the manifest synced to GitHub by `label-sync.yml`). Consider adding these labels to `.github/labels.yml` (e.g. `dependencies`, `javascript`, `github-actions` with a color/description each), or pointing `labels` at existing labels, so Dependabot PRs are actually tagged as intended.</violation>
</file>

<file name="src/App.tsx">

<violation number="1" location="src/App.tsx:268">
P2: `duplicatedBlock` is built from the outer `original` (captured via closure) and then mutated inside the `setBlocks` updater. State updaters should be pure and derive all data from `prev`; since React can invoke updaters more than once, compute `original` and `duplicatedBlock` from `prev` inside the updater instead of relying on the outer closure to avoid stale-state duplication bugs.</violation>

<violation number="2" location="src/App.tsx:273">
P2: Duplicating a decision block leaves the copy disconnected from the graph. The non-decision branch rewires the original to point at the new copy (`updated[idx] = { ...original, targetId: newId }`), but for decision nodes that branch is skipped, so the copy keeps the original's yesTargetId/noTargetId while no node points back to it. The copy still renders on the canvas but is unreachable via the new arrow-key graph navigation and behaves inconsistently with the non-decision duplicate. Consider rewiring the copied decision node's incoming parent(s) to the copy as well so the duplicate is actually part of the diagram.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

)}

{/* Keyboard Shortcuts Help Modal */}
{showShortcutsHelp && (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Opening Shortcuts Help leaves diagram keyboard commands active and prevents Tab from reaching its close controls; Delete/arrow keys can modify or navigate the underlying diagram. Suspend diagram shortcuts while this dialog is open, handle Escape to close it, and place focus inside the dialog.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/CenterCanvas.tsx, line 928:

<comment>Opening Shortcuts Help leaves diagram keyboard commands active and prevents Tab from reaching its close controls; Delete/arrow keys can modify or navigate the underlying diagram. Suspend diagram shortcuts while this dialog is open, handle Escape to close it, and place focus inside the dialog.</comment>

<file context>
@@ -878,6 +923,60 @@ export default function CenterCanvas({
       )}
+
+      {/* Keyboard Shortcuts Help Modal */}
+      {showShortcutsHelp && (
+        <div className="fixed inset-0 bg-slate-900/50 backdrop-blur-xs z-50 flex items-center justify-center p-4 animate-fade-in">
+          <div className="bg-white dark:bg-slate-800 rounded-2xl shadow-2xl border border-gray-100 dark:border-slate-700 max-w-md w-full p-6 transform transition-all scale-100">
</file context>

{/* Top Toolbar */}
<header className="h-[64px] bg-white dark:bg-slate-800 border-b border-gray-100 dark:border-slate-700 shadow-xs px-6 flex items-center justify-between shrink-0 select-none z-10">
<div className="flex items-center gap-2">
<header className="h-[64px] bg-white dark:bg-slate-800 border-b border-gray-100 dark:border-slate-700 shadow-xs px-4 flex items-center justify-between shrink-0 select-none z-10 overflow-x-auto overflow-y-visible custom-scrollbar flex-nowrap min-w-0 max-w-full">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Export dropdown is clipped below the 64px toolbar, so its PNG/PDF/PPTX/JSON choices cannot be clicked. Render the menu outside the horizontally scrolling header (or use a portal) so horizontal scrolling does not create a vertical clipping container.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/CenterCanvas.tsx, line 408:

<comment>Export dropdown is clipped below the 64px toolbar, so its PNG/PDF/PPTX/JSON choices cannot be clicked. Render the menu outside the horizontally scrolling header (or use a portal) so horizontal scrolling does not create a vertical clipping container.</comment>

<file context>
@@ -383,19 +405,19 @@ export default function CenterCanvas({
       {/* Top Toolbar */}
-      <header className="h-[64px] bg-white dark:bg-slate-800 border-b border-gray-100 dark:border-slate-700 shadow-xs px-6 flex items-center justify-between shrink-0 select-none z-10">
-        <div className="flex items-center gap-2">
+      <header className="h-[64px] bg-white dark:bg-slate-800 border-b border-gray-100 dark:border-slate-700 shadow-xs px-4 flex items-center justify-between shrink-0 select-none z-10 overflow-x-auto overflow-y-visible custom-scrollbar flex-nowrap min-w-0 max-w-full">
+        <div className="flex items-center gap-2 shrink-0">
           <span className="text-xs font-semibold uppercase tracking-wider text-gray-400 dark:text-slate-500">Workspace</span>
</file context>

}

// Duplicate Block: Ctrl + D / Cmd + D
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'd') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Duplicating a selected block also triggers the browser bookmark shortcut because this handler never cancels the key event. Prevent the default before duplicating so Ctrl/Cmd+D remains an app-only shortcut.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/useKeyboardShortcuts.ts, line 76:

<comment>Duplicating a selected block also triggers the browser bookmark shortcut because this handler never cancels the key event. Prevent the default before duplicating so Ctrl/Cmd+D remains an app-only shortcut.</comment>

<file context>
@@ -0,0 +1,184 @@
+      }
+
+      // Duplicate Block: Ctrl + D / Cmd + D
+      if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'd') {
+        e.preventDefault();
+        if (selectedBlockId) {
</file context>

}

// Tab / Shift+Tab: Cycle through blocks
if (e.key === 'Tab') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Once any block exists (blocks.length > 0, which is almost always), this global window keydown handler calls e.preventDefault() on every Tab / Shift+Tab keystroke anywhere in the document and uses it to cycle block selection instead. The isInput guard only exempts INPUT/TEXTAREA/SELECT/contentEditable, so focus can never be moved between the toolbar/sidebar buttons and links with the keyboard, breaking keyboard operability (WCAG) across the whole app. The arrow-key handler similarly hijacks all arrow keys globally. Consider restricting these navigation shortcuts to when focus is actually on the canvas (e.g. canvas wrapper focused) and not over any focusable interactive element, or skip them when the event target is a button/link.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/useKeyboardShortcuts.ts, line 94:

<comment>Once any block exists (blocks.length > 0, which is almost always), this global window keydown handler calls e.preventDefault() on every Tab / Shift+Tab keystroke anywhere in the document and uses it to cycle block selection instead. The isInput guard only exempts INPUT/TEXTAREA/SELECT/contentEditable, so focus can never be moved between the toolbar/sidebar buttons and links with the keyboard, breaking keyboard operability (WCAG) across the whole app. The arrow-key handler similarly hijacks all arrow keys globally. Consider restricting these navigation shortcuts to when focus is actually on the canvas (e.g. canvas wrapper focused) and not over any focusable interactive element, or skip them when the event target is a button/link.</comment>

<file context>
@@ -0,0 +1,184 @@
+      }
+
+      // Tab / Shift+Tab: Cycle through blocks
+      if (e.key === 'Tab') {
+        if (blocks.length === 0) return;
+        e.preventDefault();
</file context>

Comment thread src/App.tsx
if (idx === -1) return [...prev, duplicatedBlock];

const updated = [...prev];
if (original.type !== 'decision') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Duplicating a decision block leaves the copy disconnected from the graph. The non-decision branch rewires the original to point at the new copy (updated[idx] = { ...original, targetId: newId }), but for decision nodes that branch is skipped, so the copy keeps the original's yesTargetId/noTargetId while no node points back to it. The copy still renders on the canvas but is unreachable via the new arrow-key graph navigation and behaves inconsistently with the non-decision duplicate. Consider rewiring the copied decision node's incoming parent(s) to the copy as well so the duplicate is actually part of the diagram.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/App.tsx, line 273:

<comment>Duplicating a decision block leaves the copy disconnected from the graph. The non-decision branch rewires the original to point at the new copy (`updated[idx] = { ...original, targetId: newId }`), but for decision nodes that branch is skipped, so the copy keeps the original's yesTargetId/noTargetId while no node points back to it. The copy still renders on the canvas but is unreachable via the new arrow-key graph navigation and behaves inconsistently with the non-decision duplicate. Consider rewiring the copied decision node's incoming parent(s) to the copy as well so the duplicate is actually part of the diagram.</comment>

<file context>
@@ -251,6 +253,37 @@ export default function App() {
+      if (idx === -1) return [...prev, duplicatedBlock];
+
+      const updated = [...prev];
+      if (original.type !== 'decision') {
+        duplicatedBlock.targetId = original.targetId;
+        updated[idx] = { ...original, targetId: newId };
</file context>

Comment thread src/App.tsx
label: `${original.label} (Copy)`,
};

setBlocks((prev) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: duplicatedBlock is built from the outer original (captured via closure) and then mutated inside the setBlocks updater. State updaters should be pure and derive all data from prev; since React can invoke updaters more than once, compute original and duplicatedBlock from prev inside the updater instead of relying on the outer closure to avoid stale-state duplication bugs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/App.tsx, line 268:

<comment>`duplicatedBlock` is built from the outer `original` (captured via closure) and then mutated inside the `setBlocks` updater. State updaters should be pure and derive all data from `prev`; since React can invoke updaters more than once, compute `original` and `duplicatedBlock` from `prev` inside the updater instead of relying on the outer closure to avoid stale-state duplication bugs.</comment>

<file context>
@@ -251,6 +253,37 @@ export default function App() {
+      label: `${original.label} (Copy)`,
+    };
+
+    setBlocks((prev) => {
+      const idx = prev.findIndex((b) => b.id === id);
+      if (idx === -1) return [...prev, duplicatedBlock];
</file context>

Comment thread .github/dependabot.yml
interval: "weekly"
open-pull-requests-limit: 10
labels:
- "dependencies"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The automated labeling this config claims to set up won't take effect: Dependabot only applies labels that already exist in the repository, and none of dependencies, javascript, or github-actions are defined in .github/labels.yml (the manifest synced to GitHub by label-sync.yml). Consider adding these labels to .github/labels.yml (e.g. dependencies, javascript, github-actions with a color/description each), or pointing labels at existing labels, so Dependabot PRs are actually tagged as intended.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/dependabot.yml, line 13:

<comment>The automated labeling this config claims to set up won't take effect: Dependabot only applies labels that already exist in the repository, and none of `dependencies`, `javascript`, or `github-actions` are defined in `.github/labels.yml` (the manifest synced to GitHub by `label-sync.yml`). Consider adding these labels to `.github/labels.yml` (e.g. `dependencies`, `javascript`, `github-actions` with a color/description each), or pointing `labels` at existing labels, so Dependabot PRs are actually tagged as intended.</comment>

<file context>
@@ -0,0 +1,24 @@
+      interval: "weekly"
+    open-pull-requests-limit: 10
+    labels:
+      - "dependencies"
+      - "javascript"
+
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant