Skip to content

Tasks: let folders be deleted from Manage - #1703

Merged
SawyerHood merged 3 commits into
get-bb:mainfrom
builtui:fix/tasks-delete-folder
Aug 19, 2026
Merged

Tasks: let folders be deleted from Manage#1703
SawyerHood merged 3 commits into
get-bb:mainfrom
builtui:fix/tasks-delete-folder

Conversation

@builtui

@builtui builtui commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Fixes #1701

Problem

deleteFolder ships in the RPC contract, the API handler, the store, and the CLI — but no UI surface calls it. Manage → Folders rows offer rename and a parent select; the sidebar renders folders as collapse headers with no menu. A folder created from the New project dialog can only be removed via the CLI.

Change

Adds the trash control the Labels and Presets rows already have, behind the shared ConfirmDialog.

The confirmation names the outcome instead of warning about loss, because deleting a folder is not destructive: both folder foreign keys are ON DELETE SET NULL, so its projects and subfolders move to the top level and no task is touched.

  • 2 projects and 1 subfolder move to the top level. No tasks are deleted.
  • The folder is empty.

A failed delete surfaces in the section's existing role="alert" rather than closing silently, matching the labels flow.

Tests

Two tests in views/manage/manage.test.tsx: the delete path (impact copy, then deleteFolder called with the right id) and the failure path (error surfaces instead of a silent close).

turbo run typecheck test --filter=bb-plugin-tasks: 35 test files pass, typecheck clean.

Notes

No contract, CLI, or docs change — the surfaces already existed, only the app was missing one.

AGENT GENERATED: by Claude Opus 5

@bb-slop-cop

bb-slop-cop Bot commented Aug 19, 2026

Copy link
Copy Markdown

🚨 SLOP COP 🚨 · review

I am SlopCop, and I am reviewing this pull request now.

Plain English summary: This change lets a user delete a Tasks folder from the Manage view.

I will check security, code quality, performance, architecture, and the user path. I will post the full results after those checks finish.

size="icon"
variant="ghost"
className="size-6 text-muted-foreground hover:text-destructive"
aria-label={`Delete folder ${folder.name}`}

@bb-slop-cop bb-slop-cop Bot Aug 19, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review — Blocking: this new user feature has no agent CLI path.

bb tasks folder still supports only create, list, and update. Therefore, bb tasks folder delete <id-or-name> returns unknown folder subcommand: delete. The repository contract requires each end-user feature to ship through the bb CLI. Please add the delete command, explicit destructive confirmation, help, README text, and CLI tests.

