chore: more complete editor and quick action specs
This commit is contained in:
@@ -2,7 +2,9 @@
|
|||||||
-- bDS Editor Views
|
-- bDS Editor Views
|
||||||
-- Scope: UI content area (all waves + extensions)
|
-- Scope: UI content area (all waves + extensions)
|
||||||
-- Distilled from: PostEditor.tsx, MediaEditor.tsx, Editor.tsx (Dashboard),
|
-- Distilled from: PostEditor.tsx, MediaEditor.tsx, Editor.tsx (Dashboard),
|
||||||
-- SettingsView.tsx, StyleView.tsx, ChatPanel.tsx, TagsView
|
-- SettingsView.tsx, StyleView.tsx, ChatPanel.tsx, TagsView,
|
||||||
|
-- ScriptEditor.tsx, TemplateEditor.tsx, MenuEditorView.tsx,
|
||||||
|
-- MetadataDiffPanel.tsx, ImportAnalysisView.tsx
|
||||||
|
|
||||||
-- Describes the layout and behaviour of each editor view rendered in
|
-- Describes the layout and behaviour of each editor view rendered in
|
||||||
-- the main content area when a tab is active (or dashboard when no tab).
|
-- the main content area when a tab is active (or dashboard when no tab).
|
||||||
@@ -180,6 +182,161 @@ invariant PostDirtyTracking {
|
|||||||
-- Dirty indicator shown in header and tab bar
|
-- Dirty indicator shown in header and tab bar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- ─── Post editor actions ────────────────────────────────────
|
||||||
|
|
||||||
|
-- Shared modal for presenting AI suggestions with per-field accept/reject.
|
||||||
|
-- Used by PostAIAnalysis and MediaAIImageAnalysis.
|
||||||
|
value AISuggestionsModal {
|
||||||
|
fields: List<AISuggestionField>
|
||||||
|
-- Confirm applies only accepted fields; Cancel discards all
|
||||||
|
}
|
||||||
|
|
||||||
|
value AISuggestionField {
|
||||||
|
label: String
|
||||||
|
current_value: String
|
||||||
|
suggested_value: String
|
||||||
|
accepted: Boolean -- checkbox, default true
|
||||||
|
}
|
||||||
|
|
||||||
|
rule PostAIAnalysis {
|
||||||
|
when: PostAIAnalysisRequested(post_id)
|
||||||
|
-- Gate: airplane mode check (see flows.allium AIOperationGating)
|
||||||
|
-- Uses title model (not default chat model)
|
||||||
|
-- Input: post title + excerpt + content (first 2000 chars)
|
||||||
|
-- Response: suggested title, excerpt, slug
|
||||||
|
-- Opens AISuggestionsModal with 3 fields:
|
||||||
|
-- Each field: current value, suggested value, accept checkbox
|
||||||
|
-- Slug field locked (no accept checkbox) if post was ever published
|
||||||
|
-- On confirm: applies only checked fields, triggers auto-save
|
||||||
|
}
|
||||||
|
|
||||||
|
rule PostTranslateAction {
|
||||||
|
when: PostTranslateRequested(post_id, target_language)
|
||||||
|
-- Gate: airplane mode check
|
||||||
|
-- Opens language picker modal:
|
||||||
|
-- Available target languages from project blogLanguages
|
||||||
|
-- Existing translations shown with status badge (draft/published)
|
||||||
|
-- Two sequential AI calls via title model:
|
||||||
|
-- 1. Translate metadata (title, excerpt) to target language
|
||||||
|
-- 2. Translate content (full markdown body) to target language
|
||||||
|
-- Creates/updates translation record in DB
|
||||||
|
-- If source post is published: transitions source to draft
|
||||||
|
-- (copies file content back to DB so it can be edited)
|
||||||
|
}
|
||||||
|
|
||||||
|
rule PostAutoTranslateOnSave {
|
||||||
|
when: PostSaved(post_id)
|
||||||
|
-- Gate: airplane mode check + auto_translate not disabled (doNotTranslate=false)
|
||||||
|
-- For each configured blog language missing a translation:
|
||||||
|
-- Enqueue background translation task (title model)
|
||||||
|
-- Each task: translate metadata + content, create translation record
|
||||||
|
-- Cascades to linked media: for each linked media item,
|
||||||
|
-- translate media metadata for missing languages
|
||||||
|
-- See flows.allium AutoTranslationChain for full chain
|
||||||
|
}
|
||||||
|
|
||||||
|
rule PostPublishAction {
|
||||||
|
when: PostPublishRequested(post_id)
|
||||||
|
-- Implicit save first (awaited) if post is dirty
|
||||||
|
-- Then calls engine publish (see flows.allium PublishPostSideEffects)
|
||||||
|
-- Also publishes all translations whose source language is published
|
||||||
|
-- UI updates: status badge -> published, sidebar section move
|
||||||
|
}
|
||||||
|
|
||||||
|
rule PostDiscardChanges {
|
||||||
|
when: PostDiscardRequested(post_id)
|
||||||
|
-- Only available for published posts with pending draft changes
|
||||||
|
-- Native confirm dialog (rfd): "Discard changes to this post?"
|
||||||
|
-- On confirm: reads published version from .md file,
|
||||||
|
-- restores DB to published state (content=null, status=published)
|
||||||
|
-- Editor reloads with restored content
|
||||||
|
}
|
||||||
|
|
||||||
|
rule PostDeleteAction {
|
||||||
|
when: PostDeleteRequested(post_id)
|
||||||
|
-- Native confirm dialog (rfd): "Delete this post?"
|
||||||
|
-- If published: also deletes .md file and all translation files
|
||||||
|
-- If never published: only deletes DB record
|
||||||
|
-- Removes from DB, closes tab, sidebar removes item
|
||||||
|
-- See flows.allium DeletePostSideEffects
|
||||||
|
}
|
||||||
|
|
||||||
|
rule PostInsertLink {
|
||||||
|
when: PostInsertLinkRequested(post_id)
|
||||||
|
-- Keyboard shortcut: Ctrl/Cmd+K
|
||||||
|
-- Opens InsertModal with two tabs: Internal, External
|
||||||
|
-- Internal tab:
|
||||||
|
-- Search input (debounced, queries post titles)
|
||||||
|
-- Results list: title + status badge (draft/published)
|
||||||
|
-- If semantic similarity enabled: results ranked by similarity
|
||||||
|
-- Click inserts markdown link: [title](/YYYY/MM/DD/slug)
|
||||||
|
-- "Create Post" option at bottom of search results:
|
||||||
|
-- Creates new post with search query as title
|
||||||
|
-- Inserts link to newly created post
|
||||||
|
-- External tab:
|
||||||
|
-- URL input + optional display text input
|
||||||
|
-- Inserts: [text](url) or bare url if no display text
|
||||||
|
}
|
||||||
|
|
||||||
|
rule PostInsertMedia {
|
||||||
|
when: PostInsertMediaRequested(post_id)
|
||||||
|
-- Opens InsertModal (media search variant)
|
||||||
|
-- Search input, grid of media items with bds-thumb:// thumbnails
|
||||||
|
-- Click inserts markdown:
|
||||||
|
-- Images: 
|
||||||
|
-- Non-images: [originalName](bds-media://id)
|
||||||
|
}
|
||||||
|
|
||||||
|
rule PostGalleryAction {
|
||||||
|
when: PostGalleryRequested(post_id)
|
||||||
|
-- Opens gallery overlay showing all media linked to this post
|
||||||
|
-- Image grid with bds-thumb:// thumbnails
|
||||||
|
-- Click on image opens lightbox (full-size bds-media:// preview)
|
||||||
|
-- Lightbox: left/right arrow navigation, close button, ESC to close
|
||||||
|
}
|
||||||
|
|
||||||
|
rule PostDragDropImage {
|
||||||
|
when: ImageDroppedOnEditor(post_id, file_path)
|
||||||
|
-- Chain of operations (see flows.allium DragDropImageChain):
|
||||||
|
-- 1. Import media file -> media record + file copy + sidecar
|
||||||
|
-- 2. Generate thumbnails (async: small/medium/large/ai)
|
||||||
|
-- 3. Link media to post (update sidecar linkedPostIds)
|
||||||
|
-- 4. Insert markdown image at cursor: 
|
||||||
|
-- 5. If AI available: AI image analysis (async, auto-applied, no modal)
|
||||||
|
-- 6. If auto-translate enabled: cascade translate media metadata
|
||||||
|
-- Steps 1-4 synchronous. Steps 5-6 background tasks.
|
||||||
|
}
|
||||||
|
|
||||||
|
rule PostLanguageDetect {
|
||||||
|
when: PostLanguageDetectRequested(post_id)
|
||||||
|
-- Gate: airplane mode check
|
||||||
|
-- Sends content sample to title model
|
||||||
|
-- Auto-sets post language field (no modal)
|
||||||
|
-- Triggers auto-save
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Post editor mode switching:
|
||||||
|
-- Visual mode: rich-text WYSIWYG (TipTap in TS, not yet in Rust)
|
||||||
|
-- Markdown mode: code editor with markdown-with-macros highlighting
|
||||||
|
-- Preview mode: iframe with rendered HTML via preview server
|
||||||
|
-- Mode persists per-session, default from editor settings
|
||||||
|
|
||||||
|
-- Language tab switching (translation editing):
|
||||||
|
-- Flag emoji buttons in metadata bar for canonical + each translation
|
||||||
|
-- Clicking a flag loads that language's draft content into editor
|
||||||
|
-- Each flag shows status (draft/published) via CSS class
|
||||||
|
-- Active flag highlighted
|
||||||
|
|
||||||
|
-- LinkedMediaPanel (right column of metadata):
|
||||||
|
-- Shows media items linked to this post
|
||||||
|
-- Actions:
|
||||||
|
-- Import & Link: native file dialog, runs full import + link chain
|
||||||
|
-- Link Existing: opens media picker modal (search + grid)
|
||||||
|
-- Unlink: removes media-post link from sidecar (no confirmation)
|
||||||
|
-- Reorder: drag handle on each item
|
||||||
|
-- Click: opens media item in its own tab (preview intent)
|
||||||
|
-- Import & Link triggers: import -> thumbnail -> link -> optional AI analysis
|
||||||
|
|
||||||
-- ─── Media editor ─────────────────────────────────────────────
|
-- ─── Media editor ─────────────────────────────────────────────
|
||||||
|
|
||||||
-- Single column: preview area on top, metadata form below.
|
-- Single column: preview area on top, metadata form below.
|
||||||
@@ -244,6 +401,88 @@ value LinkedPostItem {
|
|||||||
-- Replace File opens a native file dialog.
|
-- Replace File opens a native file dialog.
|
||||||
-- Delete shows confirmation with references (linked posts).
|
-- Delete shows confirmation with references (linked posts).
|
||||||
|
|
||||||
|
-- ─── Media editor actions ─────────────────────────────────────
|
||||||
|
|
||||||
|
rule MediaAIImageAnalysis {
|
||||||
|
when: MediaAIImageAnalysisRequested(media_id)
|
||||||
|
-- Gate: airplane mode check (see flows.allium AIOperationGating)
|
||||||
|
-- Only available for image/* MIME types (button hidden for non-images)
|
||||||
|
-- Uses image analysis model (vision-capable, not title model)
|
||||||
|
-- Input: AI-optimized JPEG thumbnail (448x448, generated on import)
|
||||||
|
-- Response: suggested title, alt text, caption
|
||||||
|
-- Opens AISuggestionsModal with 3 fields (title, alt, caption)
|
||||||
|
-- On confirm: applies checked fields, triggers explicit save
|
||||||
|
}
|
||||||
|
|
||||||
|
rule MediaDetectLanguage {
|
||||||
|
when: MediaDetectLanguageRequested(media_id)
|
||||||
|
-- Gate: airplane mode check
|
||||||
|
-- Input: concatenation of title + alt + caption text
|
||||||
|
-- Response: detected language code
|
||||||
|
-- Immediately persists to media record (no modal, no confirmation)
|
||||||
|
-- Triggers sidecar rewrite
|
||||||
|
}
|
||||||
|
|
||||||
|
rule MediaTranslateMetadata {
|
||||||
|
when: MediaTranslateMetadataRequested(media_id, target_language)
|
||||||
|
-- Gate: airplane mode check
|
||||||
|
-- Opens language picker modal (same pattern as post translate)
|
||||||
|
-- Two-step process:
|
||||||
|
-- 1. If source language not set: detect it first (auto-persist)
|
||||||
|
-- 2. Translate title, alt, caption to target language via title model
|
||||||
|
-- Creates/updates media translation record
|
||||||
|
-- Writes translated sidecar file: {path}.{lang}.meta
|
||||||
|
}
|
||||||
|
|
||||||
|
rule MediaReplaceFile {
|
||||||
|
when: MediaReplaceFileRequested(media_id)
|
||||||
|
-- Opens native file dialog (no MIME type filter)
|
||||||
|
-- Copies selected file over existing media file path
|
||||||
|
-- If image: regenerates thumbnails synchronously (awaited)
|
||||||
|
-- Preview area updates with cache-busting timestamp query param
|
||||||
|
}
|
||||||
|
|
||||||
|
rule MediaDeleteAction {
|
||||||
|
when: MediaDeleteRequested(media_id)
|
||||||
|
-- Opens ConfirmDeleteModal (custom modal, not native dialog)
|
||||||
|
-- Shows: media display name, linked posts count and list
|
||||||
|
-- Two buttons: Cancel, Delete (destructive red style)
|
||||||
|
-- On confirm: deletes file, sidecar, thumbnails, all translations,
|
||||||
|
-- post-media links, FTS index entry
|
||||||
|
-- Closes media tab, sidebar removes item
|
||||||
|
-- See flows.allium DeleteMediaSideEffects
|
||||||
|
}
|
||||||
|
|
||||||
|
rule MediaLinkToPost {
|
||||||
|
when: MediaLinkToPostRequested(media_id)
|
||||||
|
-- Opens inline post picker overlay (not a modal, positioned near button)
|
||||||
|
-- Search input filtering unlinked posts by title
|
||||||
|
-- Up to 10 results shown, "and N more" text if results exceed 10
|
||||||
|
-- Click links media to selected post (updates sidecar linkedPostIds)
|
||||||
|
-- Linked posts list refreshes immediately
|
||||||
|
}
|
||||||
|
|
||||||
|
rule MediaTranslationEdit {
|
||||||
|
when: MediaTranslationEditClicked(media_id, language)
|
||||||
|
-- Loads translation fields inline (title, alt, caption) for that language
|
||||||
|
-- Edit in place, save persists to translated sidecar {path}.{lang}.meta
|
||||||
|
}
|
||||||
|
|
||||||
|
rule MediaTranslationRefresh {
|
||||||
|
when: MediaTranslationRefreshClicked(media_id, language)
|
||||||
|
-- Gate: airplane mode check
|
||||||
|
-- Re-translates from source language to target via title model
|
||||||
|
-- Overwrites existing translation fields
|
||||||
|
-- Rewrites translated sidecar file
|
||||||
|
}
|
||||||
|
|
||||||
|
rule MediaTranslationDelete {
|
||||||
|
when: MediaTranslationDeleteClicked(media_id, language)
|
||||||
|
-- Deletes translation record from DB
|
||||||
|
-- Deletes translated sidecar file: {path}.{lang}.meta
|
||||||
|
-- No confirmation dialog
|
||||||
|
}
|
||||||
|
|
||||||
-- ─── Settings view ────────────────────────────────────────────
|
-- ─── Settings view ────────────────────────────────────────────
|
||||||
|
|
||||||
-- VS Code-style settings page with header + search bar + scrollable sections.
|
-- VS Code-style settings page with header + search bar + scrollable sections.
|
||||||
@@ -298,6 +537,44 @@ value LinkedPostItem {
|
|||||||
-- Per-agent rows: Claude Code, Claude Desktop, GitHub Copilot, Gemini CLI,
|
-- Per-agent rows: Claude Code, Claude Desktop, GitHub Copilot, Gemini CLI,
|
||||||
-- OpenCode, Mistral Vibe, OpenAI Codex — each with Add/Remove toggle
|
-- OpenCode, Mistral Vibe, OpenAI Codex — each with Add/Remove toggle
|
||||||
|
|
||||||
|
-- ─── Settings view actions ──────────────────────────────────
|
||||||
|
|
||||||
|
-- API key validation:
|
||||||
|
-- On Save: test API call to provider endpoint before persisting
|
||||||
|
-- On validation failure: toast error message, key not saved
|
||||||
|
-- On success: key saved, masked display shown
|
||||||
|
|
||||||
|
-- Local provider enable/disable:
|
||||||
|
-- Enabling Ollama/LM Studio: fetches model list, shows capabilities table
|
||||||
|
-- Capabilities table columns: model name, chat, title, image (checkmarks)
|
||||||
|
-- Models auto-fetched on enable; manual refresh via Refresh Models button
|
||||||
|
|
||||||
|
-- Model catalog refresh:
|
||||||
|
-- Refresh button next to Default Model selector
|
||||||
|
-- Fetches available models from all enabled providers
|
||||||
|
-- Updates optgroup dropdown with provider groupings
|
||||||
|
-- Per-model info: max output tokens, context window, pricing
|
||||||
|
|
||||||
|
-- Rebuild operations (Data Maintenance section):
|
||||||
|
-- 6 buttons, each executes immediately (no confirmation dialog)
|
||||||
|
-- Runs as background task with progress visible in Tasks panel
|
||||||
|
-- On completion: wholesale replaces store data for that entity type
|
||||||
|
-- Sidebar and editors re-render with fresh data
|
||||||
|
|
||||||
|
-- Category management (Content Settings):
|
||||||
|
-- Protected categories: article, aside, page, picture (Remove button disabled)
|
||||||
|
-- Add Category: text input + Add button, validates non-empty and unique
|
||||||
|
-- Per-category settings: editable inline in table row
|
||||||
|
-- "Reset to Defaults" restores default categories set
|
||||||
|
|
||||||
|
-- MCP per-agent config:
|
||||||
|
-- Add/Remove toggle per agent row
|
||||||
|
-- Status badge: shows port number when running, "Not running" otherwise
|
||||||
|
|
||||||
|
-- Bookmarklet (Project Settings):
|
||||||
|
-- Copy button copies bookmarklet JavaScript to clipboard
|
||||||
|
-- Bookmarklet uses project's publicUrl to construct POST endpoint
|
||||||
|
|
||||||
-- ─── Style view ───────────────────────────────────────────────
|
-- ─── Style view ───────────────────────────────────────────────
|
||||||
|
|
||||||
-- Simple vertical layout: header, theme picker, controls row, preview.
|
-- Simple vertical layout: header, theme picker, controls row, preview.
|
||||||
@@ -313,6 +590,24 @@ value LinkedPostItem {
|
|||||||
|
|
||||||
-- Apply Theme persists to project metadata.
|
-- Apply Theme persists to project metadata.
|
||||||
|
|
||||||
|
-- ─── Style view actions ─────────────────────────────────────
|
||||||
|
|
||||||
|
rule StyleThemeSelect {
|
||||||
|
when: StyleThemeSelected(theme_name)
|
||||||
|
-- Updates iframe preview immediately (query param change)
|
||||||
|
-- Does NOT persist until Apply is clicked
|
||||||
|
}
|
||||||
|
|
||||||
|
rule StyleApplyTheme {
|
||||||
|
when: StyleApplyRequested(theme_name)
|
||||||
|
-- Persists picoTheme to project metadata (meta/project.json)
|
||||||
|
-- See flows.allium UpdateProjectMetadataSideEffects
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Preview mode dropdown (Auto/Light/Dark):
|
||||||
|
-- Controls iframe query param, live update on change
|
||||||
|
-- Does not persist — local UI state only
|
||||||
|
|
||||||
-- ─── Tags view ────────────────────────────────────────────────
|
-- ─── Tags view ────────────────────────────────────────────────
|
||||||
|
|
||||||
-- Three sections controlled by sidebar TagsNav:
|
-- Three sections controlled by sidebar TagsNav:
|
||||||
@@ -326,6 +621,33 @@ value LinkedPostItem {
|
|||||||
-- 3. Merge: source tag select, target tag select, Merge button.
|
-- 3. Merge: source tag select, target tag select, Merge button.
|
||||||
-- Merges all posts from source tag into target, removes source.
|
-- Merges all posts from source tag into target, removes source.
|
||||||
|
|
||||||
|
-- ─── Tags view actions ──────────────────────────────────────
|
||||||
|
|
||||||
|
-- Cloud section:
|
||||||
|
-- Tags sized by post count (same 11-22px scaling as dashboard)
|
||||||
|
-- Tags with colours get coloured background
|
||||||
|
-- Click selects tag: switches to Manage section, loads tag data
|
||||||
|
|
||||||
|
-- Manage section:
|
||||||
|
-- Create new: empty form, Save creates tag + writes tags.json
|
||||||
|
-- Edit existing: loads tag data into form, Save updates
|
||||||
|
-- Tag name input, colour picker, template select (optional)
|
||||||
|
-- Colour picker: 17 preset colour swatches + custom hex input field
|
||||||
|
-- Delete: opens ConfirmDialog showing post count
|
||||||
|
-- "This tag is used in N posts. Delete anyway?"
|
||||||
|
-- On confirm: background task removes tag from all posts,
|
||||||
|
-- rewrites published .md files, deletes tag, writes tags.json
|
||||||
|
|
||||||
|
-- Merge section:
|
||||||
|
-- Source tag multi-select, target tag single-select
|
||||||
|
-- Merge button: opens ConfirmDialog
|
||||||
|
-- "Merge N tags into {target}? This cannot be undone."
|
||||||
|
-- On confirm: background task updates all affected posts,
|
||||||
|
-- rewrites published .md files, deletes source tags, writes tags.json
|
||||||
|
|
||||||
|
-- Sync (maintenance action):
|
||||||
|
-- Rewrites tags.json from current DB state
|
||||||
|
|
||||||
-- ─── Chat panel ───────────────────────────────────────────────
|
-- ─── Chat panel ───────────────────────────────────────────────
|
||||||
|
|
||||||
-- Three-region vertical layout: header, message area, input area.
|
-- Three-region vertical layout: header, message area, input area.
|
||||||
@@ -360,28 +682,97 @@ config {
|
|||||||
chat_input_max_height: Integer = 200
|
chat_input_max_height: Integer = 200
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- ─── Chat panel actions ─────────────────────────────────────
|
||||||
|
|
||||||
|
-- Model selector:
|
||||||
|
-- Dropdown in header, groups models by provider (optgroup)
|
||||||
|
-- Selection is per-conversation override, persisted with conversation
|
||||||
|
-- Changing model mid-conversation applies to subsequent messages only
|
||||||
|
|
||||||
|
-- Streaming behaviour:
|
||||||
|
-- Messages accumulate in real-time (markdown rendered incrementally)
|
||||||
|
-- Tool call markers: checkmark icon (done) or dot (in-progress),
|
||||||
|
-- tool name, args truncated to 30 chars for string values
|
||||||
|
-- Thinking animation (three dots) during LLM processing
|
||||||
|
|
||||||
|
-- Stop/Abort:
|
||||||
|
-- Square stop icon button, visible only during active streaming
|
||||||
|
-- Stops current generation, partial message preserved as-is
|
||||||
|
|
||||||
|
-- Assistant action dispatch:
|
||||||
|
-- Assistant tool calls can trigger navigation actions:
|
||||||
|
-- open_post(id), open_media(id), open_settings(), etc.
|
||||||
|
-- Actions dispatched through store, same as user clicks
|
||||||
|
-- Navigation actions open tabs with pin intent
|
||||||
|
|
||||||
-- ─── Git diff view ────────────────────────────────────────────
|
-- ─── Git diff view ────────────────────────────────────────────
|
||||||
|
|
||||||
-- Renders diff for a file (working tree vs HEAD) or a commit.
|
-- Renders diff for a file (working tree vs HEAD) or a commit.
|
||||||
-- File diff: id = "git-diff:{filePath}"
|
-- File diff: id = "git-diff:{filePath}"
|
||||||
-- Commit diff: id = "git-diff:commit:{commitHash}"
|
-- Commit diff: id = "git-diff:commit:{commitHash}"
|
||||||
-- Supports inline and side-by-side diff display modes (from editor settings).
|
-- Supports inline and side-by-side diff display modes (from editor settings).
|
||||||
|
-- No actions beyond viewing — changes are managed via git sidebar.
|
||||||
|
|
||||||
-- ─── Menu editor view ────────────────────────────────────────
|
-- ─── Menu editor view ────────────────────────────────────────
|
||||||
|
|
||||||
-- Visual editor for the OPML navigation menu (meta/menu.opml).
|
-- Visual editor for the OPML navigation menu (meta/menu.opml).
|
||||||
-- Drag-and-drop reordering of menu items.
|
|
||||||
-- Item types: page, submenu, category_archive, home.
|
|
||||||
-- Add/remove items, edit labels and targets.
|
|
||||||
-- See menu.allium for data model.
|
-- See menu.allium for data model.
|
||||||
|
|
||||||
|
-- Layout: toolbar at top, tree view of menu items below.
|
||||||
|
-- Each item row: drag handle, icon, label, type badge, action buttons.
|
||||||
|
-- Nested items indented to show hierarchy.
|
||||||
|
|
||||||
|
-- ─── Menu editor actions ────────────────────────────────────
|
||||||
|
|
||||||
|
-- Add item (toolbar buttons):
|
||||||
|
-- Page entry: opens lazy-loaded page picker (posts with "page" category)
|
||||||
|
-- Category archive entry: opens lazy-loaded category picker
|
||||||
|
-- Submenu: creates empty container node for nesting children
|
||||||
|
-- Home entry: always available, maximum one allowed
|
||||||
|
|
||||||
|
-- Save: serializes tree to OPML 2.0, writes meta/menu.opml
|
||||||
|
|
||||||
|
-- Move operations (per-item buttons):
|
||||||
|
-- Up/Down: reorder within same nesting level
|
||||||
|
-- Indent: nest under previous sibling (becomes child)
|
||||||
|
-- Unindent: move to parent's level (becomes next sibling of parent)
|
||||||
|
-- Home item: protected — cannot be moved or deleted
|
||||||
|
|
||||||
|
-- Drag-and-drop reorder:
|
||||||
|
-- Drag handle on each item
|
||||||
|
-- Auto-expand collapsed submenus on hover (450ms delay)
|
||||||
|
-- Drop indicators show target position and nesting level
|
||||||
|
|
||||||
|
-- Delete: removes item and all children, no confirmation dialog
|
||||||
|
-- Exception: home item cannot be deleted (button hidden)
|
||||||
|
|
||||||
-- ─── Metadata diff view ──────────────────────────────────────
|
-- ─── Metadata diff view ──────────────────────────────────────
|
||||||
|
|
||||||
-- Shows DB vs filesystem differences for all entity types.
|
-- Shows DB vs filesystem differences for all entity types.
|
||||||
-- Each diff entry: entity type, field name, DB value, file value.
|
|
||||||
-- Actions: apply file value to DB, or apply DB value to file.
|
|
||||||
-- See metadata_diff.allium for diff field definitions.
|
-- See metadata_diff.allium for diff field definitions.
|
||||||
|
|
||||||
|
-- Layout: scan button at top, results grouped by entity type below.
|
||||||
|
-- Each diff entry: entity name, field name, DB value, file value,
|
||||||
|
-- two sync buttons (DB->File, File->DB).
|
||||||
|
|
||||||
|
-- ─── Metadata diff actions ──────────────────────────────────
|
||||||
|
|
||||||
|
-- Scan: runs 4 parallel scans (posts, media, scripts, templates)
|
||||||
|
-- Compares DB records against filesystem files field by field
|
||||||
|
-- Results grouped by entity type, sorted by entity name
|
||||||
|
-- Shows count of differences per entity type in section header
|
||||||
|
|
||||||
|
-- Per-field sync:
|
||||||
|
-- DB -> File button: overwrites file field with DB value
|
||||||
|
-- File -> DB button: overwrites DB field with file value
|
||||||
|
-- No confirmation dialog for individual field syncs
|
||||||
|
-- Button disappears after successful sync (field now matches)
|
||||||
|
|
||||||
|
-- Orphan file import:
|
||||||
|
-- Files on disk with no matching DB record shown separately
|
||||||
|
-- "Import" action creates DB record from file metadata
|
||||||
|
-- Appears at bottom of each entity type section
|
||||||
|
|
||||||
-- ─── Documentation views ─────────────────────────────────────
|
-- ─── Documentation views ─────────────────────────────────────
|
||||||
|
|
||||||
-- documentation: renders DOCUMENTATION.md as styled HTML
|
-- documentation: renders DOCUMENTATION.md as styled HTML
|
||||||
@@ -392,24 +783,135 @@ config {
|
|||||||
-- site_validation: checks generated site for broken links, missing assets
|
-- site_validation: checks generated site for broken links, missing assets
|
||||||
-- translation_validation: checks translation completeness across languages
|
-- translation_validation: checks translation completeness across languages
|
||||||
|
|
||||||
|
-- ─── Validation view actions ────────────────────────────────
|
||||||
|
|
||||||
|
-- Site validation:
|
||||||
|
-- Scans generated HTML for broken internal links, missing assets
|
||||||
|
-- Results: list of issues with file path + line + description
|
||||||
|
-- No auto-fix actions — informational only
|
||||||
|
|
||||||
|
-- Translation validation:
|
||||||
|
-- Checks translation completeness across configured blog languages
|
||||||
|
-- Results: matrix of posts x languages, missing translations highlighted
|
||||||
|
-- No auto-fix actions — informational only
|
||||||
|
|
||||||
-- ─── Find duplicates view ────────────────────────────────────
|
-- ─── Find duplicates view ────────────────────────────────────
|
||||||
|
|
||||||
-- Uses embedding vectors to find semantically similar posts.
|
-- Uses embedding vectors to find semantically similar posts.
|
||||||
-- Shows candidate pairs with similarity score.
|
-- Shows candidate pairs with similarity score.
|
||||||
-- See embedding.allium for vector details.
|
-- See embedding.allium for vector details.
|
||||||
|
|
||||||
|
-- ─── Find duplicates actions ────────────────────────────────
|
||||||
|
|
||||||
|
-- Uses HNSW index of post embedding vectors for similarity search
|
||||||
|
-- Shows candidate pairs: post A title, post B title, score (0.0-1.0)
|
||||||
|
-- Click on either post title opens it in a tab (preview intent)
|
||||||
|
-- No auto-merge or auto-delete actions
|
||||||
|
|
||||||
-- ─── Script editor view ──────────────────────────────────────
|
-- ─── Script editor view ──────────────────────────────────────
|
||||||
|
|
||||||
-- Code editor for Lua scripts (macros, utilities, transforms).
|
-- Code editor for Lua scripts (macros, utilities, transforms).
|
||||||
-- Header: script title, kind badge, enabled toggle.
|
|
||||||
-- Editor: code editor with Lua syntax highlighting.
|
|
||||||
-- Toolbar: Run button, entrypoint selector.
|
|
||||||
-- See script.allium for entity model.
|
-- See script.allium for entity model.
|
||||||
|
|
||||||
|
-- Layout: header bar, code editor, no preview.
|
||||||
|
-- Header: script title (editable), kind badge, enabled toggle.
|
||||||
|
-- Editor: code editor with Lua syntax highlighting.
|
||||||
|
-- Toolbar: Run button, Save button, Delete button, entrypoint selector.
|
||||||
|
|
||||||
|
-- ─── Script editor actions ──────────────────────────────────
|
||||||
|
|
||||||
|
rule ScriptSave {
|
||||||
|
when: ScriptSaveRequested(script_id)
|
||||||
|
-- Lua syntax check first (parse validation)
|
||||||
|
-- If syntax error: blocks save, shows error inline in editor gutter
|
||||||
|
-- If valid: bumps version, saves to DB + rewrites .lua file
|
||||||
|
-- See flows.allium for UpdateScriptSideEffects
|
||||||
|
}
|
||||||
|
|
||||||
|
rule ScriptRun {
|
||||||
|
when: ScriptRunRequested(script_id)
|
||||||
|
-- Executes script in Lua runtime
|
||||||
|
-- Calls configured entrypoint function (default: render)
|
||||||
|
-- stdout/stderr directed to Output panel tab
|
||||||
|
-- Output panel auto-opens if not already visible
|
||||||
|
-- Errors shown in Output panel with line numbers
|
||||||
|
}
|
||||||
|
|
||||||
|
rule ScriptDelete {
|
||||||
|
when: ScriptDeleteRequested(script_id)
|
||||||
|
-- No confirmation dialog
|
||||||
|
-- Deletes DB record + .lua file on disk
|
||||||
|
-- Closes script tab, sidebar removes item
|
||||||
|
}
|
||||||
|
|
||||||
-- ─── Template editor view ─────────────────────────────────────
|
-- ─── Template editor view ─────────────────────────────────────
|
||||||
|
|
||||||
-- Code editor for Liquid templates (post, list, not_found, partial).
|
-- Code editor for Liquid templates (post, list, not_found, partial).
|
||||||
-- Header: template title, kind badge, enabled toggle, version.
|
|
||||||
-- Editor: code editor with Liquid syntax highlighting.
|
|
||||||
-- Preview: rendered template output in iframe.
|
|
||||||
-- See template.allium for entity model and Liquid subset.
|
-- See template.allium for entity model and Liquid subset.
|
||||||
|
|
||||||
|
-- Layout: header bar, code editor, preview pane.
|
||||||
|
-- Header: template title (editable), kind badge, enabled toggle, version display.
|
||||||
|
-- Editor: code editor with Liquid syntax highlighting.
|
||||||
|
-- Preview: rendered template output in iframe (uses sample post data).
|
||||||
|
-- Toolbar: Save button, Delete button.
|
||||||
|
|
||||||
|
-- ─── Template editor actions ────────────────────────────────
|
||||||
|
|
||||||
|
rule TemplateSave {
|
||||||
|
when: TemplateSaveRequested(template_id)
|
||||||
|
-- Liquid validation first (parse check for syntax errors)
|
||||||
|
-- If invalid: blocks save, shows error inline in editor
|
||||||
|
-- If valid: bumps version, saves to DB + rewrites .liquid file
|
||||||
|
-- See flows.allium for UpdateTemplateSideEffects
|
||||||
|
}
|
||||||
|
|
||||||
|
rule TemplateDelete {
|
||||||
|
when: TemplateDeleteRequested(template_id)
|
||||||
|
-- Checks for references: posts using this template, tags with postTemplateSlug
|
||||||
|
-- If references exist: native confirm dialog (rfd)
|
||||||
|
-- "This template is used by N posts and M tags. Force delete?"
|
||||||
|
-- Force delete: nulls templateSlug on referencing posts,
|
||||||
|
-- nulls postTemplateSlug on referencing tags
|
||||||
|
-- If no references: deletes without confirmation
|
||||||
|
-- Deletes DB record + .liquid file on disk
|
||||||
|
-- Closes template tab, sidebar removes item
|
||||||
|
}
|
||||||
|
|
||||||
|
-- ─── Import analysis view ───────────────────────────────────
|
||||||
|
|
||||||
|
-- Editor for WXR (WordPress eXtended RSS) import definitions.
|
||||||
|
-- Keyed by import definition ID. Opened as always-pinned tab.
|
||||||
|
-- Workflow: Select File -> Analyze -> Resolve Conflicts -> Execute
|
||||||
|
|
||||||
|
-- Layout: header (definition name), file selector, analysis results,
|
||||||
|
-- conflict resolution panel, taxonomy mapping, execute button.
|
||||||
|
|
||||||
|
-- ─── Import analysis actions ────────────────────────────────
|
||||||
|
|
||||||
|
rule ImportSelectAndAnalyze {
|
||||||
|
when: ImportAnalyzeRequested(definition_id, file_path)
|
||||||
|
-- Parses WXR XML file
|
||||||
|
-- Extracts: posts, pages, media, tags, categories, authors
|
||||||
|
-- Shows summary counts per entity type
|
||||||
|
-- Identifies conflicts: duplicate slugs, existing categories/tags
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Conflict resolution:
|
||||||
|
-- Per-item dropdown: Import (create new), Skip (ignore), Merge (combine)
|
||||||
|
-- Default: Import for new items, Skip for existing matches
|
||||||
|
|
||||||
|
-- Taxonomy mapping:
|
||||||
|
-- Source categories/tags mapped to existing project categories/tags
|
||||||
|
-- Dropdown per source taxonomy term -> target term or "Create New"
|
||||||
|
-- AI taxonomy analysis (optional):
|
||||||
|
-- Gate: airplane mode check
|
||||||
|
-- Suggests mappings based on name similarity via title model
|
||||||
|
|
||||||
|
rule ImportExecute {
|
||||||
|
when: ImportExecuteRequested(definition_id)
|
||||||
|
-- No confirmation dialog — executes immediately
|
||||||
|
-- Processes items per conflict resolution settings
|
||||||
|
-- Creates posts, media, tags, categories as needed
|
||||||
|
-- Summary with counts shown in import view on completion
|
||||||
|
-- Created entities appear in sidebar immediately (store updated)
|
||||||
|
}
|
||||||
|
|||||||
@@ -453,3 +453,135 @@ rule DeleteMediaTranslationSideEffects {
|
|||||||
-- updateMetadata | no | no | no | no | no | no | *.json
|
-- updateMetadata | no | no | no | no | no | no | *.json
|
||||||
-- addCategory | no | no | no | no | no | no | *.json
|
-- addCategory | no | no | no | no | no | no | *.json
|
||||||
-- * updatePost rewrites file only when templateSlug changes on published post
|
-- * updatePost rewrites file only when templateSlug changes on published post
|
||||||
|
|
||||||
|
-- ═══════════════════════════════════════════════════════════════
|
||||||
|
-- PART 4: ACTION PATTERNS AND CHAINS
|
||||||
|
-- ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
-- Cross-cutting patterns for AI operations, auto-translation,
|
||||||
|
-- drag-and-drop chains, confirmation dialogs, and offline gating.
|
||||||
|
|
||||||
|
-- ─── 4.1 AI operation gating ───────────────────────────────
|
||||||
|
|
||||||
|
invariant AIOperationGating {
|
||||||
|
-- All AI operations check airplane/offline mode before executing.
|
||||||
|
-- If offline mode enabled:
|
||||||
|
-- If local provider configured (Ollama/LM Studio): route to local model
|
||||||
|
-- Else: show toast "AI unavailable in offline mode", abort operation
|
||||||
|
-- If online: use configured provider endpoint
|
||||||
|
-- Two model categories:
|
||||||
|
-- Title model: text analysis (suggestions, translation, language detect)
|
||||||
|
-- Image model: vision-capable (image analysis only)
|
||||||
|
-- Model selection: per-operation default from settings,
|
||||||
|
-- overrideable per-conversation in chat panel only
|
||||||
|
}
|
||||||
|
|
||||||
|
-- ─── 4.2 AI suggestions pattern ────────────────────────────
|
||||||
|
|
||||||
|
-- Shared flow used by PostAIAnalysis and MediaAIImageAnalysis.
|
||||||
|
-- See editors.allium AISuggestionsModal value type.
|
||||||
|
|
||||||
|
rule AISuggestionFlow {
|
||||||
|
when: AISuggestionRequested(entity_type, entity_id)
|
||||||
|
-- 1. Check AIOperationGating (abort if offline and no local model)
|
||||||
|
-- 2. Send request to appropriate model:
|
||||||
|
-- Posts: title model, input = title + excerpt + first 2000 chars
|
||||||
|
-- Media: image model, input = 448x448 AI thumbnail JPEG
|
||||||
|
-- 3. Parse response into per-field suggestions
|
||||||
|
-- 4. Open AISuggestionsModal:
|
||||||
|
-- Each field: label, current value, suggested value, accept checkbox
|
||||||
|
-- Accept checkboxes default to true
|
||||||
|
-- Special case: post slug locked (no checkbox) if ever published
|
||||||
|
-- 5. On Confirm: apply only accepted fields to entity
|
||||||
|
-- Posts: triggers auto-save (3s timer reset)
|
||||||
|
-- Media: triggers explicit save
|
||||||
|
-- 6. On Cancel: discard all suggestions, no changes
|
||||||
|
}
|
||||||
|
|
||||||
|
-- ─── 4.3 Auto-translation chain ────────────────────────────
|
||||||
|
|
||||||
|
rule AutoTranslationChain {
|
||||||
|
when: PostSaved(post_id)
|
||||||
|
-- Gate: AIOperationGating + post.doNotTranslate must be false
|
||||||
|
-- Triggered after any post save (auto-save, manual Ctrl+S, or unmount)
|
||||||
|
-- For each configured blogLanguage missing a translation for this post:
|
||||||
|
-- 1. Enqueue background task: translate metadata (title, excerpt)
|
||||||
|
-- via title model
|
||||||
|
-- 2. Enqueue background task: translate content (full markdown)
|
||||||
|
-- via title model
|
||||||
|
-- 3. Create/update translation record in DB
|
||||||
|
-- Tasks: sequential per language, parallel across languages
|
||||||
|
-- Progress visible in Tasks panel
|
||||||
|
}
|
||||||
|
|
||||||
|
rule MediaMetadataTranslationCascade {
|
||||||
|
when: PostAutoTranslateCompleted(post_id, language)
|
||||||
|
-- After a post translation completes for a given language:
|
||||||
|
-- For each media item linked to this post:
|
||||||
|
-- If media has source language set
|
||||||
|
-- and no translation exists for {language}:
|
||||||
|
-- Enqueue background task: translate media metadata
|
||||||
|
-- (title, alt, caption) via title model
|
||||||
|
-- Creates translated sidecar file: {path}.{lang}.meta
|
||||||
|
}
|
||||||
|
|
||||||
|
-- ─── 4.4 Drag-and-drop image chain ─────────────────────────
|
||||||
|
|
||||||
|
-- Full chain when image file is dropped on post editor body.
|
||||||
|
-- See editors.allium PostDragDropImage for trigger rule.
|
||||||
|
|
||||||
|
-- Synchronous steps (user waits):
|
||||||
|
-- 1. importMedia(file) -> new media record + file copy + base sidecar
|
||||||
|
-- 2. generateThumbnails(media) -> async start (small/medium/large/ai)
|
||||||
|
-- 3. linkMediaToPost(media, post) -> update sidecar linkedPostIds
|
||||||
|
-- 4. insertMarkdownImage(cursor) -> insert  at cursor
|
||||||
|
|
||||||
|
-- Background steps (non-blocking, results auto-applied):
|
||||||
|
-- 5. If AI available: aiImageAnalysis(media)
|
||||||
|
-- Uses image model on 448x448 AI thumbnail
|
||||||
|
-- Results auto-applied to media metadata (NO modal for drag-drop,
|
||||||
|
-- unlike manual Quick Action which shows AISuggestionsModal)
|
||||||
|
-- Triggers sidecar rewrite
|
||||||
|
-- 6. If auto-translate enabled (post.doNotTranslate=false):
|
||||||
|
-- For each blogLanguage: translateMediaMetadata(media, lang)
|
||||||
|
-- Creates translated sidecar files
|
||||||
|
|
||||||
|
-- ─── 4.5 Confirmation dialog patterns ──────────────────────
|
||||||
|
|
||||||
|
-- Four distinct patterns used across the application:
|
||||||
|
|
||||||
|
-- Pattern 1: Native confirm dialog (rfd crate)
|
||||||
|
-- Simple yes/no system dialog, no custom UI
|
||||||
|
-- Used by: PostDelete, PostDiscard, TemplateDelete (when references exist)
|
||||||
|
|
||||||
|
-- Pattern 2: ConfirmDeleteModal (custom modal with reference info)
|
||||||
|
-- Shows entity name, reference counts, linked entity list
|
||||||
|
-- Two buttons: Cancel, Delete (destructive red style)
|
||||||
|
-- Used by: MediaDelete (shows linked posts), TagDelete (shows post count)
|
||||||
|
|
||||||
|
-- Pattern 3: ConfirmDialog (custom modal for non-delete confirmations)
|
||||||
|
-- Shows description of action and consequences
|
||||||
|
-- Two buttons: Cancel, Confirm
|
||||||
|
-- Used by: TagMerge ("Merge N tags into {target}? Cannot be undone.")
|
||||||
|
|
||||||
|
-- Pattern 4: No confirmation (immediate execution)
|
||||||
|
-- Action executes on click, no dialog
|
||||||
|
-- Used by: all Rebuild operations, ScriptDelete, MenuItemDelete,
|
||||||
|
-- MetadataDiff per-field sync, ImportExecute, MediaTranslationDelete,
|
||||||
|
-- MediaUnlink
|
||||||
|
|
||||||
|
-- ─── 4.6 Keyboard shortcut map ─────────────────────────────
|
||||||
|
|
||||||
|
-- All shortcuts use Cmd on macOS, Ctrl on other platforms.
|
||||||
|
|
||||||
|
-- Global:
|
||||||
|
-- Ctrl/Cmd+B: toggle sidebar visibility
|
||||||
|
-- Ctrl/Cmd+W: close active tab
|
||||||
|
|
||||||
|
-- Post editor:
|
||||||
|
-- Ctrl/Cmd+S: save post immediately (resets auto-save timer)
|
||||||
|
-- Ctrl/Cmd+K: open InsertModal (insert post link)
|
||||||
|
|
||||||
|
-- Sidebar lists:
|
||||||
|
-- Enter: open selected item as pinned tab
|
||||||
|
-- Space: open selected item as preview/transient tab
|
||||||
|
|||||||
Reference in New Issue
Block a user