fix: fixed the metadata diff

This commit is contained in:
2026-04-26 23:07:17 +02:00
parent b51764df24
commit 546df93d14
7 changed files with 260 additions and 23 deletions

View File

@@ -44,7 +44,9 @@ defmodule BDS.Desktop.ShellLive do
@impl true
def mount(_params, _session, socket) do
if connected?(socket) do
connected = connected?(socket)
if connected do
:timer.send_interval(@refresh_interval, :refresh_task_status)
end
@@ -55,7 +57,8 @@ defmodule BDS.Desktop.ShellLive do
|> assign(:page_title, ShellData.title())
|> assign(:page_language, ShellData.ui_language())
|> assign(:client_shortcuts, Commands.client_shortcuts())
|> assign(:offline_mode, AI.airplane_mode?(true))
|> assign(:offline_mode, if(connected, do: AI.airplane_mode?(true), else: true))
|> assign(:handled_task_results, initial_handled_task_results())
|> assign(:assistant_prompt, "")
|> assign(:assistant_messages, [])
|> assign(:is_mac_ui, mac_ui?())
@@ -1003,19 +1006,30 @@ defmodule BDS.Desktop.ShellLive do
@impl true
def handle_info(:refresh_task_status, socket) do
task_status = BDS.Tasks.status_snapshot()
raw_task_status = BDS.Tasks.status_snapshot()
{:noreply,
socket
|> assign(:task_status, task_status)
|> assign(:editor_meta, ShellData.editor_meta(task_status))
|> assign(
:status,
ShellData.status_bar(socket.assigns.workbench, task_status, socket.assigns.dashboard,
ui_language: socket.assigns.page_language,
offline_mode: socket.assigns.offline_mode
)
)}
case next_completed_task_result(socket, raw_task_status) do
nil ->
task_status = localize_task_status(raw_task_status, socket.assigns.page_language)
{:noreply,
socket
|> assign(:task_status, task_status)
|> assign(:editor_meta, ShellData.editor_meta(task_status))
|> assign(
:status,
ShellData.status_bar(socket.assigns.workbench, task_status, socket.assigns.dashboard,
ui_language: socket.assigns.page_language,
offline_mode: socket.assigns.offline_mode
)
)}
task ->
{:noreply,
socket
|> mark_task_result_handled(task.id)
|> apply_shell_command_result(task.result)}
end
end
@impl true
@@ -1031,10 +1045,17 @@ defmodule BDS.Desktop.ShellLive do
active_view_id = Atom.to_string(workbench.active_view)
sidebar_data = ShellData.sidebar_view(projects.active_project_id, active_view_id, ShellSidebarState.current_filters(socket, active_view_id))
sidebar_data = ShellSidebarState.merge_ui_state(socket, active_view_id, sidebar_data)
task_status = BDS.Tasks.status_snapshot()
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 = Map.get(socket.assigns, :offline_mode, AI.airplane_mode?(true))
offline_mode =
if connected?(socket) do
Map.get(socket.assigns, :offline_mode, AI.airplane_mode?(true))
else
Map.get(socket.assigns, :offline_mode, true)
end
task_status = localize_task_status(raw_task_status, page_language)
socket
|> assign(:workbench, workbench)
@@ -1118,9 +1139,15 @@ defmodule BDS.Desktop.ShellLive do
<div class="panel-entry task-entry">
<div class="task-entry-header">
<strong><%= task.name %></strong>
<span class={"task-status task-status-#{task.status}"}><%= task.status |> to_string() |> String.capitalize() %></span>
<span class={"task-status task-status-#{task.status}"}><%= Map.get(task, :status_label, task.status |> to_string() |> String.capitalize()) %></span>
</div>
<span><%= task.message || task.group_name || "" %></span>
<%= if is_number(task.progress) do %>
<div class="task-progress-row">
<progress max="1" value={task.progress}></progress>
<span><%= Map.get(task, :progress_label, progress_percent(task.progress)) %></span>
</div>
<% end %>
</div>
<% end %>
</div>
@@ -1574,17 +1601,17 @@ defmodule BDS.Desktop.ShellLive do
|> Workbench.set_panel_tab(String.to_existing_atom(panel_tab))
socket
|> append_output_entry(title, message)
|> append_output_entry(translate_for_socket(socket, title), translate_for_socket(socket, message))
|> reload_shell(workbench)
end
defp apply_shell_command_result(socket, %{kind: "output", title: title, message: message} = result) do
socket
|> append_output_entry(title, message, Map.get(result, :details), Map.get(result, :level, "info"))
|> append_output_entry(translate_for_socket(socket, title), translate_for_socket(socket, message), Map.get(result, :details), Map.get(result, :level, "info"))
end
defp apply_shell_command_result(socket, %{kind: "open_url", title: title, message: message, url: url}) do
append_output_entry(socket, title, message, url)
append_output_entry(socket, translate_for_socket(socket, title), translate_for_socket(socket, message), url)
end
defp apply_shell_command_result(socket, %{kind: "open_editor", route: route, title: title, subtitle: subtitle} = result) do
@@ -1594,12 +1621,12 @@ defmodule BDS.Desktop.ShellLive do
tab_meta =
Map.put(socket.assigns.tab_meta, {route_atom, tab_id}, %{
title: title,
subtitle: subtitle,
title: translate_for_socket(socket, title),
subtitle: translate_for_socket(socket, subtitle),
action: Map.get(result, :action),
payload: Map.get(result, :payload),
project_id: Map.get(result, :project_id),
editor_meta: Map.get(result, :editorMeta, [])
editor_meta: translate_editor_meta(Map.get(result, :editorMeta, []), socket.assigns.page_language)
})
socket
@@ -1609,6 +1636,90 @@ defmodule BDS.Desktop.ShellLive do
defp apply_shell_command_result(socket, _result), do: socket
defp initial_handled_task_results do
BDS.Tasks.status_snapshot()
|> Map.get(:tasks, [])
|> Enum.filter(fn task -> task.status == :completed and is_map(task.result) end)
|> Enum.map(& &1.id)
|> MapSet.new()
end
defp next_completed_task_result(socket, task_status) do
handled = Map.get(socket.assigns, :handled_task_results, MapSet.new())
Enum.find(Map.get(task_status, :tasks, []), fn task ->
task.status == :completed and is_map(task.result) and not MapSet.member?(handled, task.id)
end)
end
defp mark_task_result_handled(socket, task_id) do
handled = Map.get(socket.assigns, :handled_task_results, MapSet.new())
assign(socket, :handled_task_results, MapSet.put(handled, task_id))
end
defp localize_task_status(task_status, locale) do
tasks = Enum.map(Map.get(task_status, :tasks, []), &localize_task(&1, locale))
active = Enum.filter(tasks, &(&1.status in [:running, :pending]))
task_status
|> Map.put(:tasks, tasks)
|> Map.put(:running_task_message, localized_running_task_message(active, locale))
end
defp localize_task(task, locale) do
progress = Map.get(task, :progress)
task
|> Map.put(:name, ShellData.translate(task.name, %{}, locale))
|> Map.put(:message, localize_task_message(Map.get(task, :message), locale))
|> Map.put(:group_name, localize_task_group(Map.get(task, :group_name), locale))
|> Map.put(:status_label, localize_task_status_label(task.status, locale))
|> Map.put(:progress_label, if(is_number(progress), do: progress_percent(progress), else: nil))
end
defp localize_task_message(nil, _locale), do: nil
defp localize_task_message("", _locale), do: ""
defp localize_task_message(message, locale), do: ShellData.translate(message, %{}, locale)
defp localize_task_group(nil, _locale), do: nil
defp localize_task_group(group, locale), do: ShellData.translate(group, %{}, locale)
defp localize_task_status_label(status, locale) do
status
|> to_string()
|> String.capitalize()
|> ShellData.translate(%{}, locale)
end
defp localized_running_task_message([], _locale), do: nil
defp localized_running_task_message([task | _rest], locale) do
cond do
task.status == :pending -> ShellData.translate("Queued", %{}, locale) <> ": " <> task.name
is_binary(task.message) and task.message != "" -> task.name <> ": " <> task.message
true -> task.name
end
end
defp translate_editor_meta(items, locale) do
Enum.map(items, fn item ->
item
|> Map.update(:label, nil, &ShellData.translate(&1, %{}, locale))
|> Map.update(:value, nil, &translate_editor_meta_value(&1, locale))
end)
end
defp translate_editor_meta_value(value, locale) when is_binary(value), do: ShellData.translate(value, %{}, locale)
defp translate_editor_meta_value(value, _locale), do: value
defp translate_for_socket(socket, text) when is_binary(text), do: ShellData.translate(text, %{}, socket.assigns.page_language)
defp translate_for_socket(_socket, text), do: text
defp progress_percent(progress) when is_number(progress) do
percentage = progress |> Kernel.*(100) |> round()
Integer.to_string(percentage) <> "%"
end
defp command_title(action) do
action
|> to_string()

