HISTORY_PAGES_UI_CONVERGE_PLAN

Plan: Pages ↔ History Pages UI Convergence

Current State

Two parallel UIs for editing Galaxy Pages exist:

AspectLegacy PagesHistory Pages
Entry/pages/editor?id=X/histories/:hid/pages/:pid
EditorPageEditorMarkdown.vue (Options API, 115 lines)HistoryNotebookView.vue (Composition API, 344 lines)
SavePOST /api/pages/{id}/revisions (creates revision, no metadata)PUT /api/pages/{id} with edit_source
StoreNone — local component state onlyhistoryNotebookStore.ts (455 lines, Pinia)
API clientPageEditor/services.js (15 lines, raw axios)api/historyPages.ts (152 lines, typed)
RevisionsNone in UI (API exists but unused)Full UI: list, preview, restore
ChatNoneNotebookChatPanel.vue (467 lines)
Diff viewsNoneProposalDiffView, SectionPatchView, sectionDiffUtils
Window ManagerNoneYes — displayOnly mode opens in WinBox
PermissionsObjectPermissionsModal (complex sharing UI)None — inherits from history
Dirty trackingNone — no unsaved indicatorYes — isDirty computed, “Unsaved” badge
Title editingSeparate form (PageForm, edit mode)Inline ClickToEdit in toolbar

Shared: Both use MarkdownEditor.vue (via mode prop) and Markdown.vue for rendering.


Goals

  1. Chat for all pages — AI assistant available when editing any markdown page (not just history-attached)
  2. Revisions for all pages — revision list, preview, restore available in legacy page editor
  3. Window Manager for all pages — pages list (/pages/list) can open pages in WinBox
  4. Single editor component — one page editor that adapts based on context (history-attached vs standalone)
  5. Single API client — one typed API module for all page operations

Phase 1: Unified API Client

Goal: Single api/pages.ts that both editors use.

Changes

Why first

Everything else depends on a single API client. No UI changes.


Phase 2: Generalize the Pinia Store

Goal: historyNotebookStore becomes usePageEditorStore — works for both history-attached and standalone pages.

Changes

Rename stores/historyNotebookStore.tsstores/pageEditorStore.ts

Add:

Keep all existing state and actions — they’re generic enough:

Standalone mode behavior

When mode === "standalone":

Tests


Phase 3: Unified Page Editor Component

Goal: Replace PageEditorMarkdown.vue + HistoryNotebookView.vue edit mode with a single editor.

New component: PageEditorView.vue

Absorbs the edit mode from HistoryNotebookView.vue (toolbar + editor + chat + revisions), replacing PageEditorMarkdown.vue.

PageEditorView.vue
  props: pageId, historyId?, displayOnly?
  ├─ Toolbar
  │   ├─ Back (→ /pages/list or /histories/:hid/pages)
  │   ├─ ClickToEdit title
  │   ├─ Revisions button + badge         (always)
  │   ├─ Preview button                    (always)
  │   ├─ Chat button                       (when agents configured)
  │   ├─ Permissions button                (standalone only)
  │   ├─ Save button + Unsaved indicator   (always)
  │   └─ Save & View button                (standalone only)
  ├─ Body
  │   ├─ SplitView (when chat open)
  │   │   ├─ MarkdownEditor
  │   │   └─ PageChatPanel
  │   ├─ MarkdownEditor + RevisionPanel (when revisions open)
  │   └─ MarkdownEditor (default)
  └─ RevisionView (when viewing specific revision)

What changes

What doesn’t change

Toolbar differences by mode

FeaturehistoryId setstandalone
Back button target/histories/:hid/pages/pages/list
Permissions buttonHiddenShown
Save & ViewHiddenShown
ChatSends page_id + agent reads historySends page_id (no history context)
Window ManagerSupported (existing)Add support

Tests


Phase 4: Chat for Standalone Pages

Goal: AI assistant available when editing any page, not just history-attached.

Backend

The agent needs a page content context but no history_id when editing standalone pages.

Current flow: POST /api/chat with page_id → backend looks up page → extracts history_id → passes to agent.