// NULL moves its projects and subfolders to the top level. Nothing else is
// removed, so the confirmation names the move rather than warning about loss.
function describeDeleteImpact(folder: Folder): string {
const projectCount = (projects.data ?? []).filter(

@bb-slop-cop bb-slop-cop Bot Aug 19, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review — The dialog can report a false delete impact while projects load.

useFolders() and useProjects() load independently. Folder rows can appear before project data arrives, and projects.data ?? [] converts that state into zero projects. A user can then see “The folder is empty” even though the delete will move projects. A project-query failure causes the same result. Please wait for valid project data, or compute the authoritative impact on the server. Add a delayed or failed listProjects test.

@bb-slop-cop bb-slop-cop 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.

🚨 SLOP COP 🚨 · review

Plain English summary: This PR adds a delete control for Tasks folders in Manage.

Deleting a folder moves its direct projects and child folders to the top level. It does not delete tasks.

I found two issues:

  1. Blocking contract gap: The new user feature has no bb tasks folder delete command. The repository requires a CLI path for every user feature. I left an inline comment.
  2. Incorrect impact text: The folder list can load before the project list. The dialog can then say a folder is empty when it contains projects. I left an inline comment.

The security review found no issue. React escapes folder names, the contract validates IDs, and SQL uses bound values.

The performance review found no issue. The dialog performs one linear scan of projects and folders, then sends one RPC call.

The code correctly reuses the shared ConfirmDialog. I found no duplicate folder-delete UI that needs a new shared component.

A server impact query would remove the UI's copy of database behavior. It would also fix the load race with authoritative counts.

All GitHub CI checks passed. The focused local Manage test reached Vitest, but host contention prevented completion, so I stopped it.

I did not run the browser path because the focused local test did not finish. Please fix the two inline issues before merge.

undraft and others added 2 commits August 19, 2026 22:26
The deleteFolder RPC, contract entry, store function, and CLI command all
shipped, but the only folder surface in the app (Manage -> Folders) offered
rename and re-parent, so a folder created from the New project dialog could
never be removed without dropping to the CLI.

Adds the trash control the Labels and Presets rows already have, behind the
shared ConfirmDialog. The confirmation names the outcome rather than warning
about loss: both folder foreign keys are ON DELETE SET NULL, so the folder's
projects and subfolders move to the top level and no task is touched.

Fixes get-bb#1701

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n loaded data

Review follow-ups for the Manage folder delete control:

- Add `bb tasks folder delete <id-or-name> [--json]` with the same
  semantics as the dialog: the folder's projects and subfolders move to
  the top level and no tasks are deleted. The command resolves folders by
  id or name, reports what moved (ids in JSON, counts in text), and fails
  with `folder not found` for unknown folders. Help text and README list
  the new subcommand.
- The Manage confirm dialog loaded folders and projects independently and
  turned a still-loading or failed project list into "The folder is
  empty". It now waits for both queries before describing the impact,
  shows a loading/error message instead, and disables confirm until then
  (ConfirmDialog gained a `confirmDisabled` prop).
- Tests: CLI delete by name and id, unknown folder, missing positional,
  moved projects/subfolders and untouched task; dialog gating on a delayed
  and a failed listProjects.

Co-Authored-By: Claude <noreply@anthropic.com>
@SawyerHood
SawyerHood force-pushed the fix/tasks-delete-folder branch from 94a557c to 551d7c5 Compare August 19, 2026 22:30
@SawyerHood

Copy link
Copy Markdown
Collaborator

Addressed both SlopCop findings (rebased onto current main, pushed as 551d7c5):

  1. CLI path — added bb tasks folder delete <id-or-name> [--json] next to create|list|update. Same semantics as the Manage dialog: the folder's projects and subfolders move to the top level, no tasks are deleted. Resolves by id or (unambiguous) name, fails with folder not found: …, and reports what moved (movedProjectIds/movedFolderIds in JSON, counts in the text message). bb tasks --help, bb tasks folder --help, and the plugin README list it. The SDK surface (deleteFolder RPC) already existed. Test covers delete by name and id, unknown folder, missing positional, and that the project/subfolder are unfiled while the task survives.
  2. False impact while projects loadFoldersSection now gates the impact text on both useFolders() and useProjects() having loaded (data present and not in flight). Until then the dialog shows "Checking what the folder contains…" (or "Could not load projects: …" on a failed listProjects) and the confirm button is disabled (ConfirmDialog gained confirmDisabled). Tests cover a delayed and a failed listProjects.

turbo run typecheck clean; turbo run test --filter=bb-plugin-tasks --force: 35 files, 355 tests pass.

AGENT GENERATED: by Claude Opus 5

@bb-slop-cop

bb-slop-cop Bot commented Aug 19, 2026

Copy link
Copy Markdown

🚨 SLOP COP 🚨 · review

I am the SlopCop. I am reviewing this change for security, code quality, performance, architecture, and end-to-end behavior.

Comment thread plugins/tasks/cli/index.ts Outdated
// Deleting a folder only unfiles what it held (ON DELETE SET NULL moves
// its projects and subfolders to the top level), so report the move the
// same way the Manage dialog does instead of warning about loss.
const movedProjects = (await listProjects(domain)).filter(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review — P2: The command computes move results before the delete and reads all projects.

Another client can change the folder between these reads and the delete. The JSON result can then contain incorrect moved IDs. This code also filters every project, although listProjects accepts folderId. Return the moved IDs from the delete transaction. Use indexed queries for the target folder.

),
)
.folders.filter((entry) => entry.parentFolderId === folder.id);
const result = tasksRpcContract.deleteFolder.output.parse(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review — P2: Both delete paths can report success when no row changes.

The RPC returns deleted: false when another client already removed the folder. The CLI still prints success and exits with code zero. The Manage dialog also closes without an error. Treat false as a not-found conflict. Refresh the UI and return a CLI error.

// The folder and project queries load independently, so the impact text
// waits for both rather than treating a still-loading (or failed) project
// list as "no projects".
const impactReady =

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review — P2: A failed refresh can enable deletion with cached data.

useTasksQuery keeps same-scope data when a refresh fails. This condition ignores both query errors and can set impactReady to true. The dialog can show stale counts and enable deletion. Require both errors to be null. Add a test with cached data followed by a failed refresh.

status Show plugin status
project create|list|show|update
folder create|list|update
folder create|list|update|delete

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review — P3: The registered folder command summary still omits delete.

This help now includes delete. However, the registered folder summary at line 1944 still lists only create, list, and update. Add delete there so all CLI help remains accurate.

@bb-slop-cop bb-slop-cop 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.

🚨 SLOP COP 🚨 · review

Plain English summary: This PR lets users delete Tasks folders from Manage and the CLI. The operation moves projects and subfolders to the top level. It keeps all tasks.

I found four issues:

  1. A failed refresh can leave cached data and still enable deletion. The dialog can show stale impact counts. Inline comment
  2. Both delete paths ignore deleted: false. The UI can close without an error, and the CLI can report false success. Inline comment
  3. The CLI reads all projects before deletion and computes moved IDs outside the delete transaction. Concurrent changes can make the result incorrect. Inline comment
  4. The registered folder command summary still omits delete. Inline comment

I found no direct security defect. React escapes folder names, the contract checks IDs, and SQL uses bound values.

The change uses the shared ConfirmDialog. However, the UI and CLI duplicate the folder-impact policy. A server impact query and an authoritative delete result would remove this duplication. They would also permit indexed queries.

GitHub reports that all current CI checks pass. The Node compatibility smoke job did not run.

I did not start a local server or a browser test. This review environment cannot safely isolate code from the untrusted fork.

…d stale impact data as failures

- deleteFolder (store, API contract, CLI, UI) now returns movedProjectIds and
  movedFolderIds read inside the delete transaction; the CLI no longer computes
  the move from a pre-delete project scan.
- deleted: false is a failure: the CLI exits 1 with a folder-not-found message
  and the Manage dialog shows the alert and refetches instead of closing
  silently.
- The Manage delete dialog withholds the impact and disables confirm when
  either the folders or projects query has an error, even if cached rows exist.
- The registered folder command summary lists delete.

Co-Authored-By: Claude <noreply@anthropic.com>
@SawyerHood

Copy link
Copy Markdown
Collaborator

Pushed 98e95e1 addressing the second-round SlopCop findings:

  1. Stale impact after a failed refreshFoldersSection now derives impactError = folders.error ?? projects.error; impactReady requires both errors to be null, so a failed refetch that keeps cached rows shows "Could not load the folder's contents: …" and disables confirm. Test: cached data, then a failing projects:changed refetch, asserts the error text, no counts, disabled confirm, no RPC call.
  2. deleted: false ignored — UI: the delete action throws (Folder "x" was already deleted.), the alert shows, and the panel refetches folders/projects since the server publishes nothing for a no-op delete. CLI: exits 1 with folder not found: <address> (it was deleted by another client) and no stdout (JSON mode included). Tests added for both.
  3. Moved IDs computed outside the transaction — the store's deleteFolder now runs in a db.transaction that reads the folder's project/subfolder IDs via the indexed folder_id / parent_folder_id queries and deletes the row, returning { deleted, movedProjectIds, movedFolderIds } (empty arrays when nothing matched). The RPC contract, API handler, CLI, and UI types all use that result; the CLI no longer scans every project. Store test covers the returned IDs and the second no-op delete.
  4. Folder command summary — the registered folder summary now reads "Create, list, update, or delete project folders".

pnpm exec turbo run typecheck --filter=bb-plugin-tasks passes; pnpm exec turbo run test --filter=bb-plugin-tasks --force is 35 files / 359 tests passing.

AGENT GENERATED: by Claude Opus 5

@SawyerHood
SawyerHood merged commit 2e444e7 into get-bb:main Aug 19, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tasks: folders cannot be deleted from the UI

2 participants