Files
bDS2/specs/tui.allium

156 lines
6.7 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
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: "up" | "down" | "j" | "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: "enter")
-- Posts open in the editor (title + textarea seeded from the
-- persisted form). 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: "s" | "p", 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 WrappedPreview {
when: TuiKeyPressed(code: "e", modifiers: "ctrl")
-- The editing textarea cannot soft-wrap (upstream ratatui-textarea
-- limitation), so ctrl+e toggles a read-only word-wrapped Markdown
-- preview of the current draft; keys in preview never edit content.
ensures: PreviewToggled()
}
rule AiQuickAction {
when: TuiKeyPressed(code: "g", 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: "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: "/")
-- "/" 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: ":")
-- 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 GitPanel {
when: TuiKeyPressed(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), 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: "metadata_diff" | "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: "ui.language")
-- The TUI re-reads the server-side UI language and re-renders; all
-- clients always share one language.
ensures: TuiRelocalized()
}