Change: when page.history_id is null, skip history tools. The agent can still do full-replacement and section-patch edits on the page content — it just can’t call list_history_datasets etc.

Options:

  1. Keep notebook_assistant agent — it already handles “no history” gracefully (tools just return empty). Simplest.
  2. New page_assistant agent — stripped version without history tools. Cleaner but more code.

Recommendation: Option 1. The agent’s system prompt says “you’re editing a notebook in history X” — when history_id is null, adjust the prompt to say “you’re editing a standalone page” and disable history tools. One if branch in notebook_assistant.py.

Frontend

Tests


Phase 5: Revisions for Standalone Pages

Goal: Revision list, preview, and restore available in the standalone page editor.

What exists

The backend already supports revisions for all pages:

What’s needed

No-ops

Tests


Phase 6: Window Manager for Standalone Pages

Goal: Grid list page actions can open pages in WinBox.

Changes

In Grid/configs/pages.ts — add a “View in Window” operation for each page row that calls:

Galaxy.frame.add({ url: `/published/page?id=${pageId}&hide_panels=true&hide_masthead=true`, title: `Page: ${title}` });

In PageEditor.vue — when navigating to preview (Save & View), detect WM and optionally open in WinBox.

In PageView.vue — add displayOnly handling similar to HistoryNotebookView:

Tests


Phase 7: Rename & Cleanup

Goal: Remove “Notebook” naming, consolidate directory structure.

Renames

FromTo
components/HistoryNotebook/components/PageEditor/ (merge with existing)
HistoryNotebookView.vueHistoryPageView.vue (list + display modes only)
HistoryNotebookEditor.vueDelete (absorbed into PageEditorView)
HistoryNotebookList.vueHistoryPageList.vue
HistoryNotebookSplit.vueEditorSplitView.vue
NotebookChatPanel.vuePageChatPanel.vue
NotebookRevisionList.vuePageRevisionList.vue
NotebookRevisionView.vuePageRevisionView.vue
stores/historyNotebookStore.tsstores/pageEditorStore.ts
useHistoryNotebookStoreusePageEditorStore

Deletes

Test updates


Phase Dependency Graph

Phase 1 (API client)

Phase 2 (store)

Phase 3 (editor component)

Phase 4 (chat) ←→ Phase 5 (revisions)  [parallel]
  ↓                ↓
Phase 6 (window manager)

Phase 7 (rename + cleanup)

What’s Already Done (No-Ops)

  1. Markdown renderingMarkdown.vue and MarkdownEditor.vue already shared
  2. Diff utilitiessectionDiffUtils.ts is generic (operates on strings, not page types)
  3. Revision data typesPageRevisionSummary/Details already match backend schema
  4. Chat sub-componentsChatMessageCell, ChatInput, chatUtils already extracted and shared
  5. API endpoints — backend already supports revisions and chat for all pages
  6. Content pipelinecontent vs content_editor dual-field pattern works for all markdown pages

Effort Estimate

PhaseFiles changedNew filesDeleted filesComplexity
1401Low
2200Medium
3411High
4300Medium
5200Low
6300Medium
7~2002Low (mechanical)

Resolved Questions

  1. Agent type: Reuse notebook_assistant. One if-branch in prompt assembly — skip history tools & say “standalone page” when history_id is null. Less code, one agent to maintain.

  2. DirectiveMode: Collapse to "page" everywhere. The directives and drag-and-drop behavior are identical — dragging a dataset from the history panel into any page editor inserts the same history_dataset_display(history_dataset_id=...) directive. No gating needed. Remove "history_notebook" from DirectiveMode union, use "page" for all page editors.

  3. Save endpoint: All frontend saves through PUT /api/pages/{id}. Keep legacy POST /api/pages/{id}/revisions endpoint for API compat but mark deprecated. Frontend stops using it. PageEditor/services.js deleted.

  4. Route path: /pages/:id/edit (path param). Grid config updated to link directly to new route. Old /pages/editor?id=X kept as a redirect for bookmarks.

  5. Grid navigation: “Edit Content” links directly to /pages/:id/edit. No intermediate redirect.