292 lines
10 KiB
Plaintext
292 lines
10 KiB
Plaintext
-- allium: 1
|
|
-- bDS Application Layout
|
|
-- Scope: UI shell (all waves)
|
|
-- Distilled from: src/renderer/App.tsx, ActivityBar.tsx, StatusBar.tsx,
|
|
-- Panel.tsx, WindowTitleBar.tsx, ResizablePanel, Sidebar.tsx
|
|
|
|
-- The top-level visual structure of the application window.
|
|
-- Describes the shell (regions, toggle behaviour, resize constraints)
|
|
-- but NOT the content of each region (see tabs.allium, sidebar_views.allium).
|
|
|
|
use "./i18n.allium" as i18n
|
|
use "./task.allium" as task
|
|
|
|
surface LayoutControlSurface {
|
|
facing _: LayoutOperator
|
|
|
|
provides:
|
|
ToggleSidebarRequested()
|
|
TogglePanelRequested()
|
|
ToggleAssistantSidebarRequested()
|
|
ActivityClicked(activity_id)
|
|
}
|
|
|
|
surface LayoutRuntimeSurface {
|
|
facing _: LayoutRuntime
|
|
|
|
provides:
|
|
GitBadgePollTick(badge)
|
|
ClearGitBadgeTick(badge)
|
|
}
|
|
|
|
-- ─── Window shell ─────────────────────────────────────────────
|
|
|
|
-- +------------------------------------------------------------+
|
|
-- | WindowTitleBar |
|
|
-- +----+--------+----------------------------+-----------------+
|
|
-- | A | Side- | TabBar | Assistant |
|
|
-- | c | bar |----------------------------| Sidebar |
|
|
-- | t | (resz) | Editor (routed by tab) | |
|
|
-- | i | |----------------------------+ |
|
|
-- | v | | Panel (bottom) | |
|
|
-- | i | | | |
|
|
-- | t | | | |
|
|
-- | y | | | |
|
|
-- +----+--------+----------------------------+-----------------+
|
|
-- | StatusBar |
|
|
-- +------------------------------------------------------------+
|
|
|
|
value AppShell {
|
|
title_bar: WindowTitleBar
|
|
activity_bar: ActivityBar
|
|
sidebar: ResizableRegion
|
|
content_area: ContentArea
|
|
assistant_sidebar: ResizableRegion
|
|
status_bar: StatusBar
|
|
}
|
|
|
|
surface AppShellSurface {
|
|
context shell: AppShell
|
|
|
|
exposes:
|
|
shell.title_bar.title
|
|
shell.sidebar.visible
|
|
shell.sidebar.width
|
|
shell.content_area.panel.visible
|
|
shell.assistant_sidebar.visible
|
|
}
|
|
|
|
value ContentArea {
|
|
-- tab_bar: see tabs.allium
|
|
-- editor: routed by active tab; see tabs.allium
|
|
panel: Panel
|
|
}
|
|
|
|
-- ─── Resizable regions ────────────────────────────────────────
|
|
|
|
config {
|
|
sidebar_initial_width: Integer = 280
|
|
sidebar_min_width: Integer = 200
|
|
sidebar_max_width: Integer = 500
|
|
assistant_initial_width: Integer = 360
|
|
assistant_min_width: Integer = 280
|
|
assistant_max_width: Integer = 640
|
|
}
|
|
|
|
value ResizableRegion {
|
|
visible: Boolean
|
|
width: Integer
|
|
min_width: Integer
|
|
max_width: Integer
|
|
}
|
|
|
|
-- ─── Toggle state ─────────────────────────────────────────────
|
|
|
|
value ShellVisibility {
|
|
sidebar_visible: Boolean
|
|
panel_visible: Boolean
|
|
assistant_sidebar_visible: Boolean
|
|
}
|
|
|
|
surface ShellVisibilitySurface {
|
|
context visibility: ShellVisibility
|
|
|
|
exposes:
|
|
visibility.sidebar_visible
|
|
visibility.panel_visible
|
|
visibility.assistant_sidebar_visible
|
|
}
|
|
|
|
rule ToggleSidebar {
|
|
when: ToggleSidebarRequested()
|
|
ensures: sidebar_visible = not sidebar_visible
|
|
}
|
|
|
|
rule TogglePanel {
|
|
when: TogglePanelRequested()
|
|
ensures: panel_visible = not panel_visible
|
|
}
|
|
|
|
rule ToggleAssistantSidebar {
|
|
when: ToggleAssistantSidebarRequested()
|
|
ensures: assistant_sidebar_visible = not assistant_sidebar_visible
|
|
}
|
|
|
|
-- ─── Window title bar ─────────────────────────────────────────
|
|
|
|
value WindowTitleBar {
|
|
-- Platform-adaptive
|
|
-- menu_bar: rendered only on non-Mac platforms (6 groups: App, File, Edit, View, Window, Help)
|
|
-- macOS: native menu bar (same 6 groups)
|
|
-- Keyboard: Alt opens mnemonics, Alt+letter opens group
|
|
-- Arrow keys navigate groups and items, Enter/Space activates
|
|
-- View group hides devTools toggle when not in dev mode
|
|
title: String -- document.title, fallback "Blogging Desktop Server"
|
|
-- Three toggle buttons (all platforms): sidebar, panel, assistant
|
|
}
|
|
|
|
-- ─── Activity bar ─────────────────────────────────────────────
|
|
|
|
-- Narrow vertical icon strip at the far-left edge of the window.
|
|
-- Two groups: top (content views) and bottom (tools).
|
|
|
|
value ActivityBar {
|
|
top_group: List<ActivityButton>
|
|
bottom_group: List<ActivityButton>
|
|
}
|
|
|
|
value ActivityButton {
|
|
id: String -- matches SidebarView name
|
|
label_key: String -- i18n key for tooltip
|
|
badge: Badge? -- only git has a badge
|
|
active: Boolean -- highlighted when this view is showing
|
|
}
|
|
|
|
value Badge {
|
|
count: Integer
|
|
display: String -- count capped at "99+"
|
|
}
|
|
|
|
-- Exhaustive activity list with preserved order
|
|
-- Top group (content views):
|
|
-- 1. posts i18n:activity.posts
|
|
-- 2. pages i18n:activity.pages
|
|
-- 3. media i18n:activity.media
|
|
-- 4. scripts i18n:activity.scripts
|
|
-- 5. templates i18n:activity.templates
|
|
-- 6. tags i18n:activity.tags
|
|
-- 7. chat i18n:activity.aiAssistant
|
|
-- 8. import i18n:activity.import
|
|
-- Bottom group (tools):
|
|
-- 9. git i18n:activity.sourceControl (badge: pending pull count)
|
|
-- 10. settings i18n:common.settings
|
|
|
|
-- Each activity ID maps 1:1 to a SidebarView of the same name.
|
|
|
|
-- ─── Activity click behaviour ─────────────────────────────────
|
|
|
|
-- All activities share the same toggle-sidebar strategy.
|
|
-- The sidebar shows the view that matches the clicked activity.
|
|
|
|
rule ActivityClick {
|
|
when: ActivityClicked(activity_id)
|
|
let target_view = activity_id
|
|
if active_view = target_view:
|
|
ensures: ToggleSidebarRequested()
|
|
-- If already on this view, toggle sidebar open/closed
|
|
else:
|
|
ensures: active_view = target_view
|
|
if not sidebar_visible:
|
|
ensures: ToggleSidebarRequested()
|
|
-- Switch view; open sidebar if hidden
|
|
}
|
|
|
|
invariant ActivityActiveHighlight {
|
|
-- An activity button shows active state iff its view is the
|
|
-- current active_view AND the sidebar is visible
|
|
for btn in ActivityBar.all_buttons:
|
|
btn.active = (active_view = btn.id and sidebar_visible)
|
|
}
|
|
|
|
-- ─── Git badge ────────────────────────────────────────────────
|
|
|
|
-- Only the git activity button carries a badge.
|
|
-- Badge shows remote "behind" count, polled every 30 seconds.
|
|
|
|
config {
|
|
git_badge_poll_interval: Integer = 30
|
|
-- seconds between badge refresh polls
|
|
}
|
|
|
|
rule RefreshGitBadge {
|
|
when: GitBadgePollTick(badge)
|
|
requires: online and active_project != null
|
|
let repo_state = git.getRepoState()
|
|
requires: repo_state.is_repo and repo_state.has_remote
|
|
ensures: git.fetch()
|
|
let remote_state = git.getRemoteState()
|
|
ensures: badge.count = max(0, remote_state.behind)
|
|
}
|
|
|
|
rule ClearGitBadge {
|
|
when: ClearGitBadgeTick(badge)
|
|
requires: not online or active_project = null or not is_repo or not has_remote
|
|
ensures: badge.count = 0
|
|
}
|
|
|
|
-- ─── Bottom panel ─────────────────────────────────────────────
|
|
|
|
value Panel {
|
|
visible: Boolean
|
|
active_tab: String -- tasks | output | post_links | git_log
|
|
}
|
|
|
|
-- Panel tab availability depends on active editor tab
|
|
invariant PanelTabAvailability {
|
|
-- tasks: always available
|
|
-- output: always available
|
|
-- post_links: only when active editor tab is a post
|
|
-- git_log: only when active editor tab is a post or media
|
|
}
|
|
|
|
invariant PanelTabFallback {
|
|
-- If active panel tab becomes unavailable, fall back to tasks
|
|
-- post_links unavailable when no post tab is active
|
|
-- git_log unavailable when neither post nor media tab is active
|
|
}
|
|
|
|
-- Tasks tab: last 10 tasks, newest first, with progress/cancel.
|
|
-- Tasks with shared group_id are collapsible groups showing aggregate progress.
|
|
-- Output tab: log entries with copy-all button.
|
|
-- Post Links tab: backlinks (posts linking here) + outlinks (posts linked from here).
|
|
-- Each entry clickable, opens linked post as pinned tab.
|
|
-- Git Log tab: file-level git history for active post/media (up to 50 entries).
|
|
-- For posts: path = posts/YYYY/MM/{slug}.md
|
|
-- For media: path relative to project root
|
|
|
|
-- ─── Status bar ───────────────────────────────────────────────
|
|
|
|
value StatusBar {
|
|
left: StatusBarLeft
|
|
right: StatusBarRight
|
|
}
|
|
|
|
value StatusBarLeft {
|
|
-- Project selector dropdown to switch active project
|
|
running_task_message: String? -- spinner + message when tasks running
|
|
running_task_overflow: Integer? -- "+N more" count when multiple running
|
|
}
|
|
|
|
value StatusBarRight {
|
|
-- In display order (left to right):
|
|
post_status: String? -- draft|published|archived dot, when post tab active
|
|
post_count: String -- "{count} posts"
|
|
media_count: String -- "{count} media"
|
|
token_usage: TokenUsage? -- shown only when active tab is chat
|
|
theme_badge: String -- pico theme name
|
|
offline_mode: Boolean -- airplane icon toggle, keyboard accessible
|
|
ui_language: String -- dropdown: en, de, fr, it, es
|
|
brand: String -- "bDS"
|
|
}
|
|
|
|
value TokenUsage {
|
|
input_tokens: Integer
|
|
output_tokens: Integer
|
|
cache_read_tokens: Integer
|
|
}
|
|
|
|
-- ─── Keyboard shortcuts (global) ──────────────────────────────
|
|
|
|
-- Ctrl/Cmd+B: toggle sidebar
|
|
-- Ctrl/Cmd+W: close active tab (see tabs.allium)
|