View File

@@ -172,7 +172,11 @@
"Menu": "Menü",
"Metadata": "Metadaten",
"Metadata Diff": "Metadaten-Diff",
"Metadata diff complete": "Metadaten-Diff abgeschlossen",
"Metadata flush, diffing, and rebuild hooks still need editor wiring.": "Metadaten-Schreiben, Diffing und Rebuild-Hooks brauchen noch die Editor-Anbindung.",
"Comparing database and filesystem metadata": "Vergleicht Datenbank- und Dateisystem-Metadaten",
"Database state compared against filesystem metadata": "Datenbankstatus mit Dateisystem-Metadaten verglichen",
"Maintenance": "Wartung",
"Missing": "Fehlend",
"Missing Pages": "Fehlende Seiten",
"Missing Translations": "Fehlende Übersetzungen",
@@ -206,10 +210,14 @@
"Published": "Veröffentlicht",
"Published Feb 10, 2026": "Veröffentlicht am 10. Feb. 2026",
"Queued": "In Warteschlange",
"Pending": "Ausstehend",
"Regenerate Calendar": "Kalender neu erzeugen",
"Retrospective": "Rückblick",
"Roadmap": "Fahrplan",
"Running": "Läuft",
"Completed": "Abgeschlossen",
"Failed": "Fehlgeschlagen",
"Cancelled": "Abgebrochen",
"Script": "Skript",
"Scripts": "Skripte",
"Select Project": "Projekt auswählen",
@@ -217,7 +225,9 @@
"Settings": "Einstellungen",
"Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "Seitenleiste, Tabs, Panel und Assistentenbereiche sind als DOM-Regionen inspizierbar",
"Site Validation": "Website-Validierung",
"Validation": "Validierung",
"Source Control": "Quellcodeverwaltung",
"Embeddings": "Embeddings",
"Stale": "Veraltet",
"Stale Pages": "Veraltete Seiten",
"Slug": "Slug",

