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.
Broadcasts a shared-sort change for a global-sort table scope (see
PhoenixKitCatalogue.Web.ViewConfig.global_sort?/1), so every open
catalogues index switches to the new ordering live. from rides in the
message — the originating LV already applied the change locally and
skips its own event.
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
| :folder
| :manufacturer
| :supplier
| :smart_rule
| :links
| :pdf
| :item_supplier_info
| :attribute_group
| :attribute_set
| :supplier_field
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.
Broadcasts a shared-sort change for a global-sort table scope (see
PhoenixKitCatalogue.Web.ViewConfig.global_sort?/1), so every open
catalogues index switches to the new ordering live. from rides in the
message — the originating LV already applied the change locally and
skips its own event.
@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.