228 lines
10 KiB
Plaintext
228 lines
10 KiB
Plaintext
-- allium: 1
|
|
-- Terminal UI (issue #26, phase 4)
|
|
-- Scope: extension (second renderer over the shared UI core)
|
|
-- Distilled from: lib/bds/tui.ex, lib/bds/ui/sidebar.ex, lib/bds/ui/post_editor/*.ex
|
|
|
|
entity TuiState {
|
|
view: posts | media | templates | scripts | tags | settings | git
|
|
focus: sidebar | editor
|
|
selected_index: Integer
|
|
editing_post: Boolean
|
|
-- The editor drives the same BDS.UI.PostEditor workflow as the GUI:
|
|
-- canonical-language edits update the post, other languages update
|
|
-- translations; publish routes through the same publishing pipeline.
|
|
}
|
|
|
|
surface TuiSurface {
|
|
facing _: TuiRuntime
|
|
|
|
provides:
|
|
TuiKeyPressed(code, modifiers)
|
|
TuiEventReceived(entity, entity_id, action)
|
|
TuiSettingsChanged(key)
|
|
ShellTaskCompleted(route)
|
|
}
|
|
|
|
rule SidebarNavigation {
|
|
when: TuiKeyPressed(code, _)
|
|
requires: code = "up" or code = "down" or code = "j" or code = "k"
|
|
-- Selection moves over the flattened sidebar items and skips
|
|
-- section headers; data comes from BDS.UI.Sidebar.view, identical
|
|
-- to the GUI sidebar content.
|
|
ensures: TuiState.selected_index.updated()
|
|
}
|
|
|
|
rule OpenEntry {
|
|
when: TuiKeyPressed(code, _)
|
|
requires: code = "enter"
|
|
-- Posts open in the editor by default (title + textarea seeded from
|
|
-- the persisted form; syntax highlighting and word wrap on; ctrl+e
|
|
-- switches to the rendered preview). New empty posts created with
|
|
-- "n" open in the editor as well. Images open in the terminal image
|
|
-- preview. Other entities report that terminal editing is not
|
|
-- available yet.
|
|
ensures: TuiState.editing_post.updated()
|
|
}
|
|
|
|
rule SaveAndPublish {
|
|
when: TuiKeyPressed(code, modifiers)
|
|
requires: code = "s" or code = "p"
|
|
requires: modifiers = "ctrl"
|
|
-- ctrl+s saves the draft, ctrl+p saves and publishes — both through
|
|
-- BDS.UI.PostEditor.Persistence, so files, embeddings, search, and
|
|
-- the event bus behave exactly as a GUI save.
|
|
ensures: PostPersisted()
|
|
}
|
|
|
|
rule EditorMode {
|
|
when: TuiKeyPressed(code, modifiers)
|
|
requires: code = "e"
|
|
requires: modifiers = "ctrl"
|
|
-- ctrl+e toggles between the syntax-highlighted editor (the default
|
|
-- mode when opening a post) and the read-only rendered-Markdown
|
|
-- preview (rendered through tui-markdown so headings/emphasis/lists
|
|
-- are styled); keys in preview never edit content, and esc returns
|
|
-- to the sidebar from either mode.
|
|
ensures: PreviewToggled()
|
|
}
|
|
|
|
rule CodeEditorWrapping {
|
|
when: TuiState.editing_post.updated()
|
|
-- The post editor renders the buffer with syntax highlighting
|
|
-- (markdown with [[macro]] accents for posts, HTML+Liquid for
|
|
-- templates, Lua for scripts), soft-wraps long lines to the
|
|
-- viewport width, keeps the cursor's visual row visible while
|
|
-- scrolling, and highlights the cursor's source line. There is no
|
|
-- line-number gutter.
|
|
ensures: TuiState.updated()
|
|
}
|
|
|
|
rule AiQuickAction {
|
|
when: TuiKeyPressed(code, modifiers)
|
|
requires: code = "g"
|
|
requires: modifiers = "ctrl"
|
|
-- One-shot AI post analysis (title/excerpt suggestions). Gated by
|
|
-- airplane mode: without a local endpoint the TUI shows a status
|
|
-- message instead of calling out.
|
|
ensures: AiSuggestionsRequestedOrRefused()
|
|
}
|
|
|
|
rule ProjectSwitcher {
|
|
when: TuiKeyPressed(code, _)
|
|
requires: code = "p"
|
|
-- "p" opens the projects overlay: all projects from the caching
|
|
-- database with the active one marked; enter activates the selected
|
|
-- project (BDS.Projects.set_active_project) and reloads the sidebar.
|
|
-- "o" switches to a folder-path prompt with bash-style tab
|
|
-- completion: tab completes a unique directory (trailing slash
|
|
-- appended), an ambiguous prefix completes to the longest common
|
|
-- prefix and lists the candidate folders; only directories are
|
|
-- offered, dotfolders only for a dot-prefixed input, and a leading
|
|
-- "~" expands to the home directory. A typed path is validated
|
|
-- to be an existing directory, then opened as a project
|
|
-- (BDS.Projects.create_project with data_path, named after the
|
|
-- folder) and activated; a full database rebuild is queued
|
|
-- automatically through the rebuild_database shell command so the
|
|
-- folder's content is loaded into the caching database. Invalid
|
|
-- paths keep the prompt open and report the error.
|
|
ensures: ProjectSwitchedOrOpened()
|
|
}
|
|
|
|
rule SidebarSearch {
|
|
when: TuiKeyPressed(code, _)
|
|
requires: code = "/"
|
|
-- "/" in sidebar focus opens a vi-style search prompt in the status
|
|
-- line that live-filters the current view through the same
|
|
-- BDS.UI.Sidebar filter params the GUI sidebar uses (posts, pages,
|
|
-- media). Plain words are a text search, "tag:x" and "category:x"
|
|
-- filter like the GUI chips, and an ISO date (yyyy-mm-dd) narrows
|
|
-- to that day — the day filter extends the shared sidebar queries.
|
|
-- Tokens combine with AND. Enter keeps the filter (shown appended
|
|
-- to the sidebar title), esc clears it; filters are per view.
|
|
ensures: SidebarFiltered()
|
|
}
|
|
|
|
rule CommandPrompt {
|
|
when: TuiKeyPressed(code, _)
|
|
requires: code = ":"
|
|
-- Vi-style prompt: ":" opens a filterable list of the parameterless
|
|
-- Blog-menu shell commands (metadata diff, validate site, force
|
|
-- render, rebuilds, reindex, translations, duplicates, upload,
|
|
-- browser preview URL) and runs the selection through the same
|
|
-- BDS.Desktop.ShellCommands backend as the GUI menu; typing ":?"
|
|
-- shows the full list as help. Overlays clear the cells beneath
|
|
-- them. Commands whose follow-up UI is GUI-only (validate
|
|
-- translations, find duplicates) are marked. Queued tasks report
|
|
-- progress in the status line until all tasks finish.
|
|
ensures: CommandListShownOrExecuted()
|
|
}
|
|
|
|
rule SettingsPanel {
|
|
when: TuiKeyPressed(code, _)
|
|
requires: code = "6"
|
|
-- "6" opens the settings panel (issue #29), matching the GUI panel
|
|
-- numbering: the sidebar lists the same preference sections as the
|
|
-- settings backends (Project, Editor, Content, AI, Technology,
|
|
-- Publishing, Data, MCP). The GUI-only Style tab is intentionally
|
|
-- absent. Enter on a section opens a section-specific editor in the
|
|
-- main area: a generic typed-field form from BDS.UI.SettingsForm.
|
|
-- Enter edits a text field in a status-line prompt, toggles a
|
|
-- boolean, or cycles an enum through its options; read-only info
|
|
-- rows are skipped by the selection. ctrl+s saves the section
|
|
-- through the same backends as the GUI editor (BDS.Metadata,
|
|
-- BDS.Settings, BDS.AI, BDS.MCP.AgentConfig) and reloads the form;
|
|
-- esc cancels the prompt, then closes the form. Category removal
|
|
-- and AI model discovery remain GUI-only.
|
|
ensures: SettingsFormShownEditedOrSaved()
|
|
}
|
|
|
|
rule TagsPanel {
|
|
when: TuiKeyPressed(code, _)
|
|
requires: code = "5"
|
|
-- "5" opens the tags view (issue #34), matching the GUI panel
|
|
-- numbering. The sidebar lists the same three sections as the GUI
|
|
-- tags editor (Tag Cloud, Create / Edit, Merge Tags — from
|
|
-- BDS.UI.Sidebar's tags nav view); enter opens the section in the
|
|
-- main area, all backed by BDS.UI.TagsPanel over the same BDS.Tags
|
|
-- operations as the GUI editor. Cloud lists tags with their post
|
|
-- usage counts ordered by usage; manage is alphabetical with "n"
|
|
-- (create prompt), enter (rename prompt), "c" (cycle colour through
|
|
-- the shared presets), "t" (cycle post template), "d" then "y"
|
|
-- (delete with post-count confirmation; any other key cancels) and
|
|
-- "s" (sync tags from post tags); merge marks tags with space and
|
|
-- "m" merges the marked tags into the selected one (at least two,
|
|
-- target included). Text prompts live in the status line; esc
|
|
-- cancels the prompt, then closes the panel. Domain tag events
|
|
-- reload an open panel, keeping it in sync with GUI and CLI writes.
|
|
ensures: TagsPanelShownOrEdited()
|
|
}
|
|
|
|
rule GitPanel {
|
|
when: TuiKeyPressed(code, _)
|
|
requires: code = "7"
|
|
-- "7" opens the git panel (issue #30) for content sync: the sidebar
|
|
-- lists the changed files from git status (code + path, branch with
|
|
-- ahead/behind counts in the title) with the commit history in its
|
|
-- lower half, like the GUI (short hash + subject; ↑ marks commits
|
|
-- that still need a push, ↓ remote-only ones), the main area shows a
|
|
-- scrollable whole-folder diff (staged + unstaged, capped) paged
|
|
-- with pgup/pgdn; enter on a file jumps the diff to it. "c" opens a
|
|
-- status-line commit prompt (git add -A + commit, empty messages
|
|
-- rejected), "u" pulls (ff-only) and "s" pushes — both run off the
|
|
-- UI process and report back as a status toast. Every action is
|
|
-- BDS.Git, the same backend as the GUI git panel. A project folder
|
|
-- that is not a git repository shows a message and refuses the
|
|
-- actions.
|
|
ensures: GitPanelShownOrSynced()
|
|
}
|
|
|
|
rule ReportPanels {
|
|
when: ShellTaskCompleted(route)
|
|
requires: route = "metadata_diff" or route = "site_validation"
|
|
-- Completed metadata diff and site validation tasks open a
|
|
-- scrollable report panel showing the differences. Enter applies
|
|
-- the whole report — metadata diff repairs all items from the
|
|
-- filesystem (file → db) and imports orphan files; site validation
|
|
-- applies the full report incrementally (render missing, remove
|
|
-- extra, re-render updated), matching the GUI defaults. Esc closes
|
|
-- the report without applying. Reports completed before this
|
|
-- session started never pop up.
|
|
ensures: ReportShownThenAppliedOrCancelled()
|
|
}
|
|
|
|
rule MultiClientSync {
|
|
when: TuiEventReceived(entity, entity_id, action)
|
|
-- Domain events refresh the sidebar; a post deleted elsewhere closes
|
|
-- the open editor. Keeps TUI sessions synchronized with GUI clients
|
|
-- and pipeline ingestion.
|
|
ensures: TuiState.updated()
|
|
}
|
|
|
|
rule ServerSideLocale {
|
|
when: TuiSettingsChanged(key)
|
|
requires: key = "ui.language"
|
|
-- The TUI re-reads the server-side UI language and re-renders; all
|
|
-- clients always share one language.
|
|
ensures: TuiRelocalized()
|
|
}
|