Skip to content

Bulk Action Bar: multi-select delete / lock / color across list menus#1513

Open
spencerpruitt wants to merge 5 commits into
Azgaar:masterfrom
spencerpruitt:bulk-action-bar
Open

Bulk Action Bar: multi-select delete / lock / color across list menus#1513
spencerpruitt wants to merge 5 commits into
Azgaar:masterfrom
spencerpruitt:bulk-action-bar

Conversation

@spencerpruitt

Copy link
Copy Markdown

Summary

Adds a shared, reusable Bulk Action Bar to every list-style overview/editor dialog, so multiple entities can be selected and acted on at once instead of one row at a time. Each menu gains a top-right "Bulk Options" toggle that reveals per-row checkboxes plus an inline toolbar with Delete / Lock / Unlock / Set color (color only where the entity type has one). Selection is scoped to the open menu.

Covers all ten list menus: States, Cultures, Religions, Regiments, Markets (TS controllers, direct attach) and Burgs, Provinces, Markers, Routes, Zones (legacy public/modules/ui JS, via a window.bulkBars bridge).

Motivation

Acting on entities one at a time is tedious — deleting twelve states means twelve trash clicks and confirmations. Two menus had grown inconsistent one-off shortcuts (Burgs "Remove All", a Zones erase-mode toggle) that existed nowhere else and were all-or-nothing. This unifies everything behind one consistent, filter-aware, multi-select control and removes those ad-hoc controls.

How it works

New module src/controllers/bulk-action/:

  • bulk-selection.ts — pure, DOM-free BulkSelection with a canSelect predicate that keeps non-deletable rows (e.g. neutral state 0) out of any selection.
  • bulk-entity-adapter.ts — the per-type BulkEntityAdapter seam (the only type-specific code).
  • bulk-action-bar.ts — DOM glue; one instance per menu; re-syncs after each list re-render. The toolbar persists across actions and keeps the selection (deleted rows drop off), so actions chain.
  • bulk-delete-confirm.ts — confirmation dialog built from a cascade summary, with a childKind-gated "also delete contained burgs" checkbox (States/Provinces) whose summary updates live.
  • adapters/ — one adapter per type.

Two attach paths. Migrated TS menus construct a BulkActionBar directly. Legacy JS menus can't import ES modules, so they reach it through legacy-bridge.ts, which registers window.bulkBars — the same window.X cross-layer pattern already used for window.COA, window.tip, window.ensureEl. A legacy menu calls window.bulkBars.mount(type, {redraw}) on open and window.bulkBars.sync(type) after row-render. The bridge is a deliberate, temporary seam: a menu drops it for the direct-import path once migrated to TS.

Cascade fidelity. Bulk delete reuses each type's existing single-delete cascade. For migrated types the single-delete data mutations were extracted into a pure *-cascade.ts now shared by both the editor's single delete and the adapter's bulk delete (States/Cultures/Religions/Regiments), so the two can't diverge. Provinces' single delete was a legacy closure that couldn't be imported, so its mutations are reimplemented in the adapter and matched against the original. Burgs/Markers/Routes/Zones/Markets delegate to their existing remove functions.

Safety. There is no undo, so locked rows are protected from bulk delete and reported as skipped in the confirmation, which also summarizes the cascade (entities removed, dependents reassigned/removed, child burgs deleted when that option is set).

Removed (with no lost capability)

  • Burgs "Remove All" button + triggerAllBurgsRemove → reproduced by "select all → Delete".
  • Zones erase-mode toggle (zonesRemove), its dragZoneBrush erase branch, and its orphaned Ctrl hotkey handler.

Tests / checks

  • ~260 unit tests: BulkSelection, every adapter, and every cascade (cascade summaries, lock-resists-delete, child-burg counts).
  • vitest run green, tsc --noEmit clean, npm run build clean, biome check clean.
  • DOM/visual behavior verified manually across all ten menus.

Notes

  • One small data-model change: Route gains an optional lock?: boolean so routes can be bulk locked/unlocked.
  • Design recorded in docs/architecture/architecture.md (the bridge) and docs/prd/bulk-action-bar.md; a glossary term was added.

🤖 Generated with Claude Code

Add a shared "Bulk Options" bar to every list-style overview/editor (States,
Cultures, Religions, Regiments, Markets, Burgs, Provinces, Markers, Routes, Zones).
It reveals per-row checkboxes plus a filter-aware select-all, then applies
Delete / Lock / Unlock / Set color to the current selection.

- New src/controllers/bulk-action/ module: pure BulkSelection, a per-type
  BulkEntityAdapter seam, BulkActionBar DOM glue, and bulkDeleteConfirm.
- Bulk delete reuses each type's single-delete cascade; migrated types share a pure
  *-cascade.ts with their editor so single and bulk delete cannot diverge. Locked
  rows are protected from bulk delete and reported as skipped. States/Provinces
  offer an optional "also delete contained burgs" choice.
- Legacy public/modules/ui menus attach via a window.bulkBars bridge (the existing
  window.X cross-layer pattern); migrated TS menus attach directly.
