fix: fixed CSM-007
This commit is contained in:
32
CODESMELL.md
32
CODESMELL.md
@@ -131,18 +131,23 @@
|
||||
|
||||
---
|
||||
|
||||
### CSM-007 — Monolithic State Rebuild ("God Function")
|
||||
- **File:** `lib/bds/desktop/shell_live.ex:554-616`
|
||||
- **What:** `reload_shell/2` rebuilds sidebar, dashboard, git badge, tasks, status bar, tab meta, and panel data on almost every event. This function is called from most `handle_event` and `handle_info` callbacks, even for trivial state changes like sidebar toggle.
|
||||
- **Why it's bad:** Even a simple sidebar toggle triggers 5+ unrelated DB queries (project_snapshot, dashboard, git_badge_count, sidebar_view, task_status). The sidebar and dashboard data are rebuilt even when only the panel content changed. Output entries and editor meta are recalculated unnecessarily.
|
||||
- **Fix:** Decompose into focused updaters, each only querying what it needs:
|
||||
- `refresh_sidebar/2` — only queries sidebar data
|
||||
- `refresh_dashboard/2` — only queries dashboard data
|
||||
- `refresh_git_badge/2` — only queries git status
|
||||
- `refresh_task_status/2` — only queries task state
|
||||
- `refresh_tab_meta/2` — only syncs tab metadata
|
||||
Each `handle_event` / `handle_info` should call only the relevant updaters.
|
||||
- **Test:** Toggle sidebar; assert no dashboard or git queries are executed. Save a post; assert sidebar refreshes but dashboard does not.
|
||||
### ~~CSM-007 — Monolithic State Rebuild ("God Function")~~ ✅ FIXED
|
||||
- **Fixed:** 2026-05-09
|
||||
- **What was done:**
|
||||
- Decomposed `reload_shell/2` into four focused updaters:
|
||||
- `refresh_layout/2` — No DB queries. Recomputes workbench-derived assigns (activity_buttons, panel_tabs, current_tab, status_bar, sidebar_header, editor_meta) from existing socket.assigns.
|
||||
- `refresh_sidebar/2` — Queries sidebar data only, then calls `refresh_layout`.
|
||||
- `refresh_content/2` — Queries projects, dashboard, git badge, and sidebar data, then calls `refresh_layout`.
|
||||
- `reload_shell/2` — Full refresh: tab_meta sync, task status, static data, then calls `refresh_content`. Kept for mount, project switch, session restore, and settings changes.
|
||||
- Replaced all call sites with the minimal refresh needed:
|
||||
- **Layout-only** (`refresh_layout`): toggle_sidebar, toggle_panel, toggle_assistant_sidebar, select_panel_tab, sync_layout, resize_panel, open_tasks_panel, select_tab, close_tab, toggle_offline_mode, layout menu actions (toggle, close_tab).
|
||||
- **Sidebar** (`refresh_sidebar`): select_view, all sidebar filter events, sidebar menu actions (view_posts, view_media, edit_preferences, etc.), chat/import editor tab_meta updates.
|
||||
- **Content** (`refresh_content`): entity_changed (CLI sync), tags_changed, sidebar create/delete.
|
||||
- **Full reload** (`reload_shell`): mount, activate_project, restore_workbench_session, set_page_language, settings_changed.
|
||||
- Updated Bridges callbacks to use focused refreshers: `refresh_layout` for toggle events and close_tab, `refresh_sidebar` for view switches and tab meta updates, `refresh_content` for entity/tag changes.
|
||||
- Split `@local_menu_actions` into `@layout_menu_actions` and `@sidebar_menu_actions` for correct dispatch.
|
||||
- Fixed `false || true` bug in `refresh_layout` where `offline_mode = assigns[:offline_mode] || true` incorrectly defaulted `false` to `true`.
|
||||
- Added 7 tests in `test/bds/csm007_reload_shell_test.exs` using telemetry-based query counting: toggle_sidebar (0 queries), toggle_panel (0 queries), sync_layout (0 queries), select_panel_tab (0 queries), toggle_offline_mode (1 query — settings write only), select_view (sidebar queries but no dashboard/projects), sidebar_search (no dashboard queries).
|
||||
|
||||
---
|
||||
|
||||
@@ -419,9 +424,10 @@
|
||||
- CSM-004: Fixed. `attach_runner` moved to `handle_continue`, `terminate/2` added for cleanup, `restart: :temporary` set, JobStore `detach_runner` bug fixed.
|
||||
- [ ] All high-severity items (CSM-006 to CSM-010) have been addressed.
|
||||
- CSM-006: Fixed. Batch INSERT for reindexing, preloaded post records for rendering.
|
||||
- CSM-007: Fixed. Decomposed into refresh_layout, refresh_sidebar, refresh_content, reload_shell.
|
||||
- [x] CSM-001 fix covers ALL 6 affected files, not just `import_definitions.ex`.
|
||||
- [x] CSM-003 fix covers ALL `Repo.delete!` call sites (posts, tags, scripts, media, projects, templates, translations).
|
||||
- [ ] CSM-007 decomposition is the prerequisite for fixing CSM-008 (render-path queries).
|
||||
- [x] CSM-007 decomposition is the prerequisite for fixing CSM-008 (render-path queries).
|
||||
- [x] Tests were written **before** implementation changes (Red → Green → Refactor).
|
||||
- [x] Full test suite passes: `mix test`.
|
||||
- [x] Dialyzer passes cleanly: `mix dialyzer` (zero warnings).
|
||||
|
||||
@@ -82,18 +82,21 @@ defmodule BDS.Desktop.ShellLive do
|
||||
"load_more_sidebar"
|
||||
]
|
||||
|
||||
@local_menu_actions MapSet.new([
|
||||
:toggle_sidebar,
|
||||
:toggle_panel,
|
||||
:toggle_assistant_sidebar,
|
||||
:view_posts,
|
||||
:view_media,
|
||||
:edit_preferences,
|
||||
:edit_menu,
|
||||
:documentation,
|
||||
:api_documentation,
|
||||
:close_tab
|
||||
])
|
||||
@layout_menu_actions MapSet.new([
|
||||
:toggle_sidebar,
|
||||
:toggle_panel,
|
||||
:toggle_assistant_sidebar,
|
||||
:close_tab
|
||||
])
|
||||
@sidebar_menu_actions MapSet.new([
|
||||
:view_posts,
|
||||
:view_media,
|
||||
:edit_preferences,
|
||||
:edit_menu,
|
||||
:documentation,
|
||||
:api_documentation
|
||||
])
|
||||
@local_menu_actions MapSet.union(@layout_menu_actions, @sidebar_menu_actions)
|
||||
@socket_menu_actions MapSet.new([
|
||||
:new_post,
|
||||
:import_media,
|
||||
@@ -174,15 +177,15 @@ defmodule BDS.Desktop.ShellLive do
|
||||
|
||||
@impl true
|
||||
def handle_event("toggle_sidebar", _params, socket) do
|
||||
{:noreply, reload_shell(socket, Workbench.toggle_sidebar(socket.assigns.workbench))}
|
||||
{:noreply, refresh_layout(socket, Workbench.toggle_sidebar(socket.assigns.workbench))}
|
||||
end
|
||||
|
||||
def handle_event("toggle_panel", _params, socket) do
|
||||
{:noreply, reload_shell(socket, Workbench.toggle_panel(socket.assigns.workbench))}
|
||||
{:noreply, refresh_layout(socket, Workbench.toggle_panel(socket.assigns.workbench))}
|
||||
end
|
||||
|
||||
def handle_event("toggle_assistant_sidebar", _params, socket) do
|
||||
{:noreply, reload_shell(socket, Workbench.toggle_assistant_sidebar(socket.assigns.workbench))}
|
||||
{:noreply, refresh_layout(socket, Workbench.toggle_assistant_sidebar(socket.assigns.workbench))}
|
||||
end
|
||||
|
||||
def handle_event("select_view", %{"view" => view_id}, socket) do
|
||||
@@ -192,7 +195,7 @@ defmodule BDS.Desktop.ShellLive do
|
||||
BoundedAtoms.sidebar_view(view_id, :posts)
|
||||
)
|
||||
|
||||
{:noreply, reload_shell(socket, workbench)}
|
||||
{:noreply, refresh_sidebar(socket, workbench)}
|
||||
end
|
||||
|
||||
def handle_event("select_panel_tab", %{"tab" => tab}, socket) do
|
||||
@@ -201,7 +204,7 @@ defmodule BDS.Desktop.ShellLive do
|
||||
|> Workbench.set_panel_visible(true)
|
||||
|> Workbench.set_panel_tab(BoundedAtoms.panel_tab(tab, :tasks))
|
||||
|
||||
{:noreply, reload_shell(socket, workbench)}
|
||||
{:noreply, refresh_layout(socket, workbench)}
|
||||
end
|
||||
|
||||
def handle_event("open_sidebar_item", %{"route" => _route, "id" => _id} = params, socket) do
|
||||
@@ -213,15 +216,15 @@ defmodule BDS.Desktop.ShellLive do
|
||||
end
|
||||
|
||||
def handle_event("sync_layout", params, socket) do
|
||||
{:noreply, reload_shell(socket, Layout.sync(socket.assigns.workbench, params))}
|
||||
{:noreply, refresh_layout(socket, Layout.sync(socket.assigns.workbench, params))}
|
||||
end
|
||||
|
||||
def handle_event("resize_panel", %{"target" => target, "width" => width}, socket) do
|
||||
{:noreply, reload_shell(socket, Layout.resize(socket.assigns.workbench, target, width))}
|
||||
{:noreply, refresh_layout(socket, Layout.resize(socket.assigns.workbench, target, width))}
|
||||
end
|
||||
|
||||
def handle_event(event, params, socket) when event in @sidebar_filter_events do
|
||||
SidebarEvents.handle(socket, event, params, &reload_shell/2)
|
||||
SidebarEvents.handle(socket, event, params, &refresh_sidebar/2)
|
||||
end
|
||||
|
||||
def handle_event("create_sidebar_item", %{"kind" => kind}, socket) do
|
||||
@@ -248,7 +251,12 @@ defmodule BDS.Desktop.ShellLive do
|
||||
:preview
|
||||
)
|
||||
|
||||
{:noreply, reload_shell(socket, workbench)}
|
||||
tab_meta = TabHelpers.sync_tab_meta(workbench, socket.assigns[:tab_meta] || %{})
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:tab_meta, tab_meta)
|
||||
|> refresh_layout(workbench)}
|
||||
end
|
||||
|
||||
def handle_event("close_tab", %{"type" => type, "id" => id}, socket) do
|
||||
@@ -259,7 +267,7 @@ defmodule BDS.Desktop.ShellLive do
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:tab_meta, tab_meta)
|
||||
|> reload_shell(workbench)}
|
||||
|> refresh_layout(workbench)}
|
||||
end
|
||||
|
||||
def handle_event(
|
||||
@@ -283,7 +291,7 @@ defmodule BDS.Desktop.ShellLive do
|
||||
:ok = AI.set_airplane_mode(next_mode)
|
||||
socket = assign(socket, :offline_mode, next_mode)
|
||||
|
||||
{:noreply, reload_shell(socket, socket.assigns.workbench)}
|
||||
{:noreply, refresh_layout(socket, socket.assigns.workbench)}
|
||||
end
|
||||
|
||||
def handle_event("update_assistant_prompt", %{"assistant" => %{"prompt" => prompt}}, socket) do
|
||||
@@ -314,7 +322,7 @@ defmodule BDS.Desktop.ShellLive do
|
||||
|> Workbench.set_panel_visible(true)
|
||||
|> Workbench.set_panel_tab(:tasks)
|
||||
|
||||
{:noreply, reload_shell(socket, workbench)}
|
||||
{:noreply, refresh_layout(socket, workbench)}
|
||||
end
|
||||
|
||||
def handle_event("settings_shell_command", %{"action" => action}, socket) do
|
||||
@@ -551,12 +559,57 @@ defmodule BDS.Desktop.ShellLive do
|
||||
index(assigns)
|
||||
end
|
||||
|
||||
defp reload_shell(socket, workbench) do
|
||||
defp refresh_layout(socket, workbench) do
|
||||
git_badge_count = socket.assigns[:git_badge_count] || 0
|
||||
activity_buttons = Workbench.activity_buttons(workbench, git_badge_count)
|
||||
task_status = socket.assigns[:task_status] || %{running_task_message: nil, running_task_overflow: nil}
|
||||
dashboard = socket.assigns[:dashboard] || BDS.UI.Dashboard.empty_snapshot()
|
||||
page_language = socket.assigns[:page_language] || ShellData.ui_language()
|
||||
offline_mode = Map.get(socket.assigns, :offline_mode, true)
|
||||
sidebar_data = socket.assigns[:sidebar_data] || %{}
|
||||
|
||||
socket
|
||||
|> assign(:workbench, workbench)
|
||||
|> assign(:activity_buttons, activity_buttons)
|
||||
|> assign(
|
||||
:sidebar_header,
|
||||
active_sidebar_label(activity_buttons, workbench.active_view, sidebar_data)
|
||||
)
|
||||
|> assign(:panel_tabs, ShellData.panel_tabs(workbench))
|
||||
|> assign(:current_tab, current_tab(workbench))
|
||||
|> assign(:editor_meta, ShellData.editor_meta(task_status))
|
||||
|> assign(
|
||||
:status,
|
||||
ShellData.status_bar(workbench, task_status, dashboard,
|
||||
ui_language: page_language,
|
||||
offline_mode: offline_mode
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
defp refresh_sidebar(socket, workbench) do
|
||||
project_id = (socket.assigns[:projects] || %{})[:active_project_id]
|
||||
active_view_id = Atom.to_string(workbench.active_view)
|
||||
|
||||
sidebar_data =
|
||||
ShellData.sidebar_view(
|
||||
project_id,
|
||||
active_view_id,
|
||||
ShellSidebarState.current_filters(socket, active_view_id)
|
||||
)
|
||||
|
||||
sidebar_data = ShellSidebarState.merge_ui_state(socket, active_view_id, sidebar_data)
|
||||
|
||||
socket
|
||||
|> assign(:sidebar_data, sidebar_data)
|
||||
|> refresh_layout(workbench)
|
||||
end
|
||||
|
||||
defp refresh_content(socket, workbench) do
|
||||
projects = ShellData.project_snapshot()
|
||||
dashboard = ShellData.dashboard(projects.active_project_id)
|
||||
git_badge_count = ShellData.git_badge_count(projects.active_project_id)
|
||||
active_view_id = Atom.to_string(workbench.active_view)
|
||||
tab_meta = TabHelpers.sync_tab_meta(workbench, socket.assigns[:tab_meta] || %{})
|
||||
|
||||
sidebar_data =
|
||||
ShellData.sidebar_view(
|
||||
@@ -566,8 +619,26 @@ defmodule BDS.Desktop.ShellLive do
|
||||
)
|
||||
|
||||
sidebar_data = ShellSidebarState.merge_ui_state(socket, active_view_id, sidebar_data)
|
||||
|
||||
socket
|
||||
|> assign(:projects, projects)
|
||||
|> assign(:current_project, ShellData.current_project(projects))
|
||||
|> assign(:dashboard, dashboard)
|
||||
|> assign(:dashboard_timeline_entries, Map.get(dashboard, :timeline_entries, []))
|
||||
|> assign(:dashboard_category_counts, Map.get(dashboard, :category_counts, []))
|
||||
|> assign(:dashboard_recent_posts, Map.get(dashboard, :recent_posts, []))
|
||||
|> assign(
|
||||
:dashboard_tag_cloud_items,
|
||||
ShellData.dashboard_tag_cloud_items(Map.get(dashboard, :tag_cloud_items, []))
|
||||
)
|
||||
|> assign(:git_badge_count, git_badge_count)
|
||||
|> assign(:sidebar_data, sidebar_data)
|
||||
|> refresh_layout(workbench)
|
||||
end
|
||||
|
||||
defp reload_shell(socket, workbench) do
|
||||
tab_meta = TabHelpers.sync_tab_meta(workbench, socket.assigns[:tab_meta] || %{})
|
||||
raw_task_status = BDS.Tasks.status_snapshot()
|
||||
activity_buttons = Workbench.activity_buttons(workbench, git_badge_count)
|
||||
page_language = socket.assigns[:page_language] || ShellData.ui_language()
|
||||
|
||||
offline_mode =
|
||||
@@ -581,38 +652,13 @@ defmodule BDS.Desktop.ShellLive do
|
||||
|
||||
socket
|
||||
|> assign(:tab_meta, tab_meta)
|
||||
|> assign(:workbench, workbench)
|
||||
|> assign(:projects, projects)
|
||||
|> assign(:current_project, ShellData.current_project(projects))
|
||||
|> assign(:dashboard, dashboard)
|
||||
|> assign(:dashboard_timeline_entries, Map.get(dashboard, :timeline_entries, []))
|
||||
|> assign(:dashboard_category_counts, Map.get(dashboard, :category_counts, []))
|
||||
|> assign(:dashboard_recent_posts, Map.get(dashboard, :recent_posts, []))
|
||||
|> assign(
|
||||
:dashboard_tag_cloud_items,
|
||||
ShellData.dashboard_tag_cloud_items(Map.get(dashboard, :tag_cloud_items, []))
|
||||
)
|
||||
|> assign(:sidebar_data, sidebar_data)
|
||||
|> assign(
|
||||
:sidebar_header,
|
||||
active_sidebar_label(activity_buttons, workbench.active_view, sidebar_data)
|
||||
)
|
||||
|> assign(:assistant_cards, ShellData.assistant_cards())
|
||||
|> assign(:editor_meta, ShellData.editor_meta(task_status))
|
||||
|> assign(:task_status, task_status)
|
||||
|> assign(
|
||||
:status,
|
||||
ShellData.status_bar(workbench, task_status, dashboard,
|
||||
ui_language: page_language,
|
||||
offline_mode: offline_mode
|
||||
)
|
||||
)
|
||||
|> assign(:activity_buttons, activity_buttons)
|
||||
|> assign(:panel_tabs, ShellData.panel_tabs(workbench))
|
||||
|> assign(:offline_mode, offline_mode)
|
||||
|> assign(:assistant_cards, ShellData.assistant_cards())
|
||||
|> assign(:supported_ui_languages, ShellData.supported_ui_languages())
|
||||
|> assign(:menu_groups, socket.assigns[:menu_groups] || TitlebarMenu.groups())
|
||||
|> assign(:titlebar_menu_item_index, socket.assigns[:titlebar_menu_item_index])
|
||||
|> assign(:current_tab, current_tab(workbench))
|
||||
|> refresh_content(workbench)
|
||||
end
|
||||
|
||||
defp encoded_shortcuts(shortcuts), do: Jason.encode!(shortcuts)
|
||||
@@ -657,6 +703,7 @@ defmodule BDS.Desktop.ShellLive do
|
||||
defp sidebar_create_callbacks do
|
||||
%{
|
||||
reload: &reload_shell/2,
|
||||
refresh_content: &refresh_content/2,
|
||||
open_sidebar: &open_sidebar_item/3,
|
||||
append_output: &append_output_entry/5
|
||||
}
|
||||
@@ -681,9 +728,11 @@ defmodule BDS.Desktop.ShellLive do
|
||||
subtitle: Map.get(params, "subtitle", "")
|
||||
})
|
||||
|
||||
tab_meta = TabHelpers.sync_tab_meta(workbench, tab_meta)
|
||||
|
||||
socket
|
||||
|> assign(:tab_meta, tab_meta)
|
||||
|> reload_shell(workbench)
|
||||
|> refresh_layout(workbench)
|
||||
end
|
||||
|
||||
defp sidebar_create_action(view), do: SidebarCreate.action(view)
|
||||
@@ -752,8 +801,16 @@ defmodule BDS.Desktop.ShellLive do
|
||||
|
||||
defp handle_menu_action(socket, action) when is_atom(action) do
|
||||
cond do
|
||||
MapSet.member?(@local_menu_actions, action) ->
|
||||
reload_shell(socket, MenuBar.execute(socket.assigns.workbench, action))
|
||||
MapSet.member?(@layout_menu_actions, action) ->
|
||||
refresh_layout(socket, MenuBar.execute(socket.assigns.workbench, action))
|
||||
|
||||
MapSet.member?(@sidebar_menu_actions, action) ->
|
||||
workbench = MenuBar.execute(socket.assigns.workbench, action)
|
||||
tab_meta = TabHelpers.sync_tab_meta(workbench, socket.assigns[:tab_meta] || %{})
|
||||
|
||||
socket
|
||||
|> assign(:tab_meta, tab_meta)
|
||||
|> refresh_sidebar(workbench)
|
||||
|
||||
MapSet.member?(@socket_menu_actions, action) ->
|
||||
handle_socket_menu_action(socket, action)
|
||||
@@ -842,14 +899,14 @@ defmodule BDS.Desktop.ShellLive do
|
||||
socket
|
||||
end
|
||||
|
||||
defp save_current_tab(socket), do: reload_shell(socket, socket.assigns.workbench)
|
||||
defp save_current_tab(socket), do: refresh_layout(socket, socket.assigns.workbench)
|
||||
|
||||
defp publish_current_tab(%{assigns: %{current_tab: %{type: :post, id: post_id}}} = socket) do
|
||||
send_update(PostEditor, id: "post-editor-#{post_id}", action: :publish)
|
||||
socket
|
||||
end
|
||||
|
||||
defp publish_current_tab(socket), do: reload_shell(socket, socket.assigns.workbench)
|
||||
defp publish_current_tab(socket), do: refresh_layout(socket, socket.assigns.workbench)
|
||||
|
||||
defp apply_shell_command(socket, action, params \\ %{}),
|
||||
do: ShellCommandRunner.execute(socket, action, params, shell_command_callbacks())
|
||||
@@ -860,6 +917,7 @@ defmodule BDS.Desktop.ShellLive do
|
||||
defp shell_command_callbacks do
|
||||
%{
|
||||
reload: &reload_shell/2,
|
||||
refresh_content: &refresh_content/2,
|
||||
append_output: &append_output_entry/5
|
||||
}
|
||||
end
|
||||
@@ -876,6 +934,7 @@ defmodule BDS.Desktop.ShellLive do
|
||||
defp overlay_callbacks,
|
||||
do: %{
|
||||
reload: &reload_shell/2,
|
||||
refresh_content: &refresh_content/2,
|
||||
append_output: &append_output_entry/5,
|
||||
execute_sidebar_delete: fn socket, route, id ->
|
||||
SidebarDelete.execute_delete(socket, route, id, sidebar_delete_callbacks())
|
||||
@@ -885,12 +944,16 @@ defmodule BDS.Desktop.ShellLive do
|
||||
defp sidebar_delete_callbacks,
|
||||
do: %{
|
||||
reload: &reload_shell/2,
|
||||
refresh_content: &refresh_content/2,
|
||||
append_output: &append_output_entry/5
|
||||
}
|
||||
|
||||
defp bridges_callbacks,
|
||||
do: %{
|
||||
reload: &reload_shell/2,
|
||||
refresh_layout: &refresh_layout/2,
|
||||
refresh_sidebar: &refresh_sidebar/2,
|
||||
refresh_content: &refresh_content/2,
|
||||
append_output: &append_output_entry/5,
|
||||
open_sidebar: &open_sidebar_item/3,
|
||||
apply_shell_command: &apply_shell_command/3,
|
||||
|
||||
@@ -25,7 +25,7 @@ defmodule BDS.Desktop.ShellLive.Bridges do
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:tab_meta, tab_meta)
|
||||
|> callbacks.reload.(socket.assigns.workbench)}
|
||||
|> callbacks.refresh_sidebar.(socket.assigns.workbench)}
|
||||
end
|
||||
|
||||
def handle_info({:chat_tool_call, conversation_id, tool_call}, socket, _callbacks) do
|
||||
@@ -82,7 +82,7 @@ defmodule BDS.Desktop.ShellLive.Bridges do
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:tab_meta, tab_meta)
|
||||
|> callbacks.reload.(socket.assigns.workbench)}
|
||||
|> callbacks.refresh_sidebar.(socket.assigns.workbench)}
|
||||
end
|
||||
|
||||
def handle_info({:open_sidebar_item, params, intent}, socket, callbacks) do
|
||||
@@ -90,25 +90,30 @@ defmodule BDS.Desktop.ShellLive.Bridges do
|
||||
end
|
||||
|
||||
def handle_info({:chat_editor_toggle_sidebar}, socket, callbacks) do
|
||||
{:noreply, callbacks.reload.(socket, Workbench.toggle_sidebar(socket.assigns.workbench))}
|
||||
{:noreply,
|
||||
callbacks.refresh_layout.(socket, Workbench.toggle_sidebar(socket.assigns.workbench))}
|
||||
end
|
||||
|
||||
def handle_info({:chat_editor_toggle_panel}, socket, callbacks) do
|
||||
{:noreply, callbacks.reload.(socket, Workbench.toggle_panel(socket.assigns.workbench))}
|
||||
{:noreply,
|
||||
callbacks.refresh_layout.(socket, Workbench.toggle_panel(socket.assigns.workbench))}
|
||||
end
|
||||
|
||||
def handle_info({:chat_editor_toggle_assistant_sidebar}, socket, callbacks) do
|
||||
{:noreply,
|
||||
callbacks.reload.(socket, Workbench.toggle_assistant_sidebar(socket.assigns.workbench))}
|
||||
callbacks.refresh_layout.(
|
||||
socket,
|
||||
Workbench.toggle_assistant_sidebar(socket.assigns.workbench)
|
||||
)}
|
||||
end
|
||||
|
||||
def handle_info({:chat_editor_switch_view, view}, socket, callbacks) do
|
||||
{:noreply,
|
||||
callbacks.reload.(socket, Workbench.click_activity(socket.assigns.workbench, view))}
|
||||
callbacks.refresh_sidebar.(socket, Workbench.click_activity(socket.assigns.workbench, view))}
|
||||
end
|
||||
|
||||
def handle_info({:entity_changed, payload}, socket, callbacks) when is_map(payload) do
|
||||
{:noreply, CliSync.apply_entity_change(socket, payload, callbacks.reload)}
|
||||
{:noreply, CliSync.apply_entity_change(socket, payload, callbacks.refresh_content)}
|
||||
end
|
||||
|
||||
def handle_info(:refresh_task_status, socket, callbacks) do
|
||||
@@ -155,7 +160,7 @@ defmodule BDS.Desktop.ShellLive.Bridges do
|
||||
end
|
||||
|
||||
def handle_info(:tags_changed, socket, callbacks) do
|
||||
{:noreply, callbacks.reload.(socket, socket.assigns.workbench)}
|
||||
{:noreply, callbacks.refresh_content.(socket, socket.assigns.workbench)}
|
||||
end
|
||||
|
||||
def handle_info({:settings_output, title, message, level}, socket, callbacks) do
|
||||
@@ -267,7 +272,8 @@ defmodule BDS.Desktop.ShellLive.Bridges do
|
||||
end
|
||||
|
||||
def handle_info({:close_tab, type, id}, socket, callbacks) do
|
||||
{:noreply, callbacks.reload.(socket, Workbench.close_tab(socket.assigns.workbench, type, id))}
|
||||
{:noreply,
|
||||
callbacks.refresh_layout.(socket, Workbench.close_tab(socket.assigns.workbench, type, id))}
|
||||
end
|
||||
|
||||
def handle_info(_message, socket, _callbacks), do: {:noreply, socket}
|
||||
|
||||
@@ -19,7 +19,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
||||
def create(socket, kind, callbacks) do
|
||||
case socket.assigns.projects.active_project_id do
|
||||
project_id when is_binary(project_id) -> create(socket, project_id, kind, callbacks)
|
||||
_other -> callbacks.reload.(socket, socket.assigns.workbench)
|
||||
_other -> callbacks.refresh_content.(socket, socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,12 +32,12 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
||||
categories: []
|
||||
}) do
|
||||
{:ok, _post} ->
|
||||
callbacks.reload.(socket, socket.assigns.workbench)
|
||||
callbacks.refresh_content.(socket, socket.assigns.workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
socket
|
||||
|> callbacks.append_output.(dgettext("ui", "New Post"), inspect(reason), nil, "error")
|
||||
|> callbacks.reload.(socket.assigns.workbench)
|
||||
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -46,7 +46,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
||||
{:ok, source_path} ->
|
||||
case BDS.Media.import_media(%{project_id: project_id, source_path: source_path}) do
|
||||
{:ok, _media} ->
|
||||
callbacks.reload.(socket, socket.assigns.workbench)
|
||||
callbacks.refresh_content.(socket, socket.assigns.workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
socket
|
||||
@@ -56,16 +56,16 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
||||
nil,
|
||||
"error"
|
||||
)
|
||||
|> callbacks.reload.(socket.assigns.workbench)
|
||||
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
:cancel ->
|
||||
callbacks.reload.(socket, socket.assigns.workbench)
|
||||
callbacks.refresh_content.(socket, socket.assigns.workbench)
|
||||
|
||||
{:error, %{message: message}} ->
|
||||
socket
|
||||
|> callbacks.append_output.(dgettext("ui", "Import media"), message, nil, "error")
|
||||
|> callbacks.reload.(socket.assigns.workbench)
|
||||
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -98,7 +98,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
||||
nil,
|
||||
"error"
|
||||
)
|
||||
|> callbacks.reload.(socket.assigns.workbench)
|
||||
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -130,7 +130,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
||||
nil,
|
||||
"error"
|
||||
)
|
||||
|> callbacks.reload.(socket.assigns.workbench)
|
||||
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -151,7 +151,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
||||
{:error, reason} ->
|
||||
socket
|
||||
|> callbacks.append_output.(dgettext("ui", "New Chat"), inspect(reason), nil, "error")
|
||||
|> callbacks.reload.(socket.assigns.workbench)
|
||||
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -180,12 +180,12 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
||||
nil,
|
||||
"error"
|
||||
)
|
||||
|> callbacks.reload.(socket.assigns.workbench)
|
||||
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
def create(socket, _project_id, _kind, callbacks),
|
||||
do: callbacks.reload.(socket, socket.assigns.workbench)
|
||||
do: callbacks.refresh_content.(socket, socket.assigns.workbench)
|
||||
|
||||
def action(:posts), do: %{kind: "post", label: "sidebar.newPost"}
|
||||
def action(:media), do: %{kind: "media", label: "sidebar.importMedia"}
|
||||
|
||||
@@ -31,7 +31,7 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> callbacks.append_output.(delete_title(route), inspect(reason), nil, "error")
|
||||
|> callbacks.reload.(socket.assigns.workbench)
|
||||
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -74,7 +74,7 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do
|
||||
nil,
|
||||
"error"
|
||||
)
|
||||
|> callbacks.reload.(socket.assigns.workbench)
|
||||
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -88,7 +88,7 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> assign(:tab_meta, Map.delete(socket.assigns.tab_meta, {type, id}))
|
||||
|> callbacks.reload.(workbench)
|
||||
|> callbacks.refresh_content.(workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
socket
|
||||
@@ -99,7 +99,7 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do
|
||||
nil,
|
||||
"error"
|
||||
)
|
||||
|> callbacks.reload.(socket.assigns.workbench)
|
||||
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
191
test/bds/csm007_reload_shell_test.exs
Normal file
191
test/bds/csm007_reload_shell_test.exs
Normal file
@@ -0,0 +1,191 @@
|
||||
defmodule BDS.CSM007ReloadShellTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
import Phoenix.ConnTest
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
@endpoint BDS.Desktop.Endpoint
|
||||
|
||||
setup do
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||
|
||||
temp_dir =
|
||||
Path.join(System.tmp_dir!(), "bds-csm007-#{System.unique_integer([:positive])}")
|
||||
|
||||
File.mkdir_p!(temp_dir)
|
||||
on_exit(fn -> File.rm_rf(temp_dir) end)
|
||||
|
||||
{:ok, project} = BDS.Projects.create_project(%{name: "CSM007", data_path: temp_dir})
|
||||
%{project: project}
|
||||
end
|
||||
|
||||
describe "toggle_sidebar" do
|
||||
test "triggers no dashboard or git queries", %{project: _project} do
|
||||
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
|
||||
query_count = count_queries(fn ->
|
||||
render_click(view, "toggle_sidebar", %{})
|
||||
end)
|
||||
|
||||
assert query_count == 0,
|
||||
"Expected 0 DB queries for sidebar toggle, got #{query_count}"
|
||||
end
|
||||
end
|
||||
|
||||
describe "toggle_panel" do
|
||||
test "triggers no DB queries" do
|
||||
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
|
||||
query_count = count_queries(fn ->
|
||||
render_click(view, "toggle_panel", %{})
|
||||
end)
|
||||
|
||||
assert query_count == 0,
|
||||
"Expected 0 DB queries for panel toggle, got #{query_count}"
|
||||
end
|
||||
end
|
||||
|
||||
describe "sync_layout" do
|
||||
test "triggers no DB queries" do
|
||||
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
|
||||
query_count = count_queries(fn ->
|
||||
render_click(view, "sync_layout", %{
|
||||
"sidebar_width" => "300",
|
||||
"sidebar_visible" => "true",
|
||||
"panel_visible" => "false"
|
||||
})
|
||||
end)
|
||||
|
||||
assert query_count == 0,
|
||||
"Expected 0 DB queries for sync_layout, got #{query_count}"
|
||||
end
|
||||
end
|
||||
|
||||
describe "select_panel_tab" do
|
||||
test "triggers no DB queries" do
|
||||
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
|
||||
query_count = count_queries(fn ->
|
||||
render_click(view, "select_panel_tab", %{"tab" => "output"})
|
||||
end)
|
||||
|
||||
assert query_count == 0,
|
||||
"Expected 0 DB queries for select_panel_tab, got #{query_count}"
|
||||
end
|
||||
end
|
||||
|
||||
describe "select_view" do
|
||||
test "triggers sidebar query but not dashboard or git queries" do
|
||||
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
|
||||
{query_count, query_sources} = count_queries_with_sources(fn ->
|
||||
render_click(view, "select_view", %{"view" => "media"})
|
||||
end)
|
||||
|
||||
assert query_count > 0, "Expected at least 1 query for view change"
|
||||
|
||||
refute "dashboard" in query_sources or "projects" in query_sources,
|
||||
"View change should not query dashboard or projects, got: #{inspect(query_sources)}"
|
||||
end
|
||||
end
|
||||
|
||||
describe "sidebar filter events" do
|
||||
test "do not trigger dashboard or git queries" do
|
||||
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
|
||||
{_count, query_sources} = count_queries_with_sources(fn ->
|
||||
render_click(view, "update_sidebar_search", %{
|
||||
"sidebar_filters" => %{"search" => "test"}
|
||||
})
|
||||
end)
|
||||
|
||||
refute "dashboard" in query_sources,
|
||||
"Sidebar search should not query dashboard, got: #{inspect(query_sources)}"
|
||||
end
|
||||
end
|
||||
|
||||
describe "toggle_offline_mode" do
|
||||
test "triggers only the settings write, no refresh queries" do
|
||||
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
|
||||
query_count = count_queries(fn ->
|
||||
render_click(view, "toggle_offline_mode", %{})
|
||||
end)
|
||||
|
||||
assert query_count == 1,
|
||||
"Expected exactly 1 DB query (settings write) for offline mode toggle, got #{query_count}"
|
||||
end
|
||||
end
|
||||
|
||||
defp count_queries(func) do
|
||||
test_pid = self()
|
||||
ref = make_ref()
|
||||
handler_id = "csm007-query-counter-#{inspect(ref)}"
|
||||
|
||||
:telemetry.attach(
|
||||
handler_id,
|
||||
[:bds, :repo, :query],
|
||||
fn _event, _measurements, _metadata, _ ->
|
||||
send(test_pid, {:query_executed, ref})
|
||||
end,
|
||||
nil
|
||||
)
|
||||
|
||||
func.()
|
||||
|
||||
:telemetry.detach(handler_id)
|
||||
count_messages(ref, 0)
|
||||
end
|
||||
|
||||
defp count_queries_with_sources(func) do
|
||||
test_pid = self()
|
||||
ref = make_ref()
|
||||
handler_id = "csm007-query-sources-#{inspect(ref)}"
|
||||
|
||||
:telemetry.attach(
|
||||
handler_id,
|
||||
[:bds, :repo, :query],
|
||||
fn _event, _measurements, metadata, _ ->
|
||||
source = metadata[:source] || extract_table(metadata[:query] || "")
|
||||
send(test_pid, {:query_executed, ref, source})
|
||||
end,
|
||||
nil
|
||||
)
|
||||
|
||||
func.()
|
||||
|
||||
:telemetry.detach(handler_id)
|
||||
collect_query_sources(ref, 0, [])
|
||||
end
|
||||
|
||||
defp collect_query_sources(ref, count, sources) do
|
||||
receive do
|
||||
{:query_executed, ^ref, source} ->
|
||||
collect_query_sources(ref, count + 1, [source | sources])
|
||||
after
|
||||
0 -> {count, Enum.uniq(sources)}
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_table(query) when is_binary(query) do
|
||||
cond do
|
||||
String.contains?(query, "dashboard") -> "dashboard"
|
||||
String.contains?(query, "projects") -> "projects"
|
||||
String.contains?(query, "posts") -> "posts"
|
||||
String.contains?(query, "media") -> "media"
|
||||
String.contains?(query, "tags") -> "tags"
|
||||
true -> "other"
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_table(_), do: "unknown"
|
||||
|
||||
defp count_messages(ref, acc) do
|
||||
receive do
|
||||
{:query_executed, ^ref} -> count_messages(ref, acc + 1)
|
||||
after
|
||||
0 -> acc
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user