Repository navigation
feat(watchlist): bulk edit and remove - #12
Merged
Merged
Conversation
- BulkDeleteWatchlist sqlc query and Service.BulkRemove - POST /watchlist/bulk/delete behind the auth gate, WatchlistBulkStore seam - end-to-end test against real Postgres
…otency - artists, artist_tags links and unnotified events survive a bulk remove - the note goes with the row
- 401/403 gate, 503 without the option, literal-path precedence - id and body bounds, dedupe, empty arrays, no error leak on 500
- ordered FOR NO KEY UPDATE pre-lock, then count-guarded insert - cap skips carry the artist name, gone ids are reported, all-capped rolls back - POST /watchlist/bulk/tags/add behind the gate and CSRF check
…an-tag rollback - 9 and 10 link adjacency, already-tagged at cap counts as applied - reversed id lists across goroutines never hit 40P01 - a bulk add waiting on an uncommitted 10th link skips that artist as cap
… contract - ADR records the ordered pre-lock and the all-capped rollback - tests pin 401/403, 503, literal-path precedence, validation, error map and skip wire shape
- bulkRemoveWatchlist, formatNameList, formatAlreadyRemoved - leading Select checkbox on each card, sticky selection bar - route-hosted destructive ConfirmDialog, close-then-clear on success
…s shown - pure effectiveSelection/selectAllState/toggleAllVisible helpers - vendored Checkbox renders indeterminate with a Minus glyph - heading-row Select all visible; hidden selections stay inert
- unit cases for effectiveSelection, selectAllState, toggleAllVisible - route cases: hidden inert, uncheck keeps hidden, empty view, one card, Clear
- applyRange spans only the visible ids - anchor resets on search, filter, sort change and empty selection
- BulkUpdateWatchlistPreferences locks targets, rebuilds each axis in canonical order, updates only changed rows - Service.BulkUpdatePreferences validates via normalizeSet; conflict and empty deltas are rejected - PATCH /watchlist/bulk/preferences with end-to-end test
… concurrency - per-type delta keeps untouched types, canonical order, no-op rows keep updated_at - validation sentinels and empty-ids ordering - held-lock cases for a concurrent axis write and a concurrent delete
- error-code mapping, over-post and old-shape refusal, id bounds with zero store calls - 401/403 gate, 503 without option, literal-path precedence, skip wire shape
- ConfirmDialog.finalFocus accepts base-ui's full union - fallback chain: Select all visible, Clear filters, page heading - cancel, Esc and failure return focus to the bar's Remove
- status region reads selection start, select-all and clear - filter announcement appends the visible selected count - Esc inside the bar clears the whole selection
- bar publishes --bulk-bar-clearance on the root while mounted - Toaster offsets, html scroll-padding-bottom and page padding use it
- BulkDetachTag and GetTagByID statements; tags.Service.BulkDetach - POST /watchlist/bulk/tags/remove reports applied, carried and gone ids - end-to-end test over HTTP against Postgres
- vendor popover (hand-written; shadcn CLI tried to pnpm add a bogus cn package) - bulkUpdatePreferences, tri-state helpers, bar popover editor, route handler - tracer: mute one type on a mixed selection sends exactly one changed type
- initialTriStates, cycleTriState, buildAxisDelta cases - popover focus, dismissal, pending lock, failure retention, one-surface rules
…ckiness - gone-skip toast, failure retry with the same delta, too-many-ids backstop - single Deluxe delta in one request, muted-filter stickiness - bulkUpdatePreferences wire shape
- bulkRemoveTag wrapper and removableTags union with carrier counts - Remove tag button and single-value picker with N-of-M counts - handleBulkRemoveTag: one request, sticky applied ids, Undo toast - undoRemoveTag re-adds by name to applied ids only (restore copy)
- Undo re-adds to applied ids only and leaves a newer selection alone - restore, cap-warning, all-at-cap and failure toasts verbatim - 404 tag_not_found refreshes and keeps the selection - bulk artist removal still offers no Undo
- removableTags union, counts, collator order and id ties - picker label, filtering, empty states, class contract, Esc and Cancel focus - bar control order and one-surface rule in remove-tag mode - bulkRemoveTag wire shape
- Refresh internal/webassets/build/client from web/ so a Go-only build serves selection, the action bar, bulk pickers, Preferences and remove confirmation
Coverage
head e8ec5b8 · 2026-10-19T19:43:38Z |
## Summary Stacked on #12. Behavior-preserving cleanup of the phase 26 bulk UI: the route and the action bar each did too many jobs. ```text web/app/ ├── routes/watchlist.tsx 1065 -> 711 lines, wiring only ├── components/watchlist/ │ ├── BulkActionBar.tsx 585 -> 330, tag modes + button row │ └── BulkPreferencesPopover.tsx new, owns tri-state, delta, Apply focus └── lib/ ├── useBulkSelection.ts new, selection, remove confirm, handlers, Undo, focus ├── bulkOutcomeMessages.ts new, pure toast data ├── preferenceOptions.ts new, option lists shared with the card └── useBarClearance.ts new, publishes --bulk-bar-clearance ``` ```diff Watchlist (route) - selectedIds, anchorRef, removeConfirm, 4 handlers, toast copy, Undo, focus refs + useBulkSelection({ entries, visible, ... }) -> { effective, bar, confirmDialog, selectAll, refs } ``` - `preferenceOptions.ts` replaces the bar's hand-synced copy of the card's option lists; `PreferencesDelta` is defined once in `api.ts`. - Toast copy is returned as data (`{ level, title, description?, duration? }`); only the hook calls `sonner`. - Opaque comment tags (`U1`, `T-26-45`, `grill Q9`) removed from touched files. - `CONTEXT.md` gains **Bulk selection**, **Effective selection**, **Bulk outcome**. - `.graphifyignore` now excludes tests, sqlc output, `web/package.json` and per-plan logs; graph rebuilt (4313 -> 1784 nodes, hubs are source, not `testing.T`). ## Evidence - **Before:** 782 web tests pass. **After:** 793 pass (11 new table tests for `bulkOutcomeMessages`). `watchlist.bulk.test.tsx` and `BulkActionBar.test.tsx` pass unedited. ```text addTagToast(add([], [cap Kid, gone 3]), ...) -> { level: "warning", title: "Couldn't add “trap” — every selected artist already has 10 tags.", description: "Kid. 1 artist was already removed from your watchlist.", duration: 8000 } ``` `prettier --check` and `tsc --noEmit` clean. Go gates not run (no Go changes). ## Merge Danger **Door:** two-way **Blast Radius:** watchlist bulk actions Frontend-only module moves. The embedded SPA bundle under `internal/webassets` is not rebuilt here, so the shipped bundle still matches #12 until the next build.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
golang.org/x/textto v0.41.0 to clear CVE-2026-56851 (trivy-fs).Verification
-raceintegration job and trivy-fs).Known advisories