# Unified Editor Session **Status:** Complete **Depends on:** Editor panel reorganisation (Phase 1-2 complete) **Estimated effort:** 4-5 hours ## Overview Unify the editing experience across all three tabs (Page, Theme, Site) so they behave consistently: - **Instant preview** — all changes show immediately on the shop - **Explicit save** — changes only persist to database when you click Save - **Free tab switching** — no warnings when switching between tabs - **Accumulated changes** — edits across all tabs are saved together - **Navigation warning** — warn on page navigation or refresh if any tab has unsaved changes ## Current State | Tab | Dirty Tracking | Save Method | Preview | |-----|----------------|-------------|---------| | Page | `editor_dirty` | Manual Save | Instant (in-memory blocks) | | Theme | None | Auto-save | Instant (CSS regenerated) | | Site | None | Auto-save | Instant (assigns updated) | ## Target State | Tab | Dirty Tracking | Save Method | Preview | |-----|----------------|-------------|---------| | Page | `page_dirty` | Unified Save | Instant (in-memory blocks) | | Theme | `theme_dirty` | Unified Save | Instant (in-memory settings) | | Site | `site_dirty` | Unified Save | Instant (in-memory settings) | Unified dirty flag: `editor_dirty = page_dirty || theme_dirty || site_dirty` ## Implementation Tasks ### Phase 1: Theme Tab — Preview Without Auto-Save (1.5h) Currently Theme auto-saves every change. We need to decouple preview from persistence. #### 1.1 Add theme dirty tracking In `page_editor_hook.ex`, add assigns: ```elixir |> assign(:theme_dirty, false) |> assign(:theme_editor_original, nil) # snapshot at load time ``` #### 1.2 Modify theme event handlers Change handlers like `theme_update_setting`, `theme_update_color`, etc. to: 1. Update `theme_editor_settings` in memory (for preview) 2. Regenerate CSS for live preview 3. Set `theme_dirty = true` 4. **Do NOT call `Settings.update_theme_settings()`** #### 1.3 Store original theme state When loading theme state in `load_theme_state/1`: ```elixir |> assign(:theme_editor_original, theme_settings) ``` #### 1.4 Add theme revert logic On discard/close with dirty state, restore from `theme_editor_original`. ### Phase 2: Site Tab — Preview Without Auto-Save (1h) #### 2.1 Add site dirty tracking ```elixir |> assign(:site_dirty, false) |> assign(:site_editor_original, nil) ``` #### 2.2 Modify site event handlers Change `handle_site_update/2` to: 1. Update `site_*` assigns in memory (for preview) 2. Set `site_dirty = true` 3. **Do NOT call `Site.put_announcement()` or `Site.put_footer_content()`** #### 2.3 Store original site state When loading site state in `load_site_state/1`: ```elixir original = %{ announcement_text: site_settings.announcement_text, announcement_link: site_settings.announcement_link, announcement_style: site_settings.announcement_style, footer_about: site_settings.footer_about, footer_copyright: site_settings.footer_copyright, show_newsletter: site_settings.show_newsletter } |> assign(:site_editor_original, original) ``` ### Phase 3: Unified Save Button (1h) #### 3.1 Compute unified dirty state ```elixir defp compute_editor_dirty(socket) do page_dirty = socket.assigns[:editor_dirty] || false theme_dirty = socket.assigns[:theme_dirty] || false site_dirty = socket.assigns[:site_dirty] || false settings_dirty = socket.assigns[:settings_dirty] || false assign(socket, :any_dirty, page_dirty || theme_dirty || site_dirty || settings_dirty) end ``` #### 3.2 Update Save button In `editor_sheet` header, change Save button to: - Enabled when `@any_dirty` - On click: `editor_save_all` event #### 3.3 Implement unified save handler ```elixir defp handle_editor_event("editor_save_all", _params, socket) do socket = save_all_tabs(socket) {:halt, socket} end defp save_all_tabs(socket) do socket |> maybe_save_page() |> maybe_save_theme() |> maybe_save_site() |> maybe_save_settings() |> assign(:editor_save_status, :saved) |> schedule_save_status_clear() end defp maybe_save_page(socket) do if socket.assigns[:editor_dirty] do # existing page save logic else socket end end defp maybe_save_theme(socket) do if socket.assigns[:theme_dirty] do Settings.update_theme_settings(socket.assigns.theme_editor_settings) assign(socket, :theme_dirty, false) else socket end end defp maybe_save_site(socket) do if socket.assigns[:site_dirty] do site = socket.assigns Site.put_announcement(site.site_announcement_text, site.site_announcement_link, site.site_announcement_style) Site.put_footer_content(site.site_footer_about, site.site_footer_copyright, site.site_footer_show_newsletter) assign(socket, :site_dirty, false) else socket end end ``` ### Phase 4: Visual Indicators (0.5h) #### 4.1 Dirty dots on tab labels In `editor_sheet` tablist: ```heex ``` CSS: ```css .editor-tab-dirty-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--color-warning); margin-left: 4px; } ``` #### 4.2 Update EditorKeyboard hook Pass unified dirty state to the hook: ```heex