- Replaces the ad-hoc Burgs "Remove All" and Zones erase-mode controls.
- Route gains an optional lock field so routes can be bulk locked/unlocked.
- ~260 unit tests cover selection, every adapter, and every cascade.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@netlify

netlify Bot commented Jun 28, 2026

Copy link
Copy Markdown

Deploy Preview for afmg ready!

Name Link
🔨 Latest commit c7e0561
🔍 Latest deploy log https://app.netlify.com/projects/afmg/deploys/6a415efd16cbe800086bb313
😎 Deploy Preview https://deploy-preview-1513--afmg.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@Azgaar

Azgaar commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Hello. Some issues I see:

  • should be make when migration is over
  • introduces fixture architectural approach, rarely used in FMG (but can be fine if controllers are defined in editor modules)
  • some features were already in FMG and I don't really see a reason why we couldn't just add missing buttons and had to introduce a whole new architecture
  • too wordy (AI-vibes are too strong)

@Azgaar Azgaar self-requested a review June 28, 2026 13:04
@spencerpruitt

Copy link
Copy Markdown
Author

Hello. Some issues I see:

  • should be make when migration is over
  • introduces fixture architectural approach, rarely used in FMG (but can be fine if controllers are defined in editor modules)
  • some features were already in FMG and I don't really see a reason why we couldn't just add missing buttons and had to introduce a whole new architecture
  • too wordy (AI-vibes are too strong)
  1. I'm not sure what the migration timeline is, but happy to hold.
  2. On "a whole new architecture"- that's a bit more than what's here. The delete/lock/cascade logic already existed, duplicated per menu. I pulled each menu's cascade out and now both the existing single-delete and the new bulk path call the same function, so it's de-duplication, not a parallel system. The shared bit is one small controller, not a framework.
  3. The fixture/factory setup is only there so the adapters are unit-testable in isolation. If you'd rather the controllers just live in the editor modules, I'm happy to wire them that way and drop the fixtures.

@Azgaar

Azgaar commented Jun 28, 2026

Copy link
Copy Markdown
Owner

I'd rather keep the change contained in existing controllers. Abstraction into a fixture is fine, but controller functions should still be in controllers, just passed to the fixture. When migration is done (timeline doesn't exist), it can be done cleared. Also would need to improve the UI, the current one takes too much space and attention (should probably be just a single button in the bottom section, just like other buttons)

spencerpruitt and others added 4 commits June 28, 2026 09:48
Address PR Azgaar#1513 review (Azgaar):
- Move each migrated menu's bulk logic (delete cascade, summary, predicates)
  out of the generic bulk-action/adapters/ fixture and into its own controller
  domain (states/cultures/religions/regiments/markets). The controller now
  assembles its BulkEntityAdapter inline and passes it to the BulkActionBar; only
  the generic seam stays under bulk-action/. Pure cascade logic remains unit-tested.
- Replace the full-width top toolbar with a single icon button in each menu's
  footer button row; toggling it reveals row checkboxes plus inline footer
  actions (delete/lock/unlock/color) and Select-all + count.

Legacy-JS adapters and the window.bulkBars bridge stay as-is (cleanup deferred
to the JS->TS migration, per review).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a Rivers bulk adapter (select + delete only; rivers have no lock/color)
wired through the existing window.bulkBars bridge, and a footer bulk button in
the Rivers overview. Delete delegates to the global Rivers.remove (which also
removes tributaries and is a no-op on an already-removed id). The existing
'Remove all rivers' button is kept.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…edesign

Make the PRD concise (per PR review), and update the architecture note and
glossary term to reflect the footer-button UI and the adapters-live-in-controllers
layout, plus the new Rivers menu.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Correctness (from high-effort review of the PR diff):
- Guard bulk Delete/Lock/Set-color with the global `customization` flag (like
  single-delete), and make `.bulkInline[hidden]` use `!important` + clear leftover
  inline display on toggle. The bulk controls live in a footer span, but editors
  toggle footer visibility via `#xBottom > button`/`> *`, which left the controls
  clickable during manual-assignment modes and could force-reveal them on exit.
- Rivers bulk-delete confirmation now counts the whole basin Rivers.remove purges
  (parent/basin matches), not just the selected rows, so it no longer understates
  the deletion. Regression test added.

Cleanup:
- Extract the `plural` helper (copy-pasted 11x) into src/utils/stringUtils.ts and
  import it everywhere, removing the duplication this PR had deepened.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@spencerpruitt

Copy link
Copy Markdown
Author

Pushed an update:

  1. The bulk logic for each migrated menu now lives in that menu's own controller and gets passed into the shared bar, rather than sitting in a separate fixture layer; only the generic select/confirm/bar code stays shared, and the cascade each menu uses is the same one its single-delete already calls.
  2. I dropped the big top bar. Each menu now just has one button in its footer row like the others, and toggling it shows
    the per-row checkboxes and a small set of inline actions. The legacy menus and the window.bulkBars bridge are
    untouched and will get cleared with the migration whenever that happens.
  3. I kept the features that already existed
  4. I added the bulk button to Rivers too.
  5. Finally, I trimmed the PRD and the PR description down.

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.

2 participants