View File

@@ -172,7 +172,11 @@
"Menu": "Menu",
"Metadata": "Metadata",
"Metadata Diff": "Metadata Diff",
"Metadata diff complete": "Metadata diff complete",
"Metadata flush, diffing, and rebuild hooks still need editor wiring.": "Metadata flush, diffing, and rebuild hooks still need editor wiring.",
"Comparing database and filesystem metadata": "Comparing database and filesystem metadata",
"Database state compared against filesystem metadata": "Database state compared against filesystem metadata",
"Maintenance": "Maintenance",
"Missing": "Missing",
"Missing Pages": "Missing Pages",
"Missing Translations": "Missing Translations",
@@ -206,10 +210,14 @@
"Published": "Published",
"Published Feb 10, 2026": "Published Feb 10, 2026",
"Queued": "Queued",
"Pending": "Pending",
"Regenerate Calendar": "Regenerate Calendar",
"Retrospective": "Retrospective",
"Roadmap": "Roadmap",
"Running": "Running",
"Completed": "Completed",
"Failed": "Failed",
"Cancelled": "Cancelled",
"Script": "Script",
"Scripts": "Scripts",
"Select Project": "Select Project",
@@ -217,7 +225,9 @@
"Settings": "Settings",
"Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "Sidebar, tabs, panel, and assistant panes are inspectable DOM regions",
"Site Validation": "Site Validation",
"Validation": "Validation",
"Source Control": "Source Control",
"Embeddings": "Embeddings",
"Stale": "Stale",
"Stale Pages": "Stale Pages",
"Slug": "Slug",

