Skip to content

Add bulk delete by entry type to the Brain UI - #929

Merged
selfcontained merged 2 commits into
mainfrom
agt_185c616cd5c1/build-brain-ui-bulk-delete-by-type
Aug 11, 2026
Merged

Add bulk delete by entry type to the Brain UI#929
selfcontained merged 2 commits into
mainfrom
agt_185c616cd5c1/build-brain-ui-bulk-delete-by-type

Conversation

@selfcontained

@selfcontainedselfcontained commented Aug 11, 2026

Copy link
Copy Markdown
Owner

The Brain explorer could delete one entry at a time, clear a collection, or clear a project — but not "clear just the events", which is what you want when one noisy job floods the event log and the objects and lists around it are still worth keeping.

Each section header (Objects / Lists / Events) now clears that type in the current scope: the selected collection, or every collection in the project when the All pill is active.

Server

  • deleteEntriesOfType(repoRoot, type, scope) deletes one entry type in one scope. Lists take their items with them via the existing brain_list_items foreign key cascade.
  • deleteFromEntryTable is now the only author of a bulk WHERE clause, the table lookup, and the blank-collection rule. deleteCollection and deleteProject route through it too, so the three paths cannot disagree about scoping.
  • toEntryTypeDeleteScope owns "collection XOR allCollections", called by both the route and the store so untyped callers hit the same gate. A missing collection is rejected rather than read as "everything" — the same reason brain_delete_events requires one (Add brain_delete_events MCP tool #928).
  • DELETE /api/v1/brain/{objects,lists,events} keeps only HTTP-shaped rules: rejecting array-valued query keys, requiring repoRoot, coercing allCollections. mapBrainValidation turns store rejections into 400s, which the pre-existing collections DELETE now shares.

Web

  • The confirmation quotes the scope's real totals, not the rows on screen. The lists are capped at 100 and narrowed client-side by the filter box, so the badge can read 0 / 6 while the dialog offers to delete all six. Totals are read live, so an agent writing entries while the dialog is open moves the number rather than leaving it stale.
  • A filter that hides every row of a type keeps the section mounted — otherwise the one control that reaches the hidden rows disappears exactly when it is needed.
  • Bulk delete is not offered until totals load, rather than guessing a count and then silently skipping the empty-collection redirect.
  • CollapsibleSection stays a layout primitive: one headerAction slot, and visibleCount/totalCount instead of a count whose meaning changed.

Reviews

Three persona reviews ran on this branch; all 10 items are resolved. They caught a filter-to-zero bug that hid the delete-all button, a stale-count snapshot that SSE invalidation made live rather than theoretical, a blank-collection divergence between the two delete paths, and a 500 introduced while fixing that last one.

Testing

  • pnpm run check, pnpm run finalize:web
  • Vitest: 150 + 56 + 9 test files pass, including 14 new brain store/route tests covering scoped and project-wide deletes, cascade, cross-repo isolation, and every malformed selector returning 4xx. brain-routes.test.ts drives the real Fastify app against a real Postgres via app.inject, so the new endpoints are covered end to end without a browser.
  • Playwright: 178 pass. No e2e was added for this feature — the backend is covered by the route tests above, and the frontend wiring is in an area that changes rarely and was verified by hand. The one e2e edit is an exact: true locator fix, needed because getByRole matches accessible names by substring and the new "Delete all ..." buttons otherwise collide with the "All" collection pill.

🤖 Generated with Claude Code

selfcontainedand others added 2 commits August 10, 2026 18:30
The Brain explorer could delete one entry at a time, clear a collection, or
clear a project — but not "clear just the events", which is what you want when
one noisy job floods the event log and the objects and lists around it are
still worth keeping.
Each section header (Objects / Lists / Events) now clears that type in the
current scope: the selected collection, or every collection in the project when
the All pill is active.
Server:
- deleteEntriesOfType(repoRoot, type, scope) deletes one entry type in one
scope. Lists take their items with them via the existing foreign key cascade.
- deleteFromEntryTable is now the only author of a bulk WHERE clause, the table
lookup, and the blank-collection rule; deleteCollection and deleteProject
route through it too, so the three paths cannot disagree about scoping.
- toEntryTypeDeleteScope owns "collection XOR allCollections", called by both
the route and the store so untyped callers hit the same gate. A missing
collection is rejected rather than read as "everything" — the same reason
brain_delete_events requires one.
- DELETE /api/v1/brain/{objects,lists,events} keeps only HTTP-shaped rules:
rejecting array-valued query keys, requiring repoRoot, coercing
allCollections. mapBrainValidation turns store rejections into 400s, which
the pre-existing collections DELETE now shares.
Web:
- The confirmation quotes the scope's real totals, not the rows on screen: the
lists are capped at 100 and narrowed client-side by the filter box, so the
badge reads "0 / 6" while the dialog offers to delete all six. Totals come
from useBrainScopeTotals and are read live, so an agent writing entries while
the dialog is open moves the number rather than leaving it stale.
- A filter that hides every row of a type keeps the section mounted, otherwise
the one control that reaches the hidden rows disappears exactly when it is
needed.
- Bulk delete is not offered until totals load, rather than guessing a count and
then silently skipping the empty-collection redirect.
- CollapsibleSection stays a layout primitive: one headerAction slot, and
visibleCount/totalCount instead of a count whose meaning changed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The backend is already covered by brain-routes.test.ts, which drives the real
Fastify app against a real Postgres through app.inject — scoped and
project-wide deletes, cascade, cross-repo isolation, and every malformed
selector, in 3s with no browser. The two Playwright tests only added frontend
wiring coverage for an area that changes rarely and was verified by hand.
Keeps the "All" pill locator fix: the section headers now render "Delete all
objects in ..." buttons, and getByRole matches accessible names by substring,
so the pre-existing test needs exact: true to stay unambiguous.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@selfcontained
selfcontained merged commit 6cb0790 into mainAug 11, 2026
1 check passed
@selfcontained
selfcontained deleted the agt_185c616cd5c1/build-brain-ui-bulk-delete-by-type branch August 11, 2026 00:55
selfcontained added a commit that referenced this pull request Aug 12, 2026
BrainCollectionView's bulk delete (#929) had no unit coverage and no E2E:
brains-page.spec.ts only exercises the single-entry and clear-collection
paths. The new tests mount the real card tree with the real use-brain
queries and mutations (only the HTTP seam and the toaster are mocked) and
pin the parts that are decided from unfiltered scope totals rather than the
rows on screen — the scoped vs all-collections delete URLs, the count the
confirmation may quote, singular/plural nouns, the collection-cleared
redirect and its two guards, invalidation, the failure path, the filter
match fields, and the 100-item cap note.
Also fixes two harness defects that only showed up locally:
- scripts/e2e-isolated.sh now installs the pinned Playwright chromium
before provisioning anything. pnpm install does not fetch browser
binaries, so a Playwright bump in the lockfile made every browser test
fail at launch; CI has always installed it as its own step.
- .prettierignore now covers apps/site/.astro. `pnpm run check` generates
it, after which `pnpm run format` — and so `pnpm run ci` — failed on any
machine that had run check. CI passes it only because its checkout is
still fresh.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto 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.

1 participant

@selfcontained