-- 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 bottom_group: List } 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)