Studio: delete selected recipe blocks with the Delete key (#14223)

Fixes https://github.com/unslothai/unsloth/issues/9211 (the "couldn't
find a way to remove a node" part).

## Problem

The Data Recipes canvas only deletes a selected block with Backspace,
which is React Flow's default `deleteKeyCode`. On Windows and most Linux
keyboards people press Delete to remove things, and on the Data Recipes
canvas Delete does nothing. There is also no visible delete button, so
users conclude blocks cannot be removed at all (see the comment on #9211
from a Windows 10 / Edge user).

## Change

Pass `deleteKeyCode={["Backspace", "Delete"]}` to the recipe canvas, as
a module-level constant so React Flow's key listener is not
re-subscribed on every render.

- Backspace keeps working exactly as before (macOS users are
unaffected).
- Delete now removes the selected blocks and edges through the same
React Flow path Backspace already uses, so node removal, edge cleanup
and the store's removal helpers are unchanged.
- Typing in inputs, textareas and editors is unaffected: React Flow
ignores the delete key while focus is in an editable element, same as
for Backspace today.
- No UI changes, no new buttons, no flow changes.

## Tests

- `tests/recipe-canvas-delete-keys.test.ts`: fails on main (constant
missing) and when only the canvas wiring is reverted, passes with the
change.
- Typecheck clean; eslint / biome diagnostics on the touched source
files are identical to main.

## Before / after

Two fresh Studio installs (merge base `a33463f` vs this branch), the
"Text to SQL" learning recipe opened fresh on each, one block selected,
then the key pressed. Node counts are read from the canvas DOM.

| | Chromium | Firefox | WebKit |
|---|---|---|---|
| Delete removes the selected block, before | no | no | no |
| Delete removes the selected block, after | yes | yes | yes |
| Backspace removes the selected block, before and after | yes | yes |
yes |
| Delete while typing in a block's inline field keeps the block, before
and after | not exercised (no block selected) | yes | yes |

![Before: Delete pressed, the selected block on the right stays. After:
Delete pressed, the block and its link are
removed.](https://raw.githubusercontent.com/danielhanchen/unsloth-staging-2/pr-media/pr/14223/pr14223_delete_key_crop-3c4ccdd36a34.png)

Before: the selected block on the right edge (and its dashed link) is
still there after pressing Delete. After: it is removed.
This commit is contained in:
Daniel Han 2026-10-19 06:44:17 -07:00 • committed by GitHub
parent e632df1ca6
commit f5e91f1ca5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 22 additions and 0 deletions

View file

@ -6,3 +6,5 @@ export const DEFAULT_NODE_HEIGHT = 120;
export const MIN_NODE_WIDTH = 260;
export const MAX_NODE_WIDTH = 900;
export const MAX_NOTE_NODE_WIDTH = 600;
// React Flow deletes on Backspace only; Windows and Linux keyboards reach for Delete (#9211).
export const CANVAS_DELETE_KEYS = ["Backspace", "Delete"];

View file

@ -31,6 +31,7 @@ import { Button } from "@/components/ui/button";
import { GuidedTour, useGuidedTourController } from "@/features/tour";
import { buildRecipeEditorTourSteps } from "./tour";
import { BlockSheet } from "./components/block-sheet";
import { CANVAS_DELETE_KEYS } from "./constants";
import { LayoutControls } from "./components/controls/layout-controls";
import { RunValidateFloatingControls } from "./components/controls/run-validate-floating-controls";
import { ViewportControls } from "./components/controls/viewport-controls";
@ -669,6 +670,7 @@ export function RecipeStudioPage({
onConnect={onConnect}
onNodeClick={handleNodeClick}
onNodeDoubleClick={handleNodeDoubleClick}
deleteKeyCode={CANVAS_DELETE_KEYS}
isValidConnection={isValidConnection}
onMoveEnd={(event) => {
if (event) {

View file

@ -0,0 +1,18 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
import assert from "node:assert/strict";
import { test } from "node:test";
import { CANVAS_DELETE_KEYS } from "../src/features/recipe-studio/constants.ts";
import { readSrc } from "./helpers/kit.ts";
test("recipe canvas deletes selected blocks on Delete as well as Backspace", () => {
assert.deepEqual([...CANVAS_DELETE_KEYS].sort(), ["Backspace", "Delete"]);
const src = readSrc("features/recipe-studio/recipe-studio-page.tsx");
const canvas = src.slice(src.indexOf("<ReactFlow<"));
assert.match(
canvas.slice(0, canvas.indexOf("\n >\n")),
/\bdeleteKeyCode=\{CANVAS_DELETE_KEYS\}/,
);
});