393 lines
15 KiB
Plaintext
393 lines
15 KiB
Plaintext
-- allium: 1
|
|
-- bDS Miscellaneous Editor Views
|
|
-- Scope: UI content area — dashboard, menu editor, metadata diff, git diff,
|
|
-- documentation, validation, find duplicates, import analysis
|
|
-- Distilled from: Editor.tsx (Dashboard), MenuEditorView.tsx,
|
|
-- MetadataDiffPanel.tsx, ImportAnalysisView.tsx
|
|
|
|
-- Describes the layout and behaviour of smaller editor views that don't
|
|
-- warrant their own spec file.
|
|
|
|
use "./tabs.allium" as tabs
|
|
|
|
-- ─── External surfaces ──────────────────────────────────────
|
|
|
|
surface User {
|
|
provides: ImportAnalyzeRequested(definition_id, file_path)
|
|
provides: ImportExecuteRequested(definition_id)
|
|
provides: SiteValidationScanRequested()
|
|
provides: SiteValidationApplyRequested(report)
|
|
provides: TranslationValidationScanRequested()
|
|
provides: TranslationValidationFixRequested(report)
|
|
provides: DuplicateSearchRequested()
|
|
provides: DuplicatePairDismissed(post_id_a, post_id_b)
|
|
provides: DuplicatePairsBatchDismissed(pair_ids)
|
|
provides: MenuSaveRequested()
|
|
provides: MenuItemAdded(kind, data)
|
|
provides: MenuItemDeleted(item_id)
|
|
provides: MenuItemMoved(item_id, direction)
|
|
}
|
|
|
|
-- ─── Dashboard (no tab active) ───────────────────────────────
|
|
|
|
-- Shown as default/welcome view when no entity tab is active.
|
|
-- Single centered column.
|
|
|
|
value Dashboard {
|
|
title: String
|
|
subtitle: String
|
|
stats: DashboardStats
|
|
timeline: DashboardTimeline
|
|
tag_cloud: DashboardTagCloud
|
|
category_cloud: DashboardCategoryCloud
|
|
recent_posts: List<DashboardRecentPost>
|
|
}
|
|
|
|
value DashboardStats {
|
|
-- Three stat cards side by side:
|
|
total_posts: Integer
|
|
published_count: Integer
|
|
draft_count: Integer
|
|
archived_count: Integer -- shown only if > 0
|
|
media_count: Integer
|
|
image_count: Integer
|
|
total_media_size: String -- formatted B/KB/MB/GB
|
|
tag_count: Integer
|
|
category_count: Integer
|
|
}
|
|
|
|
value DashboardTimeline {
|
|
-- Bar chart of posts over time
|
|
-- Last 12 months that have data
|
|
-- Each bar: count label on top, month abbreviation + year below
|
|
-- Bar height proportional to max count
|
|
months: List<DashboardTimelineMonth>
|
|
}
|
|
|
|
value DashboardTimelineMonth {
|
|
label: String -- month abbreviation
|
|
year: Integer
|
|
count: Integer
|
|
}
|
|
|
|
value DashboardTagCloud {
|
|
-- Up to 40 tags, sorted alphabetically
|
|
-- Font size scaled 11px-22px based on post count
|
|
-- Tags with colours get coloured background with contrast text
|
|
-- Hover title shows post count
|
|
-- "and N more" text if > 40 tags
|
|
tags: List<DashboardTag>
|
|
overflow_count: Integer?
|
|
}
|
|
|
|
value DashboardTag {
|
|
name: String
|
|
count: Integer
|
|
color: String?
|
|
}
|
|
|
|
value DashboardCategoryCloud {
|
|
-- All categories as badge-like tags
|
|
-- Each shows category name + count
|
|
categories: List<DashboardCategory>
|
|
}
|
|
|
|
value DashboardCategory {
|
|
name: String
|
|
count: Integer
|
|
}
|
|
|
|
value DashboardRecentPost {
|
|
-- Last 5 posts by updatedAt descending
|
|
-- Each row: title (or "Untitled"), status badge (draft/published), date
|
|
-- Single-click: preview tab, double-click: pin tab
|
|
post_id: String
|
|
title: String
|
|
status: String
|
|
date: String
|
|
}
|
|
|
|
config {
|
|
dashboard_max_tags: Integer = 40
|
|
dashboard_tag_min_font: Integer = 11
|
|
dashboard_tag_max_font: Integer = 22
|
|
dashboard_recent_count: Integer = 5
|
|
dashboard_timeline_months: Integer = 12
|
|
}
|
|
|
|
-- ─── Menu editor view ────────────────────────────────────────
|
|
|
|
-- Visual editor for the OPML navigation menu (meta/menu.opml).
|
|
-- 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 ────────────────────────────────────
|
|
|
|
rule MenuAddItem {
|
|
when: MenuItemAdded(kind, data)
|
|
-- kind = page: opens lazy-loaded page picker (posts with "page" category)
|
|
-- kind = category_archive: opens lazy-loaded category picker
|
|
-- kind = submenu: creates empty container node for nesting children
|
|
-- kind = home: always available, maximum one allowed
|
|
ensures: MenuTreeUpdated()
|
|
}
|
|
|
|
rule MenuSave {
|
|
when: MenuSaveRequested()
|
|
-- Serializes tree to OPML 2.0, writes meta/menu.opml
|
|
ensures: MenuFileWritten()
|
|
}
|
|
|
|
rule MenuMoveItem {
|
|
when: MenuItemMoved(item_id, direction)
|
|
-- direction = up | down | indent | unindent
|
|
-- 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)
|
|
requires: not is_home_item(item_id)
|
|
-- Home item is protected: cannot be moved or deleted
|
|
ensures: MenuTreeUpdated()
|
|
}
|
|
|
|
rule MenuDeleteItem {
|
|
when: MenuItemDeleted(item_id)
|
|
requires: not is_home_item(item_id)
|
|
-- Removes item and all children, no confirmation dialog
|
|
ensures: MenuTreeUpdated()
|
|
}
|
|
|
|
-- 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
|
|
|
|
config {
|
|
menu_drag_expand_delay: Integer = 450
|
|
}
|
|
|
|
-- ─── Metadata diff view ──────────────────────────────────────
|
|
|
|
-- Shows DB vs filesystem differences for all entity types.
|
|
-- 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
|
|
|
|
-- ─── Git diff view ────────────────────────────────────────────
|
|
|
|
-- Renders diff for a file (working tree vs HEAD) or a commit.
|
|
-- File diff: id = "git-diff:{filePath}"
|
|
-- Commit diff: id = "git-diff:commit:{commitHash}"
|
|
-- Supports inline and side-by-side diff display modes (from editor settings).
|
|
-- No actions beyond viewing — changes are managed via git sidebar.
|
|
|
|
-- ─── Documentation views ─────────────────────────────────────
|
|
|
|
-- documentation: renders DOCUMENTATION.md as styled HTML
|
|
-- api_documentation: renders API.md as styled HTML
|
|
|
|
-- ─── Site validation view ───────────────────────────────────
|
|
|
|
-- Compares sitemap.xml to generated HTML files on disk.
|
|
-- Detects missing pages, orphan HTML, and stale content.
|
|
|
|
value SiteValidationReport {
|
|
expected_url_count: Integer
|
|
existing_html_count: Integer
|
|
missing_url_paths: List<String> -- in sitemap, no HTML on disk
|
|
extra_url_paths: List<String> -- HTML on disk, not in sitemap
|
|
updated_post_url_paths: List<String> -- source .md newer than HTML
|
|
}
|
|
|
|
-- Layout: scan button, summary line, three URL list sections.
|
|
-- Summary: "Expected URLs: N — Existing HTML URLs: N — Missing: N — Extra: N — Updated: N"
|
|
-- Each section: heading, list of URL paths, or "None found".
|
|
|
|
rule SiteValidationScan {
|
|
when: SiteValidationScanRequested()
|
|
-- Parses <loc> entries from sitemap.xml into expected URL set
|
|
-- Scans HTML output dir for index.html files (zero-byte = missing)
|
|
-- Compares source .md mtime against generated HTML mtime
|
|
ensures: SiteValidationReport
|
|
}
|
|
|
|
rule SiteValidationApply {
|
|
when: SiteValidationApplyRequested(report)
|
|
-- Classifies affected paths into generation sections (core, single, category, tag, date)
|
|
-- Renders only affected sections in parallel
|
|
-- Deletes extra HTML files, removes empty directories
|
|
-- Regenerates calendar if anything changed
|
|
-- Rebuilds search index if anything rendered or deleted
|
|
-- Toast: "Validation applied: N rendered, N deleted"
|
|
ensures: GenerateSiteRequested(sections: report.affected_sections)
|
|
}
|
|
|
|
-- ─── Translation validation view ───────────────────────────
|
|
|
|
-- Checks translation integrity across DB rows and filesystem files.
|
|
-- NOT a matrix view — it is a list of issues found.
|
|
|
|
value TranslationValidationReport {
|
|
checked_database_row_count: Integer
|
|
checked_filesystem_file_count: Integer
|
|
invalid_database_rows: List<TranslationValidationIssue>
|
|
invalid_filesystem_files: List<TranslationValidationIssue>
|
|
}
|
|
|
|
value TranslationValidationIssue {
|
|
issue: String
|
|
-- Issue kinds:
|
|
-- missing_source_post: translationFor points to nonexistent post
|
|
-- same_language_as_canonical: translation language matches source post language
|
|
-- do_not_translate_has_translations: source post is doNotTranslate but has translations
|
|
-- content_in_database: published translation still has content in DB (should be on disk)
|
|
translation_id: String?
|
|
translation_for: String
|
|
canonical_language: String?
|
|
translation_language: String
|
|
title: String?
|
|
file_path: String?
|
|
}
|
|
|
|
-- Layout: Revalidate + Fix Issues buttons, summary line, two issue sections.
|
|
-- Summary: "Checked DB rows: N — Checked files: N — Invalid DB rows: N — Invalid files: N"
|
|
-- Each issue rendered as card with coloured left border.
|
|
-- Card shows: issue label, source post ID, translation ID, title, languages, file path.
|
|
|
|
rule TranslationValidationScan {
|
|
when: TranslationValidationScanRequested()
|
|
-- Database pass: checks all translation rows for integrity issues
|
|
-- Filesystem pass: scans posts/ for translation .md files, checks frontmatter
|
|
ensures: TranslationValidationReport
|
|
}
|
|
|
|
rule TranslationValidationFix {
|
|
when: TranslationValidationFixRequested(report)
|
|
-- content_in_database: flushes content to .md file, sets content = null in DB
|
|
-- missing_source_post | same_language_as_canonical | do_not_translate:
|
|
-- DB issues: deletes translation row
|
|
-- Filesystem issues: deletes the .md file
|
|
-- After fix: automatically re-validates
|
|
-- Toast: "Deleted N DB rows and N files, flushed N translations to disk"
|
|
ensures: TranslationValidationScan
|
|
}
|
|
|
|
-- ─── Find duplicates view ──────────────────────────────────
|
|
|
|
-- Uses embedding vectors to find semantically similar posts.
|
|
-- Gate: requires semanticSimilarityEnabled in project metadata.
|
|
-- If disabled: shows "Semantic similarity is not enabled" message.
|
|
|
|
value DuplicateSearchResult {
|
|
pairs: List<DuplicatePair>
|
|
}
|
|
|
|
value DuplicatePair {
|
|
post_id_a: String
|
|
title_a: String
|
|
post_id_b: String
|
|
title_b: String
|
|
similarity: Decimal -- 0.0 to 1.0
|
|
exact_match: Boolean -- true if titles + content identical
|
|
}
|
|
|
|
-- Layout: header, count, paginated pair list (500 per page).
|
|
-- Each pair row: checkbox, post A title (clickable), arrow, post B title (clickable),
|
|
-- similarity badge ("Exact duplicate" or "N% similar"), Dismiss button.
|
|
-- Exact matches styled distinctly. Batch controls: Check All, Uncheck All,
|
|
-- Dismiss Checked (N). Refresh button.
|
|
|
|
rule DuplicateSearch {
|
|
when: DuplicateSearchRequested()
|
|
requires: semantic_similarity_enabled
|
|
-- Loads USearch vector index for project
|
|
-- For each indexed post: search 21 nearest neighbors
|
|
-- similarity = max(0, 1 - distance)
|
|
-- Filter: similarity >= threshold, exclude dismissed pairs
|
|
-- For 100% embedding similarity: load post bodies, compare title+content
|
|
-- If identical: exact_match = true
|
|
-- Sort: exact matches first, then descending similarity
|
|
ensures: DuplicateSearchResult
|
|
}
|
|
|
|
rule DuplicateDismiss {
|
|
when: DuplicatePairDismissed(post_id_a, post_id_b)
|
|
-- Inserts into dismissed_duplicate_pairs with canonical ID ordering
|
|
-- Excluded from future searches
|
|
-- See schema.allium DismissedDuplicatePair
|
|
ensures: PairDismissed(post_id_a, post_id_b)
|
|
}
|
|
|
|
rule DuplicateBatchDismiss {
|
|
when: DuplicatePairsBatchDismissed(pair_ids)
|
|
-- Batch insert in chunks of 100
|
|
ensures: for pair in pair_ids: PairDismissed(pair)
|
|
}
|
|
|
|
config {
|
|
duplicate_similarity_threshold: Decimal = 0.92
|
|
duplicate_page_size: Integer = 500
|
|
duplicate_neighbor_count: Integer = 21
|
|
}
|
|
|
|
-- ─── 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)
|
|
}
|