fix: fixed CSM-017
This commit is contained in:
37
CODESMELL.md
37
CODESMELL.md
@@ -282,23 +282,26 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### CSM-017 — `send(self(), ...)` Component Chatter
|
### ~~CSM-017 — `send(self(), ...)` Component Chatter~~ ✅ FIXED
|
||||||
- **Files:** 25+ call sites across editor components:
|
- **Fixed:** 2026-05-09
|
||||||
- `lib/bds/desktop/shell_live/script_editor.ex` (3 sends)
|
- **What was done:**
|
||||||
- `lib/bds/desktop/shell_live/post_editor.ex` (2 sends)
|
- Created `BDS.Desktop.ShellLive.Notify` — a single dispatch module that standardizes all parent communication from LiveComponent editors. Provides typed functions: `output/3`, `output/4`, `tab_meta/4`, `tab_meta_merge/3`, `close_tab/2`, `reload/0`, `dirty/3`, `command/2`, `open_sidebar_item/2`, and `parent/1` (escape hatch for chat-specific messages).
|
||||||
- `lib/bds/desktop/shell_live/template_editor.ex` (3 sends)
|
- Replaced all 25+ `send(self(), ...)` calls across 11 editor components with `Notify.*` calls:
|
||||||
- `lib/bds/desktop/shell_live/media_editor.ex` (2 sends)
|
- `post_editor.ex` — 13 calls (dirty, tab_meta, close_tab, output)
|
||||||
- `lib/bds/desktop/shell_live/chat_editor.ex` (1 send)
|
- `media_editor.ex` — 7 calls (dirty, tab_meta, output)
|
||||||
- `lib/bds/desktop/shell_live/menu_editor.ex` (1 send)
|
- `chat_editor.ex` — 15 calls (output, tab_meta, open_sidebar_item, plus chat-specific via `Notify.parent`)
|
||||||
- `lib/bds/desktop/shell_live/settings_editor.ex` (2 sends)
|
- `template_editor.ex` — 3 calls (close_tab, output, reload)
|
||||||
- `lib/bds/desktop/shell_live/misc_editor.ex` (4 sends)
|
- `script_editor.ex` — 3 calls (close_tab, output, reload)
|
||||||
- `lib/bds/desktop/shell_live/tags_editor.ex` (2 sends)
|
- `misc_editor.ex` — 4 calls (command, output, tab_meta_merge, open_sidebar_item)
|
||||||
- `lib/bds/desktop/shell_live/import_editor.ex` (1 send)
|
- `settings_editor.ex` — 2 calls (output, parent)
|
||||||
- `lib/bds/desktop/shell_live/overlay_manager.ex` (3 sends)
|
- `tags_editor.ex` — 2 calls (output, parent)
|
||||||
- `lib/bds/desktop/main_window.ex` (1 send)
|
- `menu_editor.ex` — 1 call (output)
|
||||||
- **What:** Components send messages to the parent via `send(self(), ...)`, forcing a broad `handle_info` in `ShellLive`. Each message type must be handled in the parent, creating tight coupling.
|
- `import_editor.ex` — 2 calls (tab_meta, output)
|
||||||
- **Fix:** Prefer `Phoenix.LiveView.send_update/2` for targeted component updates, or delegate through a single dispatch module that translates actions into specific state changes.
|
- `overlay_manager.ex` — 3 calls (parent for cross-component routing)
|
||||||
- **Test:** Refactor one component; assert it no longer uses `send(self(), ...)`.
|
- Consolidated Bridges from 30+ editor-specific `handle_info` clauses to 4 generic handlers: `{:editor_output, ...}`, `{:editor_tab_meta, ...}`, `{:editor_dirty, ...}`, `{:editor_command, ...}`.
|
||||||
|
- Removed 18 editor-specific message atoms from Bridges (`:post_editor_output`, `:media_editor_output`, `:post_editor_dirty`, `:media_editor_dirty`, `:post_editor_tab_meta`, etc.).
|
||||||
|
- Kept chat-specific messages (`{:chat_editor_task_started, ...}`, `{:chat_editor_toggle_sidebar}`, etc.) and cross-component routing (`{:post_editor_insert_content, ...}`) in Bridges since they originate from AI streaming or overlay actions, not from editor self-notification.
|
||||||
|
- Added 24 tests in `test/bds/csm017_component_chatter_test.exs`: 11 source-level tests asserting no `send(self(), ...)` in any editor file, 1 aggregate test verifying all shell_live `send(self(), ...)` calls are in `notify.ex`, 2 Bridges tests verifying old patterns are gone and new generic handlers exist, 10 Notify API tests verifying each function sends the correct message.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -9,25 +9,73 @@ defmodule BDS.Desktop.ShellLive.Bridges do
|
|||||||
alias BDS.Desktop.ShellLive.{CliSync, SessionUtil}
|
alias BDS.Desktop.ShellLive.{CliSync, SessionUtil}
|
||||||
alias BDS.UI.Workbench
|
alias BDS.UI.Workbench
|
||||||
|
|
||||||
|
@refreshable_tab_meta_types [:import, :chat]
|
||||||
|
|
||||||
@spec handle_info(tuple() | atom(), Phoenix.LiveView.Socket.t(), map()) ::
|
@spec handle_info(tuple() | atom(), Phoenix.LiveView.Socket.t(), map()) ::
|
||||||
{:noreply, Phoenix.LiveView.Socket.t()}
|
{:noreply, Phoenix.LiveView.Socket.t()}
|
||||||
def handle_info({:import_editor_output, title, message, level}, socket, callbacks) do
|
|
||||||
{:noreply, callbacks.append_output.(socket, title, message, nil, level)}
|
# ── Generic editor notifications (sent via Notify module) ────────────────
|
||||||
|
|
||||||
|
def handle_info({:editor_output, title, message, detail, level}, socket, callbacks) do
|
||||||
|
{:noreply, callbacks.append_output.(socket, title, message, detail, level)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:import_editor_tab_meta, definition_id, title, subtitle}, socket, callbacks) do
|
def handle_info({:editor_tab_meta, type, id, updates}, socket, callbacks)
|
||||||
tab_meta =
|
when is_atom(type) and is_map(updates) do
|
||||||
Map.put(socket.assigns.tab_meta, {:import, definition_id}, %{
|
key = {type, id}
|
||||||
title: title,
|
current_meta = Map.get(socket.assigns.tab_meta, key, %{})
|
||||||
subtitle: subtitle || ""
|
next_meta = Map.merge(current_meta, updates)
|
||||||
})
|
tab_meta = Map.put(socket.assigns.tab_meta, key, next_meta)
|
||||||
|
|
||||||
|
socket = assign(socket, :tab_meta, tab_meta)
|
||||||
|
|
||||||
|
if type in @refreshable_tab_meta_types do
|
||||||
|
{:noreply, callbacks.refresh_sidebar.(socket, socket.assigns.workbench)}
|
||||||
|
else
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info({:editor_dirty, type, id, dirty?}, socket, _callbacks) do
|
||||||
|
workbench =
|
||||||
|
if dirty? do
|
||||||
|
Workbench.mark_dirty(socket.assigns.workbench, type, id)
|
||||||
|
else
|
||||||
|
Workbench.clear_dirty(socket.assigns.workbench, type, id)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:noreply, assign(socket, :workbench, workbench)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info({:editor_command, action, params}, socket, callbacks) do
|
||||||
|
{:noreply, callbacks.apply_shell_command.(socket, action, params)}
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Shared actions (already generic) ─────────────────────────────────────
|
||||||
|
|
||||||
|
def handle_info({:open_sidebar_item, params, intent}, socket, callbacks) do
|
||||||
|
{:noreply, callbacks.open_sidebar.(socket, params, intent)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info(:reload_shell, socket, callbacks) do
|
||||||
|
{:noreply, callbacks.reload.(socket, socket.assigns.workbench)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info({:close_tab, type, id}, socket, callbacks) do
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
callbacks.refresh_layout.(socket, Workbench.close_tab(socket.assigns.workbench, type, id))}
|
||||||
|> assign(:tab_meta, tab_meta)
|
|
||||||
|> callbacks.refresh_sidebar.(socket.assigns.workbench)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_info(:tags_changed, socket, callbacks) do
|
||||||
|
{:noreply, callbacks.refresh_content.(socket, socket.assigns.workbench)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info(:settings_changed, socket, callbacks) do
|
||||||
|
{:noreply, callbacks.reload.(socket, socket.assigns.workbench)}
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Chat editor messages (sent from AI streaming, not from Notify) ──────
|
||||||
|
|
||||||
def handle_info({:chat_tool_call, conversation_id, tool_call}, socket, _callbacks) do
|
def handle_info({:chat_tool_call, conversation_id, tool_call}, socket, _callbacks) do
|
||||||
send_update(ChatEditor,
|
send_update(ChatEditor,
|
||||||
id: "chat-editor-#{conversation_id}",
|
id: "chat-editor-#{conversation_id}",
|
||||||
@@ -68,27 +116,6 @@ defmodule BDS.Desktop.ShellLive.Bridges do
|
|||||||
{:noreply, assign(socket, :chat_editor_request_refs, refs)}
|
{:noreply, assign(socket, :chat_editor_request_refs, refs)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:chat_editor_output, title, message, level}, socket, callbacks) do
|
|
||||||
{:noreply, callbacks.append_output.(socket, title, message, nil, level)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:chat_editor_tab_meta, conversation_id, title, subtitle}, socket, callbacks) do
|
|
||||||
tab_meta =
|
|
||||||
Map.put(socket.assigns.tab_meta, {:chat, conversation_id}, %{
|
|
||||||
title: title,
|
|
||||||
subtitle: subtitle || ""
|
|
||||||
})
|
|
||||||
|
|
||||||
{:noreply,
|
|
||||||
socket
|
|
||||||
|> assign(:tab_meta, tab_meta)
|
|
||||||
|> callbacks.refresh_sidebar.(socket.assigns.workbench)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:open_sidebar_item, params, intent}, socket, callbacks) do
|
|
||||||
{:noreply, callbacks.open_sidebar.(socket, params, intent)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:chat_editor_toggle_sidebar}, socket, callbacks) do
|
def handle_info({:chat_editor_toggle_sidebar}, socket, callbacks) do
|
||||||
{:noreply,
|
{:noreply,
|
||||||
callbacks.refresh_layout.(socket, Workbench.toggle_sidebar(socket.assigns.workbench))}
|
callbacks.refresh_layout.(socket, Workbench.toggle_sidebar(socket.assigns.workbench))}
|
||||||
@@ -112,6 +139,35 @@ defmodule BDS.Desktop.ShellLive.Bridges do
|
|||||||
callbacks.refresh_sidebar.(socket, Workbench.click_activity(socket.assigns.workbench, view))}
|
callbacks.refresh_sidebar.(socket, Workbench.click_activity(socket.assigns.workbench, view))}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# ── Post editor cross-component messages (sent from OverlayManager) ─────
|
||||||
|
|
||||||
|
def handle_info({:post_editor_insert_content, post_id, content}, socket, _callbacks) do
|
||||||
|
send_update(PostEditor,
|
||||||
|
id: "post-editor-#{post_id}",
|
||||||
|
action: :insert_content,
|
||||||
|
content: content
|
||||||
|
)
|
||||||
|
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info({:post_editor_translate, post_id, language}, socket, _callbacks) do
|
||||||
|
send_update(PostEditor, id: "post-editor-#{post_id}", action: :translate, language: language)
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info({:post_editor_apply_ai_suggestions, post_id, fields}, socket, _callbacks) do
|
||||||
|
send_update(PostEditor,
|
||||||
|
id: "post-editor-#{post_id}",
|
||||||
|
action: :apply_ai_suggestions,
|
||||||
|
fields: fields
|
||||||
|
)
|
||||||
|
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── External system messages ─────────────────────────────────────────────
|
||||||
|
|
||||||
def handle_info({:entity_changed, payload}, socket, callbacks) when is_map(payload) do
|
def handle_info({:entity_changed, payload}, socket, callbacks) when is_map(payload) do
|
||||||
{:noreply, CliSync.apply_entity_change(socket, payload, callbacks.refresh_content)}
|
{:noreply, CliSync.apply_entity_change(socket, payload, callbacks.refresh_content)}
|
||||||
end
|
end
|
||||||
@@ -155,126 +211,5 @@ defmodule BDS.Desktop.ShellLive.Bridges do
|
|||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:tags_editor_output, title, message, level}, socket, callbacks) do
|
|
||||||
{:noreply, callbacks.append_output.(socket, title, message, nil, level)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info(:tags_changed, socket, callbacks) do
|
|
||||||
{:noreply, callbacks.refresh_content.(socket, socket.assigns.workbench)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:settings_output, title, message, level}, socket, callbacks) do
|
|
||||||
{:noreply, callbacks.append_output.(socket, title, message, nil, level)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info(:settings_changed, socket, callbacks) do
|
|
||||||
{:noreply, callbacks.reload.(socket, socket.assigns.workbench)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:menu_editor_output, title, message, level}, socket, callbacks) do
|
|
||||||
{:noreply, callbacks.append_output.(socket, title, message, nil, level)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:script_editor_output, title, message, level}, socket, callbacks) do
|
|
||||||
{:noreply, callbacks.append_output.(socket, title, message, nil, level)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:template_editor_output, title, message, level}, socket, callbacks) do
|
|
||||||
{:noreply, callbacks.append_output.(socket, title, message, nil, level)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:misc_editor_output, title, message, _detail, level}, socket, callbacks) do
|
|
||||||
{:noreply, callbacks.append_output.(socket, title, message, nil, level)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:misc_editor_command, action, params}, socket, callbacks) do
|
|
||||||
{:noreply, callbacks.apply_shell_command.(socket, action, params)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:misc_editor_tab_meta, tab_type, tab_id, updates}, socket, _callbacks) do
|
|
||||||
key = {tab_type, tab_id}
|
|
||||||
current_meta = Map.get(socket.assigns.tab_meta, key, %{})
|
|
||||||
next_meta = Map.merge(current_meta, updates)
|
|
||||||
{:noreply, assign(socket, :tab_meta, Map.put(socket.assigns.tab_meta, key, next_meta))}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:post_editor_output, title, message, level}, socket, callbacks) do
|
|
||||||
{:noreply, callbacks.append_output.(socket, title, message, nil, level)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:post_editor_dirty, post_id, dirty?}, socket, _callbacks) do
|
|
||||||
workbench =
|
|
||||||
if dirty? do
|
|
||||||
Workbench.mark_dirty(socket.assigns.workbench, :post, post_id)
|
|
||||||
else
|
|
||||||
Workbench.clear_dirty(socket.assigns.workbench, :post, post_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:noreply, assign(socket, :workbench, workbench)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:post_editor_tab_meta, post_id, title, subtitle}, socket, _callbacks) do
|
|
||||||
tab_meta =
|
|
||||||
Map.put(socket.assigns.tab_meta, {:post, post_id}, %{title: title, subtitle: subtitle})
|
|
||||||
|
|
||||||
{:noreply, assign(socket, :tab_meta, tab_meta)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:post_editor_insert_content, post_id, content}, socket, _callbacks) do
|
|
||||||
send_update(PostEditor,
|
|
||||||
id: "post-editor-#{post_id}",
|
|
||||||
action: :insert_content,
|
|
||||||
content: content
|
|
||||||
)
|
|
||||||
|
|
||||||
{:noreply, socket}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:post_editor_translate, post_id, language}, socket, _callbacks) do
|
|
||||||
send_update(PostEditor, id: "post-editor-#{post_id}", action: :translate, language: language)
|
|
||||||
{:noreply, socket}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:post_editor_apply_ai_suggestions, post_id, fields}, socket, _callbacks) do
|
|
||||||
send_update(PostEditor,
|
|
||||||
id: "post-editor-#{post_id}",
|
|
||||||
action: :apply_ai_suggestions,
|
|
||||||
fields: fields
|
|
||||||
)
|
|
||||||
|
|
||||||
{:noreply, socket}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:media_editor_output, title, message, level}, socket, callbacks) do
|
|
||||||
{:noreply, callbacks.append_output.(socket, title, message, nil, level)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:media_editor_dirty, media_id, dirty?}, socket, _callbacks) do
|
|
||||||
workbench =
|
|
||||||
if dirty? do
|
|
||||||
Workbench.mark_dirty(socket.assigns.workbench, :media, media_id)
|
|
||||||
else
|
|
||||||
Workbench.clear_dirty(socket.assigns.workbench, :media, media_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:noreply, assign(socket, :workbench, workbench)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:media_editor_tab_meta, media_id, title, subtitle}, socket, _callbacks) do
|
|
||||||
tab_meta =
|
|
||||||
Map.put(socket.assigns.tab_meta, {:media, media_id}, %{title: title, subtitle: subtitle})
|
|
||||||
|
|
||||||
{:noreply, assign(socket, :tab_meta, tab_meta)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info(:reload_shell, socket, callbacks) do
|
|
||||||
{:noreply, callbacks.reload.(socket, socket.assigns.workbench)}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:close_tab, type, id}, socket, callbacks) do
|
|
||||||
{:noreply,
|
|
||||||
callbacks.refresh_layout.(socket, Workbench.close_tab(socket.assigns.workbench, type, id))}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info(_message, socket, _callbacks), do: {:noreply, socket}
|
def handle_info(_message, socket, _callbacks), do: {:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
|||||||
|
|
||||||
alias BDS.{AI, BoundedAtoms, MapUtils, Persistence}
|
alias BDS.{AI, BoundedAtoms, MapUtils, Persistence}
|
||||||
alias BDS.Desktop.ShellLive.ChatEditor.{MessageBuild, ModelSelection, ToolTracking}
|
alias BDS.Desktop.ShellLive.ChatEditor.{MessageBuild, ModelSelection, ToolTracking}
|
||||||
|
alias BDS.Desktop.ShellLive.Notify
|
||||||
alias BDS.Desktop.ShellLive.TabHelpers
|
alias BDS.Desktop.ShellLive.TabHelpers
|
||||||
use Gettext, backend: BDS.Gettext
|
use Gettext, backend: BDS.Gettext
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
|||||||
{:noreply, assign(socket, :model_selector_open?, false) |> build_data()}
|
{:noreply, assign(socket, :model_selector_open?, false) |> build_data()}
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
notify_parent({:chat_editor_output, dgettext("ui", "Chat"), inspect(reason), "error"})
|
Notify.output(dgettext("ui", "Chat"), inspect(reason), "error")
|
||||||
{:noreply, assign(socket, :model_selector_open?, false) |> build_data()}
|
{:noreply, assign(socket, :model_selector_open?, false) |> build_data()}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -129,14 +130,14 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("open_chat_settings", _params, socket) do
|
def handle_event("open_chat_settings", _params, socket) do
|
||||||
notify_parent(
|
Notify.open_sidebar_item(
|
||||||
{:open_sidebar_item,
|
|
||||||
%{
|
%{
|
||||||
"route" => "settings",
|
"route" => "settings",
|
||||||
"id" => "settings-ai",
|
"id" => "settings-ai",
|
||||||
"title" => "Settings",
|
"title" => "Settings",
|
||||||
"subtitle" => "AI"
|
"subtitle" => "AI"
|
||||||
}, :pin}
|
},
|
||||||
|
:pin
|
||||||
)
|
)
|
||||||
|
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
@@ -203,10 +204,8 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
|||||||
build_data(socket)
|
build_data(socket)
|
||||||
|
|
||||||
socket.assigns.offline_mode ->
|
socket.assigns.offline_mode ->
|
||||||
notify_parent(
|
Notify.output(dgettext("ui", "Chat"),
|
||||||
{:chat_editor_output, dgettext("ui", "Chat"),
|
dgettext("ui", "Automatic AI actions stay gated by airplane mode."), "info")
|
||||||
dgettext("ui", "Automatic AI actions stay gated by airplane mode."), "info"}
|
|
||||||
)
|
|
||||||
|
|
||||||
build_data(socket)
|
build_data(socket)
|
||||||
|
|
||||||
@@ -227,7 +226,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
|||||||
|
|
||||||
:ok = allow_repo_sandbox(task.pid)
|
:ok = allow_repo_sandbox(task.pid)
|
||||||
|
|
||||||
notify_parent({:chat_editor_task_started, conversation_id, task.ref})
|
Notify.parent({:chat_editor_task_started, conversation_id, task.ref})
|
||||||
|
|
||||||
socket
|
socket
|
||||||
|> assign(:input, "")
|
|> assign(:input, "")
|
||||||
@@ -254,7 +253,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
|||||||
%{ref: ref} = _request ->
|
%{ref: ref} = _request ->
|
||||||
:ok = AI.cancel_chat(conversation_id)
|
:ok = AI.cancel_chat(conversation_id)
|
||||||
|
|
||||||
notify_parent({:chat_editor_task_cancelled, conversation_id, ref})
|
Notify.parent({:chat_editor_task_cancelled, conversation_id, ref})
|
||||||
|
|
||||||
socket
|
socket
|
||||||
|> assign(:request, nil)
|
|> assign(:request, nil)
|
||||||
@@ -293,9 +292,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
|||||||
assign(socket, :request, nil) |> build_data()
|
assign(socket, :request, nil) |> build_data()
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
notify_parent(
|
Notify.output(dgettext("ui", "Chat"), format_error(reason), "error")
|
||||||
{:chat_editor_output, dgettext("ui", "Chat"), format_error(reason), "error"}
|
|
||||||
)
|
|
||||||
|
|
||||||
assign(socket, :request, nil) |> build_data()
|
assign(socket, :request, nil) |> build_data()
|
||||||
end
|
end
|
||||||
@@ -347,7 +344,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
|||||||
|> MapUtils.attr(:title)
|
|> MapUtils.attr(:title)
|
||||||
|
|
||||||
if is_binary(title) and String.trim(title) != "" do
|
if is_binary(title) and String.trim(title) != "" do
|
||||||
notify_parent({:chat_editor_tab_meta, socket.assigns.conversation_id, title, ""})
|
Notify.tab_meta(:chat, socket.assigns.conversation_id, title, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
socket
|
socket
|
||||||
@@ -368,14 +365,14 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
|||||||
:open_post ->
|
:open_post ->
|
||||||
case Map.get(payload, "postId") || Map.get(payload, "post_id") do
|
case Map.get(payload, "postId") || Map.get(payload, "post_id") do
|
||||||
post_id when is_binary(post_id) and post_id != "" ->
|
post_id when is_binary(post_id) and post_id != "" ->
|
||||||
notify_parent(
|
Notify.open_sidebar_item(
|
||||||
{:open_sidebar_item,
|
|
||||||
%{
|
%{
|
||||||
"route" => "post",
|
"route" => "post",
|
||||||
"id" => post_id,
|
"id" => post_id,
|
||||||
"title" => TabHelpers.post_title(post_id),
|
"title" => TabHelpers.post_title(post_id),
|
||||||
"subtitle" => TabHelpers.post_subtitle(post_id)
|
"subtitle" => TabHelpers.post_subtitle(post_id)
|
||||||
}, :pin}
|
},
|
||||||
|
:pin
|
||||||
)
|
)
|
||||||
|
|
||||||
assign(socket, :action_error, nil) |> build_data()
|
assign(socket, :action_error, nil) |> build_data()
|
||||||
@@ -387,14 +384,14 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
|||||||
:open_media ->
|
:open_media ->
|
||||||
case Map.get(payload, "mediaId") || Map.get(payload, "media_id") do
|
case Map.get(payload, "mediaId") || Map.get(payload, "media_id") do
|
||||||
media_id when is_binary(media_id) and media_id != "" ->
|
media_id when is_binary(media_id) and media_id != "" ->
|
||||||
notify_parent(
|
Notify.open_sidebar_item(
|
||||||
{:open_sidebar_item,
|
|
||||||
%{
|
%{
|
||||||
"route" => "media",
|
"route" => "media",
|
||||||
"id" => media_id,
|
"id" => media_id,
|
||||||
"title" => TabHelpers.media_title(media_id),
|
"title" => TabHelpers.media_title(media_id),
|
||||||
"subtitle" => TabHelpers.media_subtitle(media_id)
|
"subtitle" => TabHelpers.media_subtitle(media_id)
|
||||||
}, :pin}
|
},
|
||||||
|
:pin
|
||||||
)
|
)
|
||||||
|
|
||||||
assign(socket, :action_error, nil) |> build_data()
|
assign(socket, :action_error, nil) |> build_data()
|
||||||
@@ -404,14 +401,14 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
|||||||
end
|
end
|
||||||
|
|
||||||
:open_settings ->
|
:open_settings ->
|
||||||
notify_parent(
|
Notify.open_sidebar_item(
|
||||||
{:open_sidebar_item,
|
|
||||||
%{
|
%{
|
||||||
"route" => "settings",
|
"route" => "settings",
|
||||||
"id" => "settings-ai",
|
"id" => "settings-ai",
|
||||||
"title" => "Settings",
|
"title" => "Settings",
|
||||||
"subtitle" => "AI"
|
"subtitle" => "AI"
|
||||||
}, :pin}
|
},
|
||||||
|
:pin
|
||||||
)
|
)
|
||||||
|
|
||||||
assign(socket, :action_error, nil) |> build_data()
|
assign(socket, :action_error, nil) |> build_data()
|
||||||
@@ -421,14 +418,14 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
|||||||
Map.get(payload, "conversationId") || Map.get(payload, "conversation_id") ||
|
Map.get(payload, "conversationId") || Map.get(payload, "conversation_id") ||
|
||||||
socket.assigns.conversation_id
|
socket.assigns.conversation_id
|
||||||
|
|
||||||
notify_parent(
|
Notify.open_sidebar_item(
|
||||||
{:open_sidebar_item,
|
|
||||||
%{
|
%{
|
||||||
"route" => "chat",
|
"route" => "chat",
|
||||||
"id" => chat_id,
|
"id" => chat_id,
|
||||||
"title" => Map.get(payload, "title", "Chat"),
|
"title" => Map.get(payload, "title", "Chat"),
|
||||||
"subtitle" => Map.get(payload, "subtitle", "")
|
"subtitle" => Map.get(payload, "subtitle", "")
|
||||||
}, :pin}
|
},
|
||||||
|
:pin
|
||||||
)
|
)
|
||||||
|
|
||||||
assign(socket, :action_error, nil) |> build_data()
|
assign(socket, :action_error, nil) |> build_data()
|
||||||
@@ -439,20 +436,20 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
|||||||
set_action_error(socket, "Invalid payload for switchView action")
|
set_action_error(socket, "Invalid payload for switchView action")
|
||||||
|
|
||||||
view ->
|
view ->
|
||||||
notify_parent({:chat_editor_switch_view, view})
|
Notify.parent({:chat_editor_switch_view, view})
|
||||||
assign(socket, :action_error, nil) |> build_data()
|
assign(socket, :action_error, nil) |> build_data()
|
||||||
end
|
end
|
||||||
|
|
||||||
:toggle_sidebar ->
|
:toggle_sidebar ->
|
||||||
notify_parent({:chat_editor_toggle_sidebar})
|
Notify.parent({:chat_editor_toggle_sidebar})
|
||||||
assign(socket, :action_error, nil) |> build_data()
|
assign(socket, :action_error, nil) |> build_data()
|
||||||
|
|
||||||
:toggle_panel ->
|
:toggle_panel ->
|
||||||
notify_parent({:chat_editor_toggle_panel})
|
Notify.parent({:chat_editor_toggle_panel})
|
||||||
assign(socket, :action_error, nil) |> build_data()
|
assign(socket, :action_error, nil) |> build_data()
|
||||||
|
|
||||||
:toggle_assistant_sidebar ->
|
:toggle_assistant_sidebar ->
|
||||||
notify_parent({:chat_editor_toggle_assistant_sidebar})
|
Notify.parent({:chat_editor_toggle_assistant_sidebar})
|
||||||
assign(socket, :action_error, nil) |> build_data()
|
assign(socket, :action_error, nil) |> build_data()
|
||||||
|
|
||||||
:unknown ->
|
:unknown ->
|
||||||
@@ -822,9 +819,6 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
|
|||||||
|
|
||||||
# ── Private helpers ───────────────────────────────────────────────────────
|
# ── Private helpers ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
defp notify_parent(message) do
|
|
||||||
send(self(), message)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp active_project_id(socket) do
|
defp active_project_id(socket) do
|
||||||
socket.assigns[:project_id]
|
socket.assigns[:project_id]
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
|||||||
|
|
||||||
alias BDS.{AI, ImportAnalysis, ImportDefinitions, ImportExecution}
|
alias BDS.{AI, ImportAnalysis, ImportDefinitions, ImportExecution}
|
||||||
alias BDS.Desktop.{FilePicker, FolderPicker, ShellData}
|
alias BDS.Desktop.{FilePicker, FolderPicker, ShellData}
|
||||||
|
alias BDS.Desktop.ShellLive.Notify
|
||||||
|
|
||||||
alias BDS.Desktop.ShellLive.ImportEditor.{
|
alias BDS.Desktop.ShellLive.ImportEditor.{
|
||||||
AnalysisState,
|
AnalysisState,
|
||||||
@@ -641,12 +642,11 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
|||||||
defp maybe_update_tab_meta(socket, name) do
|
defp maybe_update_tab_meta(socket, name) do
|
||||||
title = name || dgettext("ui", "Untitled Import")
|
title = name || dgettext("ui", "Untitled Import")
|
||||||
|
|
||||||
notify_parent(
|
Notify.tab_meta(:import, socket.assigns.definition_id, title,
|
||||||
{:import_editor_tab_meta, socket.assigns.definition_id, title,
|
|
||||||
dgettext(
|
dgettext(
|
||||||
"ui",
|
"ui",
|
||||||
"Select a WordPress export file (WXR) and an uploads folder to analyze what would be imported."
|
"Select a WordPress export file (WXR) and an uploads folder to analyze what would be imported."
|
||||||
)}
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
socket
|
socket
|
||||||
@@ -1404,12 +1404,8 @@ defmodule BDS.Desktop.ShellLive.ImportEditor do
|
|||||||
|
|
||||||
# ── Private helpers ───────────────────────────────────────────────────────
|
# ── Private helpers ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
defp notify_parent(message) do
|
|
||||||
send(self(), message)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp notify_output(socket, title, message, level \\ "info") do
|
defp notify_output(socket, title, message, level \\ "info") do
|
||||||
notify_parent({:import_editor_output, title, message, level})
|
Notify.output(title, message, level)
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
|||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
alias BDS.Desktop.{FilePicker}
|
alias BDS.Desktop.{FilePicker}
|
||||||
|
alias BDS.Desktop.ShellLive.Notify
|
||||||
alias BDS.{AI, I18n, Media}
|
alias BDS.{AI, I18n, Media}
|
||||||
alias BDS.Media.Media, as: MediaRecord
|
alias BDS.Media.Media, as: MediaRecord
|
||||||
alias BDS.Media.Translation
|
alias BDS.Media.Translation
|
||||||
@@ -90,7 +91,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
|||||||
|> build_data()
|
|> build_data()
|
||||||
|
|
||||||
if dirty? != was_dirty? do
|
if dirty? != was_dirty? do
|
||||||
notify_parent({:media_editor_dirty, socket.assigns.media_id, dirty?})
|
Notify.dirty(:media, socket.assigns.media_id, dirty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
@@ -123,12 +124,10 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
|||||||
|> assign(:dirty?, false)
|
|> assign(:dirty?, false)
|
||||||
|> build_data()
|
|> build_data()
|
||||||
|
|
||||||
notify_parent({:media_editor_dirty, media.id, false})
|
Notify.dirty(:media, media.id, false)
|
||||||
|
|
||||||
notify_parent(
|
Notify.tab_meta(:media, media.id, display_title(updated_media),
|
||||||
{:media_editor_tab_meta, media.id, display_title(updated_media),
|
updated_media.original_name || updated_media.mime_type || "")
|
||||||
updated_media.original_name || updated_media.mime_type || ""}
|
|
||||||
)
|
|
||||||
|
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
|
|
||||||
@@ -483,12 +482,10 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
|||||||
|> assign(:save_state, :saved)
|
|> assign(:save_state, :saved)
|
||||||
|> build_data()
|
|> build_data()
|
||||||
|
|
||||||
notify_parent({:media_editor_dirty, media.id, false})
|
Notify.dirty(:media, media.id, false)
|
||||||
|
|
||||||
notify_parent(
|
Notify.tab_meta(:media, media.id, display_title(updated_media),
|
||||||
{:media_editor_tab_meta, media.id, display_title(updated_media),
|
updated_media.original_name || updated_media.mime_type || "")
|
||||||
updated_media.original_name || updated_media.mime_type || ""}
|
|
||||||
)
|
|
||||||
|
|
||||||
notify_output(socket, dgettext("ui", "Media"), dgettext("ui", "Media saved"))
|
notify_output(socket, dgettext("ui", "Media"), dgettext("ui", "Media saved"))
|
||||||
socket
|
socket
|
||||||
@@ -528,7 +525,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
|||||||
|> assign(:quick_actions_open?, false)
|
|> assign(:quick_actions_open?, false)
|
||||||
|> build_data()
|
|> build_data()
|
||||||
|
|
||||||
notify_parent({:media_editor_dirty, media.id, dirty?})
|
Notify.dirty(:media, media.id, dirty?)
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -569,12 +566,8 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp notify_parent(message) do
|
|
||||||
send(self(), message)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp notify_output(socket, title, message, level \\ "info") do
|
defp notify_output(socket, title, message, level \\ "info") do
|
||||||
send(self(), {:media_editor_output, title, message, level})
|
Notify.output(title, message, level)
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
|
|||||||
|
|
||||||
use Gettext, backend: BDS.Gettext
|
use Gettext, backend: BDS.Gettext
|
||||||
|
|
||||||
|
alias BDS.Desktop.ShellLive.Notify
|
||||||
|
|
||||||
alias BDS.Desktop.ShellLive.MenuEditor.{
|
alias BDS.Desktop.ShellLive.MenuEditor.{
|
||||||
DraftManagement,
|
DraftManagement,
|
||||||
PageCategory,
|
PageCategory,
|
||||||
@@ -251,7 +253,7 @@ defmodule BDS.Desktop.ShellLive.MenuEditor do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp notify_output(title, message, level) do
|
defp notify_output(title, message, level) do
|
||||||
send(self(), {:menu_editor_output, title, message, level})
|
Notify.output(title, message, level)
|
||||||
end
|
end
|
||||||
|
|
||||||
attr(:menu_editor, :map, required: true)
|
attr(:menu_editor, :map, required: true)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
|||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
alias BDS.{Embeddings, Generation, Git, HelpDocs, Posts, Repo}
|
alias BDS.{Embeddings, Generation, Git, HelpDocs, Posts, Repo}
|
||||||
|
alias BDS.Desktop.ShellLive.Notify
|
||||||
alias BDS.MapUtils
|
alias BDS.MapUtils
|
||||||
alias BDS.Settings.Setting
|
alias BDS.Settings.Setting
|
||||||
use Gettext, backend: BDS.Gettext
|
use Gettext, backend: BDS.Gettext
|
||||||
@@ -358,19 +359,19 @@ defmodule BDS.Desktop.ShellLive.MiscEditor do
|
|||||||
# ── Private helpers ────────────────────────────────────────────────────────
|
# ── Private helpers ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
defp notify_command(action, params \\ %{}) do
|
defp notify_command(action, params \\ %{}) do
|
||||||
send(self(), {:misc_editor_command, action, params})
|
Notify.command(action, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp notify_output(title, message, detail \\ nil, level \\ "info") do
|
defp notify_output(title, message, detail \\ nil, level \\ "info") do
|
||||||
send(self(), {:misc_editor_output, title, message, detail, level})
|
Notify.output(title, message, detail, level)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp notify_tab_meta(tab_type, tab_id, updates) do
|
defp notify_tab_meta(tab_type, tab_id, updates) do
|
||||||
send(self(), {:misc_editor_tab_meta, tab_type, tab_id, updates})
|
Notify.tab_meta_merge(tab_type, tab_id, updates)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp notify_open_sidebar_item(params, intent) do
|
defp notify_open_sidebar_item(params, intent) do
|
||||||
send(self(), {:open_sidebar_item, params, intent})
|
Notify.open_sidebar_item(params, intent)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp rerun_action(assigns) do
|
defp rerun_action(assigns) do
|
||||||
|
|||||||
70
lib/bds/desktop/shell_live/notify.ex
Normal file
70
lib/bds/desktop/shell_live/notify.ex
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
defmodule BDS.Desktop.ShellLive.Notify do
|
||||||
|
@moduledoc """
|
||||||
|
Standardized parent notification API for LiveComponent editors.
|
||||||
|
|
||||||
|
Instead of each editor defining its own `notify_parent/1` and sending
|
||||||
|
editor-specific message atoms (e.g. `{:post_editor_output, ...}`),
|
||||||
|
all editors call functions from this module, which sends generic
|
||||||
|
messages that Bridges handles with a single clause per action type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@spec output(String.t(), String.t(), String.t()) :: :ok
|
||||||
|
def output(title, message, level) do
|
||||||
|
send(self(), {:editor_output, title, message, nil, level})
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec output(String.t(), String.t(), String.t() | nil, String.t()) :: :ok
|
||||||
|
def output(title, message, detail, level) do
|
||||||
|
send(self(), {:editor_output, title, message, detail, level})
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec tab_meta(atom(), term(), String.t(), String.t()) :: :ok
|
||||||
|
def tab_meta(type, id, title, subtitle) do
|
||||||
|
send(self(), {:editor_tab_meta, type, id, %{title: title, subtitle: subtitle || ""}})
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec tab_meta_merge(atom(), term(), map()) :: :ok
|
||||||
|
def tab_meta_merge(type, id, updates) when is_map(updates) do
|
||||||
|
send(self(), {:editor_tab_meta, type, id, updates})
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec close_tab(atom(), term()) :: :ok
|
||||||
|
def close_tab(type, id) do
|
||||||
|
send(self(), {:close_tab, type, id})
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec reload :: :ok
|
||||||
|
def reload do
|
||||||
|
send(self(), :reload_shell)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec dirty(atom(), term(), boolean()) :: :ok
|
||||||
|
def dirty(type, id, dirty?) do
|
||||||
|
send(self(), {:editor_dirty, type, id, dirty?})
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec command(atom() | String.t(), map()) :: :ok
|
||||||
|
def command(action, params \\ %{}) do
|
||||||
|
send(self(), {:editor_command, action, params})
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec open_sidebar_item(map(), atom() | nil) :: :ok
|
||||||
|
def open_sidebar_item(params, intent \\ nil) do
|
||||||
|
send(self(), {:open_sidebar_item, params, intent})
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec parent(term()) :: :ok
|
||||||
|
def parent(message) do
|
||||||
|
send(self(), message)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -11,6 +11,7 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
|
|||||||
|
|
||||||
alias BDS.Desktop.ShellLive.{
|
alias BDS.Desktop.ShellLive.{
|
||||||
MediaEditor,
|
MediaEditor,
|
||||||
|
Notify,
|
||||||
PostEditor,
|
PostEditor,
|
||||||
TabHelpers
|
TabHelpers
|
||||||
}
|
}
|
||||||
@@ -170,7 +171,7 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
|
|||||||
"[#{result.original_name}](bds-media://#{result.media_id})"
|
"[#{result.original_name}](bds-media://#{result.media_id})"
|
||||||
end
|
end
|
||||||
|
|
||||||
send(self(), {:post_editor_insert_content, post_id, syntax})
|
Notify.parent({:post_editor_insert_content, post_id, syntax})
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -195,7 +196,7 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
|
|||||||
end
|
end
|
||||||
|
|
||||||
if details do
|
if details do
|
||||||
send(self(), {:post_editor_insert_content, post_id, details})
|
Notify.parent({:post_editor_insert_content, post_id, details})
|
||||||
end
|
end
|
||||||
|
|
||||||
socket
|
socket
|
||||||
@@ -213,7 +214,7 @@ defmodule BDS.Desktop.ShellLive.OverlayManager do
|
|||||||
socket =
|
socket =
|
||||||
case {socket.assigns[:shell_overlay], current_tab} do
|
case {socket.assigns[:shell_overlay], current_tab} do
|
||||||
{%{kind: :language_picker}, %{type: :post, id: post_id}} ->
|
{%{kind: :language_picker}, %{type: :post, id: post_id}} ->
|
||||||
send(self(), {:post_editor_translate, post_id, code})
|
Notify.parent({:post_editor_translate, post_id, code})
|
||||||
socket
|
socket
|
||||||
|
|
||||||
{%{kind: :language_picker}, %{type: :media, id: media_id}} ->
|
{%{kind: :language_picker}, %{type: :media, id: media_id}} ->
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
|
|
||||||
alias BDS.{AI, Posts, Preview}
|
alias BDS.{AI, Posts, Preview}
|
||||||
alias BDS.Desktop.ShellData
|
alias BDS.Desktop.ShellData
|
||||||
|
alias BDS.Desktop.ShellLive.Notify
|
||||||
alias BDS.Desktop.ShellLive.PostEditor.{DraftManagement, ListValues, Persistence, PostMetadata}
|
alias BDS.Desktop.ShellLive.PostEditor.{DraftManagement, ListValues, Persistence, PostMetadata}
|
||||||
alias BDS.Posts.Post
|
alias BDS.Posts.Post
|
||||||
alias BDS.Tags
|
alias BDS.Tags
|
||||||
@@ -181,7 +182,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
|> build_data()
|
|> build_data()
|
||||||
|
|
||||||
if dirty? != was_dirty? do
|
if dirty? != was_dirty? do
|
||||||
notify_parent({:post_editor_dirty, post_id, dirty?})
|
Notify.dirty(:post, post_id, dirty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
@@ -456,12 +457,10 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
|> assign(:dirty?, false)
|
|> assign(:dirty?, false)
|
||||||
|> build_data()
|
|> build_data()
|
||||||
|
|
||||||
notify_parent(
|
Notify.tab_meta(:post, post.id, record_title(record, refreshed_post),
|
||||||
{:post_editor_tab_meta, post.id, record_title(record, refreshed_post),
|
Atom.to_string(record_status(record)))
|
||||||
Atom.to_string(record_status(record))}
|
|
||||||
)
|
|
||||||
|
|
||||||
notify_parent({:post_editor_dirty, post.id, false})
|
Notify.dirty(:post, post.id, false)
|
||||||
notify_output(socket, dgettext("ui", "Post"), dgettext("ui", "Post saved"))
|
notify_output(socket, dgettext("ui", "Post"), dgettext("ui", "Post saved"))
|
||||||
socket
|
socket
|
||||||
|
|
||||||
@@ -497,12 +496,10 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
|> assign(:dirty?, false)
|
|> assign(:dirty?, false)
|
||||||
|> build_data()
|
|> build_data()
|
||||||
|
|
||||||
notify_parent(
|
Notify.tab_meta(:post, post.id, record_title(record, refreshed_post),
|
||||||
{:post_editor_tab_meta, post.id, record_title(record, refreshed_post),
|
Atom.to_string(record_status(record)))
|
||||||
Atom.to_string(record_status(record))}
|
|
||||||
)
|
|
||||||
|
|
||||||
notify_parent({:post_editor_dirty, post.id, false})
|
Notify.dirty(:post, post.id, false)
|
||||||
notify_output(socket, dgettext("ui", "Post"), dgettext("ui", "Post published"))
|
notify_output(socket, dgettext("ui", "Post"), dgettext("ui", "Post published"))
|
||||||
socket
|
socket
|
||||||
|
|
||||||
@@ -534,13 +531,11 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
|> assign(:dirty?, false)
|
|> assign(:dirty?, false)
|
||||||
|> build_data()
|
|> build_data()
|
||||||
|
|
||||||
notify_parent(
|
Notify.tab_meta(:post, post.id,
|
||||||
{:post_editor_tab_meta, post.id,
|
|
||||||
restored_post.title || restored_post.slug || restored_post.id,
|
restored_post.title || restored_post.slug || restored_post.id,
|
||||||
Atom.to_string(restored_post.status || :draft)}
|
Atom.to_string(restored_post.status || :draft))
|
||||||
)
|
|
||||||
|
|
||||||
notify_parent({:post_editor_dirty, post.id, false})
|
Notify.dirty(:post, post.id, false)
|
||||||
socket
|
socket
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
@@ -555,7 +550,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
|
|
||||||
case Posts.delete_post(post_id) do
|
case Posts.delete_post(post_id) do
|
||||||
{:ok, :deleted} ->
|
{:ok, :deleted} ->
|
||||||
notify_parent({:close_tab, :post, post_id})
|
Notify.close_tab(:post, post_id)
|
||||||
socket
|
socket
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
@@ -642,7 +637,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
|> assign(:quick_actions_open?, false)
|
|> assign(:quick_actions_open?, false)
|
||||||
|> build_data()
|
|> build_data()
|
||||||
|
|
||||||
notify_parent({:post_editor_dirty, post_id, false})
|
Notify.dirty(:post, post_id, false)
|
||||||
socket
|
socket
|
||||||
else
|
else
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
@@ -698,7 +693,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
|> assign(:shell_overlay, nil)
|
|> assign(:shell_overlay, nil)
|
||||||
|> build_data()
|
|> build_data()
|
||||||
|
|
||||||
notify_parent({:post_editor_dirty, post_id, true})
|
Notify.dirty(:post, post_id, true)
|
||||||
socket
|
socket
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
@@ -741,7 +736,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
|> put_component_draft_field(field_key(kind), updated)
|
|> put_component_draft_field(field_key(kind), updated)
|
||||||
|> build_data()
|
|> build_data()
|
||||||
|
|
||||||
notify_parent({:post_editor_dirty, socket.assigns.post_id, true})
|
Notify.dirty(:post, socket.assigns.post_id, true)
|
||||||
assign(socket, :dirty?, true)
|
assign(socket, :dirty?, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -771,7 +766,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
|> put_component_draft_field(field_key(kind), updated)
|
|> put_component_draft_field(field_key(kind), updated)
|
||||||
|> build_data()
|
|> build_data()
|
||||||
|
|
||||||
notify_parent({:post_editor_dirty, socket.assigns.post_id, true})
|
Notify.dirty(:post, socket.assigns.post_id, true)
|
||||||
assign(socket, :dirty?, true)
|
assign(socket, :dirty?, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -807,12 +802,8 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
defp assign_query(socket, :tags, value), do: assign(socket, :tag_query, value)
|
defp assign_query(socket, :tags, value), do: assign(socket, :tag_query, value)
|
||||||
defp assign_query(socket, :categories, value), do: assign(socket, :category_query, value)
|
defp assign_query(socket, :categories, value), do: assign(socket, :category_query, value)
|
||||||
|
|
||||||
defp notify_parent(message) do
|
|
||||||
send(self(), message)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp notify_output(socket, title, message, level \\ "info") do
|
defp notify_output(socket, title, message, level \\ "info") do
|
||||||
send(self(), {:post_editor_output, title, message, level})
|
Notify.output(title, message, level)
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ defmodule BDS.Desktop.ShellLive.ScriptEditor do
|
|||||||
use Phoenix.LiveComponent
|
use Phoenix.LiveComponent
|
||||||
|
|
||||||
alias BDS.{Scripts, Scripting}
|
alias BDS.{Scripts, Scripting}
|
||||||
|
alias BDS.Desktop.ShellLive.Notify
|
||||||
alias BDS.Scripts.Script
|
alias BDS.Scripts.Script
|
||||||
use Gettext, backend: BDS.Gettext
|
use Gettext, backend: BDS.Gettext
|
||||||
|
|
||||||
@@ -225,7 +226,7 @@ defmodule BDS.Desktop.ShellLive.ScriptEditor do
|
|||||||
|
|
||||||
case Scripts.delete_script(script_id) do
|
case Scripts.delete_script(script_id) do
|
||||||
{:ok, _deleted} ->
|
{:ok, _deleted} ->
|
||||||
send(self(), {:close_tab, :scripts, script_id})
|
Notify.close_tab(:scripts, script_id)
|
||||||
socket
|
socket
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
@@ -282,12 +283,12 @@ defmodule BDS.Desktop.ShellLive.ScriptEditor do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp notify_output(socket, title, message, level \\ "info") do
|
defp notify_output(socket, title, message, level \\ "info") do
|
||||||
send(self(), {:script_editor_output, title, message, level})
|
Notify.output(title, message, level)
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
defp notify_reload(socket) do
|
defp notify_reload(socket) do
|
||||||
send(self(), :reload_shell)
|
Notify.reload()
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
|
|||||||
alias BDS.Desktop.ShellLive.SettingsEditor.AISettings
|
alias BDS.Desktop.ShellLive.SettingsEditor.AISettings
|
||||||
alias BDS.Desktop.ShellLive.SettingsEditor.EditorSettings
|
alias BDS.Desktop.ShellLive.SettingsEditor.EditorSettings
|
||||||
alias BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories
|
alias BDS.Desktop.ShellLive.SettingsEditor.ManagedCategories
|
||||||
|
alias BDS.Desktop.ShellLive.Notify
|
||||||
alias BDS.Desktop.ShellLive.SettingsEditor.MCPConfig
|
alias BDS.Desktop.ShellLive.SettingsEditor.MCPConfig
|
||||||
alias BDS.Desktop.ShellLive.SettingsEditor.ProjectSettings
|
alias BDS.Desktop.ShellLive.SettingsEditor.ProjectSettings
|
||||||
alias BDS.Desktop.ShellLive.SettingsEditor.PublishingSettings
|
alias BDS.Desktop.ShellLive.SettingsEditor.PublishingSettings
|
||||||
@@ -308,13 +309,13 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor do
|
|||||||
|
|
||||||
defp append_output_callback do
|
defp append_output_callback do
|
||||||
fn socket, title, message, _details, level ->
|
fn socket, title, message, _details, level ->
|
||||||
send(self(), {:settings_output, title, message, level})
|
Notify.output(title, message, level)
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp notify_parent(message) do
|
defp notify_parent(message) do
|
||||||
send(self(), message)
|
Notify.parent(message)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp current_settings_section(assigns) do
|
defp current_settings_section(assigns) do
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|
|||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
alias BDS.{Repo, Tags}
|
alias BDS.{Repo, Tags}
|
||||||
|
alias BDS.Desktop.ShellLive.Notify
|
||||||
alias BDS.Posts.Post
|
alias BDS.Posts.Post
|
||||||
alias BDS.Tags.Tag
|
alias BDS.Tags.Tag
|
||||||
alias BDS.Templates.Template
|
alias BDS.Templates.Template
|
||||||
@@ -292,11 +293,11 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
|
|||||||
defp noreply(socket), do: {:noreply, socket}
|
defp noreply(socket), do: {:noreply, socket}
|
||||||
|
|
||||||
defp notify_parent(message) do
|
defp notify_parent(message) do
|
||||||
send(self(), message)
|
Notify.parent(message)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp notify_output(title, message, level) do
|
defp notify_output(title, message, level) do
|
||||||
send(self(), {:tags_editor_output, title, message, level})
|
Notify.output(title, message, level)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec tag_font_size(term(), term()) :: term()
|
@spec tag_font_size(term(), term()) :: term()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ defmodule BDS.Desktop.ShellLive.TemplateEditor do
|
|||||||
use Phoenix.LiveComponent
|
use Phoenix.LiveComponent
|
||||||
|
|
||||||
alias BDS.{MCP, Templates}
|
alias BDS.{MCP, Templates}
|
||||||
|
alias BDS.Desktop.ShellLive.Notify
|
||||||
alias BDS.Templates.Template
|
alias BDS.Templates.Template
|
||||||
use Gettext, backend: BDS.Gettext
|
use Gettext, backend: BDS.Gettext
|
||||||
|
|
||||||
@@ -182,7 +183,7 @@ defmodule BDS.Desktop.ShellLive.TemplateEditor do
|
|||||||
|
|
||||||
case Templates.delete_template(template_id, force: true) do
|
case Templates.delete_template(template_id, force: true) do
|
||||||
{:ok, _deleted} ->
|
{:ok, _deleted} ->
|
||||||
send(self(), {:close_tab, :templates, template_id})
|
Notify.close_tab(:templates, template_id)
|
||||||
socket
|
socket
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
@@ -231,12 +232,12 @@ defmodule BDS.Desktop.ShellLive.TemplateEditor do
|
|||||||
defp normalize_template_kind(_kind), do: :post
|
defp normalize_template_kind(_kind), do: :post
|
||||||
|
|
||||||
defp notify_output(socket, title, message, level \\ "info") do
|
defp notify_output(socket, title, message, level \\ "info") do
|
||||||
send(self(), {:template_editor_output, title, message, level})
|
Notify.output(title, message, level)
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
defp notify_reload(socket) do
|
defp notify_reload(socket) do
|
||||||
send(self(), :reload_shell)
|
Notify.reload()
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
172
test/bds/csm017_component_chatter_test.exs
Normal file
172
test/bds/csm017_component_chatter_test.exs
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
defmodule BDS.CSM017ComponentChatterTest do
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
@editor_files [
|
||||||
|
"lib/bds/desktop/shell_live/post_editor.ex",
|
||||||
|
"lib/bds/desktop/shell_live/media_editor.ex",
|
||||||
|
"lib/bds/desktop/shell_live/template_editor.ex",
|
||||||
|
"lib/bds/desktop/shell_live/script_editor.ex",
|
||||||
|
"lib/bds/desktop/shell_live/chat_editor.ex",
|
||||||
|
"lib/bds/desktop/shell_live/settings_editor.ex",
|
||||||
|
"lib/bds/desktop/shell_live/menu_editor.ex",
|
||||||
|
"lib/bds/desktop/shell_live/tags_editor.ex",
|
||||||
|
"lib/bds/desktop/shell_live/misc_editor.ex",
|
||||||
|
"lib/bds/desktop/shell_live/import_editor.ex",
|
||||||
|
"lib/bds/desktop/shell_live/overlay_manager.ex"
|
||||||
|
]
|
||||||
|
|
||||||
|
describe "no direct send(self(), ...) in editor components" do
|
||||||
|
for file <- @editor_files do
|
||||||
|
test "#{Path.basename(file)} uses Notify instead of send(self(), ...)" do
|
||||||
|
path = Path.join(File.cwd!(), unquote(file))
|
||||||
|
content = File.read!(path)
|
||||||
|
|
||||||
|
lines =
|
||||||
|
content
|
||||||
|
|> String.split("\n")
|
||||||
|
|> Enum.with_index(1)
|
||||||
|
|> Enum.filter(fn {line, _num} ->
|
||||||
|
String.contains?(line, "send(self(),") and
|
||||||
|
not String.contains?(line, "Process.send_after") and
|
||||||
|
not String.contains?(line, "# send(self()")
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert lines == [],
|
||||||
|
"#{unquote(file)} still has direct send(self(), ...) calls at lines: #{inspect(Enum.map(lines, &elem(&1, 1)))}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Notify module is the single point of parent communication" do
|
||||||
|
test "all send(self(), ...) calls in shell_live/ are in notify.ex" do
|
||||||
|
shell_live_dir = Path.join(File.cwd!(), "lib/bds/desktop/shell_live")
|
||||||
|
|
||||||
|
send_self_files =
|
||||||
|
Path.wildcard(Path.join(shell_live_dir, "*.ex"))
|
||||||
|
|> Enum.filter(fn path ->
|
||||||
|
content = File.read!(path)
|
||||||
|
basename = Path.basename(path)
|
||||||
|
|
||||||
|
String.contains?(content, "send(self(),") and
|
||||||
|
not String.contains?(content, "Process.send_after(self(),") and
|
||||||
|
basename != "notify.ex" and
|
||||||
|
basename != "bridges.ex"
|
||||||
|
end)
|
||||||
|
|> Enum.reject(fn path ->
|
||||||
|
content = File.read!(path)
|
||||||
|
|
||||||
|
lines =
|
||||||
|
content
|
||||||
|
|> String.split("\n")
|
||||||
|
|> Enum.filter(fn line ->
|
||||||
|
String.contains?(line, "send(self(),") and
|
||||||
|
not String.contains?(line, "Process.send_after")
|
||||||
|
end)
|
||||||
|
|
||||||
|
Enum.empty?(lines)
|
||||||
|
end)
|
||||||
|
|> Enum.map(&Path.basename/1)
|
||||||
|
|
||||||
|
assert send_self_files == [],
|
||||||
|
"These files still have direct send(self(), ...) calls: #{inspect(send_self_files)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Bridges handles generic message types" do
|
||||||
|
test "no editor-specific output handlers remain in Bridges" do
|
||||||
|
bridges_path = Path.join(File.cwd!(), "lib/bds/desktop/shell_live/bridges.ex")
|
||||||
|
content = File.read!(bridges_path)
|
||||||
|
|
||||||
|
old_patterns = [
|
||||||
|
":import_editor_output",
|
||||||
|
":chat_editor_output",
|
||||||
|
":tags_editor_output",
|
||||||
|
":settings_output",
|
||||||
|
":menu_editor_output",
|
||||||
|
":script_editor_output",
|
||||||
|
":template_editor_output",
|
||||||
|
":misc_editor_output",
|
||||||
|
":post_editor_output",
|
||||||
|
":media_editor_output",
|
||||||
|
":post_editor_dirty",
|
||||||
|
":media_editor_dirty",
|
||||||
|
":post_editor_tab_meta",
|
||||||
|
":media_editor_tab_meta",
|
||||||
|
":import_editor_tab_meta",
|
||||||
|
":chat_editor_tab_meta",
|
||||||
|
":misc_editor_tab_meta",
|
||||||
|
":misc_editor_command"
|
||||||
|
]
|
||||||
|
|
||||||
|
remaining =
|
||||||
|
Enum.filter(old_patterns, fn pattern ->
|
||||||
|
String.contains?(content, pattern)
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert remaining == [],
|
||||||
|
"Bridges still contains editor-specific handlers: #{inspect(remaining)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Bridges has generic editor_output handler" do
|
||||||
|
bridges_path = Path.join(File.cwd!(), "lib/bds/desktop/shell_live/bridges.ex")
|
||||||
|
content = File.read!(bridges_path)
|
||||||
|
|
||||||
|
assert String.contains?(content, ":editor_output")
|
||||||
|
assert String.contains?(content, ":editor_tab_meta")
|
||||||
|
assert String.contains?(content, ":editor_dirty")
|
||||||
|
assert String.contains?(content, ":editor_command")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Notify module API" do
|
||||||
|
test "output/3 sends {:editor_output, ...} message" do
|
||||||
|
BDS.Desktop.ShellLive.Notify.output("Title", "Message", "info")
|
||||||
|
assert_received {:editor_output, "Title", "Message", nil, "info"}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "output/4 sends {:editor_output, ...} with detail" do
|
||||||
|
BDS.Desktop.ShellLive.Notify.output("Title", "Msg", "detail", "warning")
|
||||||
|
assert_received {:editor_output, "Title", "Msg", "detail", "warning"}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "tab_meta/4 sends {:editor_tab_meta, ...}" do
|
||||||
|
BDS.Desktop.ShellLive.Notify.tab_meta(:post, "abc", "Title", "Subtitle")
|
||||||
|
assert_received {:editor_tab_meta, :post, "abc", %{title: "Title", subtitle: "Subtitle"}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "tab_meta_merge/3 sends {:editor_tab_meta, ...} with arbitrary updates" do
|
||||||
|
BDS.Desktop.ShellLive.Notify.tab_meta_merge(:misc, "id", %{title: "T"})
|
||||||
|
assert_received {:editor_tab_meta, :misc, "id", %{title: "T"}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "close_tab/2 sends {:close_tab, ...}" do
|
||||||
|
BDS.Desktop.ShellLive.Notify.close_tab(:templates, "t1")
|
||||||
|
assert_received {:close_tab, :templates, "t1"}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "reload/0 sends :reload_shell" do
|
||||||
|
BDS.Desktop.ShellLive.Notify.reload()
|
||||||
|
assert_received :reload_shell
|
||||||
|
end
|
||||||
|
|
||||||
|
test "dirty/3 sends {:editor_dirty, ...}" do
|
||||||
|
BDS.Desktop.ShellLive.Notify.dirty(:post, "p1", true)
|
||||||
|
assert_received {:editor_dirty, :post, "p1", true}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "command/2 sends {:editor_command, ...}" do
|
||||||
|
BDS.Desktop.ShellLive.Notify.command(:generate, %{force: true})
|
||||||
|
assert_received {:editor_command, :generate, %{force: true}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "open_sidebar_item/2 sends {:open_sidebar_item, ...}" do
|
||||||
|
BDS.Desktop.ShellLive.Notify.open_sidebar_item(%{"route" => "post"}, :pin)
|
||||||
|
assert_received {:open_sidebar_item, %{"route" => "post"}, :pin}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "parent/1 sends arbitrary message" do
|
||||||
|
BDS.Desktop.ShellLive.Notify.parent({:custom, :message})
|
||||||
|
assert_received {:custom, :message}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user