View File

@@ -172,7 +172,11 @@
"Menu": "Menú",
"Metadata": "Metadatos",
"Metadata Diff": "Diff de metadatos",
"Metadata diff complete": "Diff de metadatos completado",
"Metadata flush, diffing, and rebuild hooks still need editor wiring.": "El guardado de metadatos, el diff y los hooks de reconstrucción todavía necesitan la conexión del editor.",
"Comparing database and filesystem metadata": "Comparando metadatos de la base de datos y del sistema de archivos",
"Database state compared against filesystem metadata": "Estado de la base de datos comparado con los metadatos del sistema de archivos",
"Maintenance": "Mantenimiento",
"Missing": "Faltante",
"Missing Pages": "Páginas faltantes",
"Missing Translations": "Traducciones faltantes",
@@ -206,10 +210,14 @@
"Published": "Publicado",
"Published Feb 10, 2026": "Publicado el 10 feb 2026",
"Queued": "En cola",
"Pending": "Pendiente",
"Regenerate Calendar": "Regenerar calendario",
"Retrospective": "Retrospectiva",
"Roadmap": "Hoja de ruta",
"Running": "En ejecución",
"Completed": "Completado",
"Failed": "Fallido",
"Cancelled": "Cancelado",
"Script": "Script",
"Scripts": "Scripts",
"Select Project": "Seleccionar proyecto",
@@ -217,7 +225,9 @@
"Settings": "Configuración",
"Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "La barra lateral, las pestañas, el panel y el asistente son regiones DOM inspeccionables",
"Site Validation": "Validación del sitio",
"Validation": "Validación",
"Source Control": "Control de código fuente",
"Embeddings": "Embeddings",
"Stale": "Desactualizado",
"Stale Pages": "Páginas desactualizadas",
"Slug": "Slug",

View File

@@ -172,7 +172,11 @@
"Menu": "Menu",
"Metadata": "Métadonnées",
"Metadata Diff": "Diff des métadonnées",
"Metadata diff complete": "Diff des métadonnées terminé",
"Metadata flush, diffing, and rebuild hooks still need editor wiring.": "Lécriture des métadonnées, le diff et les hooks de reconstruction ont encore besoin du câblage de léditeur.",
"Comparing database and filesystem metadata": "Comparaison des métadonnées entre la base et le système de fichiers",
"Database state compared against filesystem metadata": "État de la base comparé aux métadonnées du système de fichiers",
"Maintenance": "Maintenance",
"Missing": "Manquant",
"Missing Pages": "Pages manquantes",
"Missing Translations": "Traductions manquantes",
@@ -206,10 +210,14 @@
"Published": "Publié",
"Published Feb 10, 2026": "Publié le 10 févr. 2026",
"Queued": "En file",
"Pending": "En attente",
"Regenerate Calendar": "Régénérer le calendrier",
"Retrospective": "Rétrospective",
"Roadmap": "Feuille de route",
"Running": "En cours",
"Completed": "Terminé",
"Failed": "Échec",
"Cancelled": "Annulé",
"Script": "Script",
"Scripts": "Scripts",
"Select Project": "Sélectionner un projet",
@@ -217,7 +225,9 @@
"Settings": "Paramètres",
"Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "La barre latérale, les onglets, le panneau et lassistant sont des régions DOM inspectables",
"Site Validation": "Validation du site",
"Validation": "Validation",
"Source Control": "Contrôle de source",
"Embeddings": "Embeddings",
"Stale": "Obsolète",
"Stale Pages": "Pages obsolètes",
"Slug": "Slug",

View File

