Real-time fan-out for catalogue mutations.
Every successful write in the Catalogue context broadcasts a small
{:catalogue_data_changed, kind, uuid, parent_catalogue_uuid} event
to a single shared topic. List/detail LiveViews subscribe/0 once in
mount/3 (after connected?(socket)) and re-fetch the affected
slice on any event, so two admins editing the same data converge
without manual refresh.
parent_catalogue_uuid lets a detail LV cheaply ignore broadcasts
for unrelated catalogues — without it, every item edit anywhere in
the system would force every open detail page to reload its slice.
Global resources (manufacturers, suppliers, manufacturer↔supplier
links) carry nil here; consumers that care about them subscribe
to the kind regardless of parent.
Payloads are intentionally minimal — UUID + kind + parent, no record data — to (a) avoid leaking field-level changes through PubSub, and (b) keep the consumer in charge of how much to re-load (single row vs full list).
Subscriptions are cleaned up automatically when the LV process terminates; callers don't need to unsubscribe.
Summary
Functions
Broadcasts a {:catalogue_data_changed, kind, uuid, parent_catalogue_uuid}
event after a successful write.
Broadcasts a bulk-change event so other open detail pages animate the affected items leaving / arriving on screen.
Broadcasts a card-refresh event so other open detail pages re-fetch a single category card's items after a reorder.
Broadcasts a category-reorder event so other open detail pages
re-fetch the category list (positions changed). Heavier than
broadcast_card_refresh/5 — receivers do a full reset_and_load
since category order affects every streamed card on the page.
Subscribes the current process to the catalogue PubSub topic.
Returns the canonical topic name. Useful for tests.
Types
@type event() :: {:catalogue_data_changed, kind(), Ecto.UUID.t() | nil, Ecto.UUID.t() | nil}
Event message format for handle_info/2.
@type kind() ::
:catalogue
| :category
| :item
| :manufacturer
| :supplier
| :smart_rule
| :links
| :pdf
Resource kind that mutated.
Functions
@spec broadcast(kind(), Ecto.UUID.t() | nil, Ecto.UUID.t() | nil) :: :ok
Broadcasts a {:catalogue_data_changed, kind, uuid, parent_catalogue_uuid}
event after a successful write.
uuid— UUID of the resource that mutated;nilwhen the change isn't tied to a specific record (e.g. a bulk link sync).parent_catalogue_uuid— UUID of the catalogue that contains the mutated resource, or the UUID itself forkind: :catalogueevents. Passnilfor resources that aren't scoped to a single catalogue (:manufacturer,:supplier,:links); detail LVs use this to filter out cross-catalogue noise.
@spec broadcast_bulk_change( Ecto.UUID.t(), :trashed | :restored | :moved | :permanent_delete, [Ecto.UUID.t()], pid() ) :: :ok
Broadcasts a bulk-change event so other open detail pages animate the affected items leaving / arriving on screen.
kind:
:trashed— items are going away (red flash → state refresh).:restored— items are coming back (state refresh → green flash).:moved— items are leaving one scope and entering another (red flash on source DOM → state refresh → green flash on destination DOM).:permanent_delete— items are gone for good (same animation as:trashed; the row removal is harder to undo but the visual cue is the same).
uuids is the affected item list. The receiver does a full
reset_and_load since a bulk change can rearrange every visible card;
per-scope filtering would only be a worthwhile optimisation once
catalogues routinely render dozens of cards.
@spec broadcast_card_refresh( Ecto.UUID.t(), Ecto.UUID.t() | :uncategorized, Ecto.UUID.t() | nil, atom(), pid() ) :: :ok
Broadcasts a card-refresh event so other open detail pages re-fetch a single category card's items after a reorder.
scope is a category UUID or :uncategorized. from is the
originating process; receivers compare against self() to skip
self-originated events (the source LV already updated locally).
flash_uuid + flash_status let the receiver fire a
sortable:flash push_event keyed to the moved row, so a second
open tab sees the same green/red flash the originator did.
@spec broadcast_category_reorder( Ecto.UUID.t(), Ecto.UUID.t() | nil, atom(), pid() ) :: :ok
Broadcasts a category-reorder event so other open detail pages
re-fetch the category list (positions changed). Heavier than
broadcast_card_refresh/5 — receivers do a full reset_and_load
since category order affects every streamed card on the page.
@spec subscribe() :: :ok | {:error, term()}
Subscribes the current process to the catalogue PubSub topic.
Call from mount/3 guarded by connected?(socket) so the
disconnected (initial render) pass doesn't subscribe and never
unsubscribes. Do this after any subscription requirements but
before the initial DB load to avoid a race where a write between
the load and the subscribe leaves the UI stale.
@spec topic() :: String.t()
Returns the canonical topic name. Useful for tests.