Deal batch actions
One list of actions on a batch of deals, declared once in the teacher app and shared by the explorer, the session picker and the collection detail.
An action on a batch of deals (add to a session, add to a collection, create a collection, bookmark, export as PBN, move to trash) is declared once in apps/app/src/features/deals/batch-actions and consumed by every screen. No screen re-implements an action or its dialog.
The pieces
| Piece | Where | Role |
|---|---|---|
DealBatch | batch-actions/DealBatch.ts | The boards the action applies to, the user's bookmarks among them, and the context: a free selection (DEALS_CONTEXT) or a collection |
| One definition per action | batch-actions/actions/* | id, availability(batch), isActive(batch), opensDialog and the useRunner(batch) hook that executes it (mutation, toast, dialog) |
SessionPicker, CollectionPicker | batch-actions/pickers/* | Connected pickers that run the mutation on a pick; the action dialogs and the deal preview sheet both mount them. Action files import them, never the reverse |
DEAL_BATCH_ACTIONS | batch-actions/DealBatchActions.ts | The ordered list of definitions |
DEAL_BATCH_SURFACES | batch-actions/DealBatchActions.ts | Which actions each screen offers |
useDealBatchActions(batch, surface) | batch-actions/useDealBatchActions.tsx | ViewModels, run(id) and the dialogs of a surface |
DealBatchMenuItems | batch-actions/deal-batch-menu-items.tsx | Connected items to drop into an open dropdown menu |
DealBatchActionItems | @workspace/ui-training/deal/actions | Passive rendering: label, icon, tone, disabled reason, delete confirmation (see Deal) |
DealBatchActionDialog, DealCollectionPicker | @workspace/ui-training/deal/actions | Passive dialog shell and collection list of the picker actions; the app feeds them (toDealCollectionPickerItems) and runs the mutation |
Label and icon follow the action id in ui-training; the copy comes from DealActionsStringsBridge, which binds DealActionsStrings to the deals namespace.
Availability
An action the batch cannot run stays visible and disabled, with its reason under the label; it is never hidden.
| Action | Available when | Otherwise |
|---|---|---|
addToSession | the batch holds a deal; always on a collection (the collection is linked as a whole) | emptySelection |
addToCollection | the batch holds a deal, mine or not | emptySelection |
createCollection | the batch holds a deal, mine or not | emptySelection |
bookmark | the batch holds a deal; removes when every deal is bookmarked | emptySelection |
exportPbn | the batch holds a deal | emptySelection |
delete | every deal can be deleted by me (permissions.canDelete) | notOwned, or emptySelection |
Availability is a pure function of the batch, unit-tested per action for my deals, other authors' deals and a mixed selection (apps/app/__tests__/features/deals/batch-actions). run(id) checks it again through canRunOn: an action the surface does not offer, or that the batch cannot run, is a no-op even when a caller triggers it outside the menu (the degraded notice calls run("exportPbn")). It shapes the UI only: the API still checks ownership on every mutation.
Context
The same action runs differently by context:
- Free selection (
{ kind: "deals" }):addToSessioncopies the deals into the picked session;exportPbnnames the file and the PBNEventafter "Deals". - Collection (
{ kind: "collection", collectionId, collectionName }):addToSessionlinks the collection to the picked session;exportPbnuses the collection name.
Surfaces
| Surface | Screen | Actions |
|---|---|---|
explorerRow | Row menu of the deals explorer | add to session, add to collection, bookmark, delete |
sessionPickerRow | Row menu of the session deal picker | delete |
collection | Collection detail menus (header, mobile FAB) | add to session |
degradedCollection | Notice of a collection above the plan ceiling | export as PBN |
The deal preview sheet reuses the connected pickers of the actions (SessionPicker, CollectionPicker, exported by actions/add-to-session and actions/add-to-collection) inline. The explorer e2e bridge (bulkAddToCollection) runs the add-to-collection action on its selection.
Adding an action
- Add its id to
DealBatchActionIdinui-training, with its icon, tone and label indeal-batch-action-items.tsxand its copy inDealActionsStrings. - Write its definition in
batch-actions/actions/, with a pureavailability. - Insert it in
DEAL_BATCH_ACTIONSand mount its runner inuseDealBatchActions. - Offer it on the surfaces that need it in
DEAL_BATCH_SURFACES, and cover its availability in the unit tests.