Repository navigation
feat: collection table view and bulk editing (006) - #7
Merged
Merged
Conversation
…(v2.0.0) Approved spec with the Fable spec-review revisions applied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
View switch buttons and prefetch guard, redirects after Set condition and Remove, the tick-submission protocol, and Undo after sign-out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Edit many and Done submit instead of linking, the header checkbox acts on changes, the bulk form's default submit is the filter, and the Undo URL follows the local-path rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…k editing Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fix Sort.from_key for the default key, really hide the Edit many form, harden id and page params, check the Undo cap in one query, run the 360px checks in the narrow frame, split long examples and Phase 6's commit, and add tests for AC-2.3, AC-6.5, AC-7.6 and AC-7.9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plan: Phase 1: The table's model: shared filter, rows and sort Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Plan: Phase 2: The table view and its sortable headers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plan: Phase 3: The view switch and the saved view Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plan: Phase 4: Row actions return to the table Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plan: Phase 5: The bulk selection model Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plan: Phase 6: Bulk mode: Edit many, the bulk form, selecting and Done Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hing Plan: Phase 6: Bulk mode: Edit many, the bulk form, selecting and Done Ruling: SelectionsController#ids keeps only digit strings (grep(/\A\d+\z/)) instead of grep(String) — an empty ticked_ids[] list arrives as [""] and counted as a tick of lot 0, giving NONE_LEFT instead of NOTHING_SELECTED — cost if wrong: none, non-numeric ids never matched a lot anyway Ruling: bulk_mode_spec scopes the aria-selected row lookup to "tbody tr:has(...)" — with SQLite reusing id 1, "tr:has(input[value='1'])" matched the header row's Select all box (value "1") — cost if wrong: none, the assertion is unchanged for body rows Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e table Plan: Phase 6: Bulk mode: Edit many, the bulk form, selecting and Done Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plan: Phase 7: Live count, mixed header and Esc Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plan: Phase 8: Set condition on the selection Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plan: Phase 9: Bulk Remove with Undo Ruling: the layout's status line prefers flash[:alert] over flash[:notice] and offers no Undo beside an alert — a refusal rendered with flash.now[:alert] otherwise showed a notice still in the flash from an unfollowed redirect — a stale notice is now hidden whenever an alert is present Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plan: Phase 10: Design-system docs, phone sweep and query counts Ruling: BulkForm doc's markup follows the rendered partial (c-collection wrapper, rendered_q/page/all_rendered hidden fields, the c-table__select checkbox column, pager position) rather than the plan's abbreviated example — the doc must match what the app renders — none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plan: Phase 10: Design-system docs, phone sweep and query counts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plan: Phase 10: Design-system docs, phone sweep and query counts Ruling: the Set condition and removal examples are tagged :aggregate_failures — RuboCop's RSpec/MultipleExpectations rejects two expectations otherwise, and the project rule asks for the tag — none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bin/brakeman runs with --ensure-latest, so the 8.1.0 release made the pre-commit hook and bin/ci fail on 8.0.6. No new warnings. Ruling: bump the gem rather than drop --ensure-latest — keeps the project's CI policy — cost if wrong: one revertible lockfile line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in one statement Plan: Phase 11: Integration verification Set condition updates lots that don't merge with one tenant-scoped update_all (lot_key rebuilt in SQL from bound parameters), and Undo inserts restored lots that don't merge with one insert_all. Merging lots keep the validated path. Timings at 5,000 lots (none merge), before -> after: - Set condition: 8,547 ms -> 377-496 ms (budget < 2 s) - Undo: 7,407 ms -> 800-893 ms (plan threshold 5 s) - Remove all: unchanged at ~1.8 s (budget < 2 s) Ruling: existing_targets filters by catalog_entry_id in SQL and drops changed lots and non-matching keys in Ruby — the lot_key IN plus id NOT IN query over 5,000 ids took 2.9 s alone — loads every lot of the changed printings (one query, ~100 ms at 5,000). Ruling: the update_all justification is a same-line comment, not a rubocop:disable — Rails/SkipsModelValidations is off in this config — none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plan: Phase 11: Integration verification Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Plan: Phase 11: Integration verification Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…m the implementation review Plan: Phase 11: Integration verification Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…4.0.1) From the implementation review: every other sortable column states none; columns that can't be sorted state nothing. Wording only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Memories: migrate schema noise and :primary rollback; Brakeman --ensure-latest. Lessons from spec 006: Turbo prefetch vs GET side effects, bulk writes and perf data, repeated reviews and implementer rulings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The filter bar and the bulk bar now share one sticky c-bulkhead block below the app header, so scrolling no longer slides the count, actions and Done under the header. The bulk bar sits outside form#bulk, so its buttons join the form with form="bulk"; Filter stays the default submit. Plan: Phase 11: Integration verification Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Card-name links in the collection table rendered browser-default blue and underlined. They now use brand, underline on hover and show the focus ring, documented as the TableItemLink addition. Plan: Phase 11: Integration verification Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PR screenshot walkthrough showed Select all then an untick counting 51 of 59 while the action covered 59. FR-4 now resets on a header toggle and then applies rows that differ from their rendered baseline; new AC-5.10. Plan gains Phase 12 for the fix and the empty-state link colour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With scripting, ticking "Select all" then unticking a row showed "3 of 5 selected" but the action covered all 5: the header always changed on the client, so the server ignored every row. Each row now carries its rendered baseline (baseline_ids[]) and scripting reports a header toggle (header_toggled); after a toggle only rows that differ from their baseline apply (spec v5.0.0 FR-4, AC-5.10). Plan: Phase 12: Selection baselines (spec v5.0.0) and empty-state link colour Ruling: model spec "selects every matching lot from the header…" now clears with ticked [b], baseline [b] instead of ticked [a, b] — a was rendered unticked (an exception), so a real browser can't send a ticked against a ticked baseline; leaving rows as rendered keeps the "header untick clears" intent — cost if wrong: one less case where a re-ticked exception is discarded, which v5.0.0 now selects by design Ruling: model spec "selects lots added later" ticks a with baseline [a] when entering Select all (a was rendered ticked after include!), and sends baseline [a, b] on the next page — keeps the example's intent (reach Select all with no exceptions, then make both exceptions) — cost if wrong: none expected Ruling: request spec "keeps an untick as an exception across pages" and "clears everything when the header is unticked…" send baseline [first] (the row rendered ticked under Select all); submit_bulk gains baseline: and always sends header_toggled "0" — cost if wrong: none expected Ruling: added a request spec for the rendered baseline_ids[]/header_toggled fields, not listed in the phase — cost if wrong: one extra example Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The empty collection's "Search cards" link rendered in the browser's default blue with an underline; 004's design rule makes links brand. Plan: Phase 12: Selection baselines (spec v5.0.0) and empty-state link colour Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Desktop, phone (390px narrow frame) and dark-theme captures of the table, view switch, sorting, bulk mode, Set condition, Remove and Undo, plus the bulk-flow walkthrough GIF, from real Scryfall data. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
plainprogrammer
force-pushed
the
006-collection-list-view
branch
from
October 1, 2026 14:52
80b64a3 to
d977550
Compare
Root cause: a frame navigation with data-turbo-action="advance" pushes the URL, but its follow-up visit is willRender: false, so Turbo's lastRenderedLocation stays at the old URL. On Back, the restoration visit's turbo:before-cache caches the current, filtered page under that old key. Normally Back has already read the cached snapshot; on a slow runner the write lands first and Back restores the filtered results at the unfiltered URL (CI failures in collection_spec and catalog_search_spec). Reproduction: after filtering, call Turbo.session.view.cacheSnapshot() once (the same write, landing early), then go_back. The new spec/system/back_navigation_spec.rb does this for the collection and search pages and failed with "3 of 5 items" / stale results before the fix. Fix: <meta name="turbo-cache-control" content="no-cache"> on the collection and search pages, so Turbo never caches them and Back fetches fresh HTML. Cost: Back to those pages refetches instead of restoring instantly. It is still a Turbo visit, so window state survives. Memory notes updated with the root cause. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
catalog_search_spec.rb:4 times out opening a card page on GitHub CI only (runs 36793202581, 36882235200); cold template compilation and the Back race are ruled out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 tasks done
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
This PR implements spec 006 Collection Table View and Bulk Editing (v5.0.0), following its plan and data model.
Sec-Purpose,X-Sec-Purpose,X-Moz,Purpose).Edit many).delete_all.collector/additions.css.Migrations (additive, reversible):
users.collection_view(defaultgrid),bulk_selections+bulk_selection_marks, andbulk_removals(a partial unique index allows one undoable removal per session).Also in this branch
bin/brakemanruns with--ensure-latest, so the upstream release had started failing CI.Screenshots
These were captured from the dev server with real Scryfall data, in headless Firefox. The phone shots come from a 390px frame.
Bulk-flow walkthrough: grid → Table → sort by condition → row menu → Edit many → tick → Select all with one untick → Set condition → Remove → Undo → Esc.
What the walkthrough caught. These are all fixed test-first in this PR:
2746e20).cd95320).brandlink colour (24ccf5b). Empty-state links from 004 get the same fix (7fcfb6f).Process
Ruling:lines.Performance (spec NFR, measured at 5,000 lots)
Measured on synthetic data in a rolled-back test-env transaction:
6e553c3)The queries for the table and the bulk table don't grow with the number of rows (
spec/requests/query_counts_spec.rb).Test plan
bin/cipasses: RuboCop, Brakeman, bundler-audit, importmap audit and RSpec (480 examples, 0 failures)db/schema.rbunchanged🤖 Generated with Claude Code