@@ -172,7 +172,11 @@
"Menu": "Menu",
"Metadata": "Metadati",
"Metadata Diff": "Diff metadati",
"Metadata diff complete": "Diff metadati completato",
"Metadata flush, diffing, and rebuild hooks still need editor wiring.": "Il salvataggio dei metadati, il diff e gli hook di ricostruzione hanno ancora bisogno del collegamento nelleditor.",
"Comparing database and filesystem metadata": "Confronto tra i metadati del database e del filesystem",
"Database state compared against filesystem metadata": "Stato del database confrontato con i metadati del filesystem",
"Maintenance": "Manutenzione",
"Missing": "Mancante",
"Missing Pages": "Pagine mancanti",
"Missing Translations": "Traduzioni mancanti",
@@ -206,10 +210,14 @@
"Published": "Pubblicato",
"Published Feb 10, 2026": "Pubblicato il 10 feb 2026",
"Queued": "In coda",
"Pending": "In attesa",
"Regenerate Calendar": "Rigenera calendario",
"Retrospective": "Retrospettiva",
"Roadmap": "Roadmap",
"Running": "In esecuzione",
"Completed": "Completato",
"Failed": "Fallito",
"Cancelled": "Annullato",
"Script": "Script",
"Scripts": "Script",
"Select Project": "Seleziona progetto",
@@ -217,7 +225,9 @@
"Settings": "Impostazioni",
"Sidebar, tabs, panel, and assistant panes are inspectable DOM regions": "Barra laterale, schede, pannello e assistente sono regioni DOM ispezionabili",
"Site Validation": "Validazione sito",
"Validation": "Validazione",
"Source Control": "Controllo del codice sorgente",
"Embeddings": "Embeddings",
"Stale": "Obsoleto",
"Stale Pages": "Pagine obsolete",
"Slug": "Slug",

View File

@@ -839,6 +839,82 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ ~s(class="task-list") or html =~ "No background tasks running"
end
test "metadata diff tasks localize task text, show progress, and open the diff result in the UI" do
parent = self()
:ok = BDS.Tasks.clear_finished()
{:ok, _task} =
BDS.Tasks.submit_task(
"Metadata Diff",
fn report ->
send(parent, {:metadata_diff_worker, self()})
report.(0.35, "Comparing database and filesystem metadata")
receive do
:finish ->
%{
kind: "open_editor",
action: "metadata_diff",
project_id: "test-project",
route: "metadata_diff",
title: "Metadata Diff",
subtitle: "Database state compared against filesystem metadata",
editorMeta: [
%{label: "Diffs", value: "1"},
%{label: "Orphans", value: "1"}
],
payload: %{
summary: %{diff_count: 1, orphan_count: 1},
diff_reports: [
%{
entity_type: "post",
entity_id: "post-1",
differences: [
%{field: "slug", db_value: "hello-db", file_value: "hello-file"}
]
}
],
orphan_reports: [
%{path: "posts/2026/04/orphan.md", entity_type: "post"}
]
}
}
end
end,
%{group_name: "Maintenance"}
)
assert_receive {:metadata_diff_worker, worker_pid}
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
_html = render_change(view, "change_ui_language", %{"ui_language" => "de"})
send(view.pid, :refresh_task_status)
html =
view
|> element("[data-testid='status-task-button']")
|> render_click()
assert html =~ "Metadaten-Diff"
assert html =~ "Vergleicht Datenbank- und Dateisystem-Metadaten"
assert html =~ "35%"
assert html =~ ~s(task-status-running)
send(worker_pid, :finish)
send(view.pid, :refresh_task_status)
html = render(view)
assert html =~ ~s(data-tab-type="metadata_diff")
assert html =~ "Metadaten-Diff"
assert html =~ "slug"
assert html =~ "hello-db"
assert html =~ "hello-file"
assert html =~ "posts/2026/04/orphan.md"
end
test "post tabs render a real editor and drive save publish discard flows", %{project: project} do
assert {:ok, _tag} = Tags.create_tag(%{project_id: project.id, name: "alpha", color: "#112233"})
assert {:ok, _tag} = Tags.create_tag(%{project_id: project.id, name: "beta", color: "#445566"})