# Draft-then-publish workflow **Status:** Planned (after unified-editing-mode) ## Problem Current editing workflow uses explicit save — changes are either saved (immediately live) or lost on navigation. This creates: 1. **Data loss anxiety** — navigate away accidentally, lose all work 2. **`beforeunload` warnings** — browser-native dialogs with no custom options 3. **No experimentation** — can't safely try changes without affecting live site ## Solution Implement a draft-then-publish model where all edits auto-save as drafts, visitors see only published versions, and explicit "Publish" commits changes live. ## Design principles 1. **Drafts are per-user** — each admin sees their own pending changes 2. **Visitors always see published** — no accidental exposure of drafts 3. **Auto-save constantly** — no risk of lost work 4. **Clear visual distinction** — obvious when viewing draft vs published 5. **Single publish action** — one button publishes all pending changes ## Scope Three types of editable content: | Type | Storage | Draft approach | |------|---------|----------------| | Page blocks | `pages.blocks` (JSON) | `draft_blocks` column | | Theme settings | `settings.theme_settings` (JSON) | `draft_theme_settings` key | | Shop settings | `settings` table (various keys) | `draft_*` keys or `settings_drafts` table | ## Data model ### Option A: Draft columns (recommended for simplicity) ```elixir # Migration: add draft columns to pages alter table(:pages) do add :draft_blocks, :map add :draft_user_id, references(:users, type: :binary_id, on_delete: :nilify_all) add :draft_updated_at, :utc_datetime_usec end # New settings keys # - "draft_theme_settings" → JSON blob # - "draft_site_name" → string # - "draft_header_nav" → JSON # etc. ``` ### Option B: Separate drafts table (more flexible) ```elixir create table(:drafts, primary_key: false) do add :id, :binary_id, primary_key: true add :user_id, references(:users, type: :binary_id, on_delete: :delete_all), null: false add :entity_type, :string, null: false # "page", "theme", "settings" add :entity_id, :string # page id, or nil for global add :data, :map, null: false timestamps(type: :utc_datetime_usec) end create unique_index(:drafts, [:user_id, :entity_type, :entity_id]) ``` **Recommendation:** Start with Option A (simpler), migrate to Option B if multi-user or more complex draft needs emerge. ## Implementation ### Phase 1: Page drafts | Task | Est | Notes | |------|-----|-------| | Add draft columns to pages table | 30m | Migration | | Update Pages context with draft functions | 1h | `save_draft/2`, `publish_draft/1`, `discard_draft/1`, `has_draft?/1` | | Modify PageEditorHook to auto-save drafts | 1h | Debounced save on every change | | Add "Publish" and "Discard" buttons to editor | 1h | Replace current "Save" | | Show draft indicator when viewing page with unpublished changes | 1h | Banner or badge | | Remove `beforeunload` warning (no longer needed) | 15m | Drafts persist | ### Phase 2: Theme drafts | Task | Est | Notes | |------|-----|-------| | Add draft_theme_settings to settings | 30m | New key | | Update Settings context with theme draft functions | 1h | Similar to pages | | Modify theme editor to auto-save drafts | 1h | | | Theme preview shows draft, shop shows published | 1h | Conditional CSS injection | | Add publish/discard to theme editor | 30m | | ### Phase 3: Settings drafts | Task | Est | Notes | |------|-----|-------| | Decide which settings are draftable | 30m | Not all need drafts (e.g. API keys) | | Add draft handling to Settings context | 1.5h | | | Update settings editor to use drafts | 1h | | | Unified "Publish all" option | 1h | Publish page + theme + settings together | ### Phase 4: Polish | Task | Est | Notes | |------|-----|-------| | Draft age indicator ("Last saved 5 mins ago") | 30m | | | Draft conflict handling (stale draft warning) | 1h | Edge case: published changed since draft created | | Version history / rollback (stretch goal) | 3h | Store previous published versions | ## UX flow ### Editing a page 1. Admin navigates to page, clicks "Edit" 2. Editor panel opens, showing current published state 3. Admin makes changes — **auto-saved as draft every few seconds** 4. Admin can navigate away freely — draft persists 5. Admin returns later — draft loaded automatically 6. "Publish" button commits draft → published 7. "Discard" button deletes draft, reverts to published ### Visual indicators ``` ┌─────────────────────────────────────────────────────────┐ │ ⚠️ You have unpublished changes (last saved 2 mins ago) │ │ [Publish] [Discard] │ └─────────────────────────────────────────────────────────┘ ``` When admin views a page with a draft: - Banner at top of editor panel - "Unpublished" badge next to page title - Different background tint in editor (subtle) ### Visitors see published only The `render_page/1` function checks: 1. If admin is viewing AND has draft → show draft 2. Otherwise → show published `blocks` ```elixir defp get_blocks_for_render(page, current_user) do if current_user && page.draft_user_id == current_user.id && page.draft_blocks do page.draft_blocks else page.blocks end end ``` ## Migration path 1. Deploy with draft support disabled (feature flag) 2. Run migration to add draft columns 3. Enable draft mode 4. Existing pages continue working (no draft = show published) 5. New edits create drafts ## Future considerations - **Multi-user drafts** — currently single draft per page, could extend to per-user drafts - **Draft preview link** — shareable URL that shows draft to non-admins (for review) - **Scheduled publishing** — "Publish at 9am tomorrow" - **Draft comparison** — side-by-side diff of draft vs published ## Dependencies - Unified editing mode (phases 1-7) must be complete first - This work builds on the editor panel UI established there ## Estimates | Phase | Est | |-------|-----| | Phase 1: Page drafts | 5h | | Phase 2: Theme drafts | 4h | | Phase 3: Settings drafts | 4h | | Phase 4: Polish | 5h | | **Total** | **18h** | ## References - [Squarespace draft model](https://support.squarespace.com/hc/en-us/articles/205815578-Saving-and-publishing-site-changes) - [Shopify theme versions](https://shopify.dev/docs/themes/architecture#theme-versions) - [WordPress autosave](https://developer.wordpress.org/plugins/post/autosave/)