-- allium: 1 -- bDS Sidebar Views -- Scope: UI content (all waves + extensions) -- Distilled from: Sidebar.tsx, PostsList.tsx, MediaList.tsx, -- SidebarEntityList.tsx, SettingsNav.tsx, TagsNav.tsx, -- ChatList.tsx, ImportList.tsx, ScriptsList.tsx, TemplatesList.tsx, -- GitSidebar.tsx, sidebarDateFormatting.ts -- Describes the content and behaviour of each of the 10 sidebar views. -- The sidebar shell (visibility, resize, view switching) is in layout.allium. -- Tab opening behaviour is in tabs.allium. use "./layout.allium" as layout use "./tabs.allium" as tabs use "./post.allium" as post use "./media.allium" as media use "./tag.allium" as tag use "./i18n.allium" as i18n -- ─── Sidebar view registry ─────────────────────────────────── -- 10 views: posts, pages, media, scripts, templates, settings, tags, chat, import, git -- Default view: posts -- The sidebar renders exactly one view at a time, selected by active_view. -- Each ActivityId maps 1:1 to a SidebarView of the same name. config { default_sidebar_view: String = "posts" } -- ─── Shared patterns ───────────────────────────────────────── -- SidebarEntityList: reusable pattern for scripts, templates, chat, import. -- Shows: header with title + create button, scrollable item list, -- empty state with message + action CTA. -- All titles and names are CSS-truncated with ellipsis when they overflow -- the sidebar width. Hard JS truncation limits are noted per view. -- Relative date formatting for entity list items (chat, scripts, templates, import): -- Same day (diffDays=0) -> time only via toLocaleTimeString (e.g. "2:30 PM") -- Yesterday (diffDays=1) -> i18n key sidebar.chat.yesterday -- <7 days -> short weekday via toLocaleDateString weekday:short (e.g. "Mon") -- >=7 days -> short month + day via toLocaleDateString month:short day:numeric (e.g. "Feb 10") -- Locale map: en->en-US, de->de-DE, fr->fr-FR, it->it-IT, es->es-ES -- Fallback locale: en-US -- Post/page date formatting (different from relative dates above): -- toLocaleDateString(locale, { month: 'short', day: 'numeric', year: 'numeric' }) -- Example: "Feb 10, 2026" -- ─── 1. Posts view ──────────────────────────────────────────── value PostsView { mode: String -- "posts" or "pages" search_query: String? -- FTS via posts.search(query) filter_panel_visible: Boolean -- collapsible, toggled by icon button calendar_filter: CalendarFilter? -- year/month archive tree tag_filter: List -- selected tags (multi-select chips with colours) category_filter: List -- selected categories (multi-select chips) draft_section: List published_section: List archived_section: List has_more: Boolean -- pagination, 500 per batch } -- Drafts section always shows all drafts regardless of filters. -- Published and archived sections respect active filters. -- "Clear All Filters" button resets search, date, tags, categories. -- Filters auto-refresh when any post's status changes. value PostListItem { post_id: String type_icon: String -- derived from categories (below) title: String -- post.title, fallback "Untitled"; CSS ellipsis on overflow language_count: Integer? -- badge shown when availableLanguages.count > 1 date: String -- locale-formatted (month day, year) active: Boolean -- highlighted when activeTabId = post.id } -- Per-item data shown (left to right): -- [type_icon] [title ...ellipsis] [lang badge] -- [date] -- Title is CSS-truncated (white-space:nowrap, text-overflow:ellipsis). -- Date source: drafts/archived use updatedAt; published use publishedAt ?? updatedAt. -- Post type icon derivation from categories (first match wins, case-insensitive): -- picture/photo/image -> camera icon -- aside/note/quick -> notepad icon -- link/bookmark -> link icon -- video -> film icon -- quote -> speech bubble icon -- default -> document icon rule PostListClick { when: PostListItemClicked(post_id, click_type) if click_type = single: ensures: OpenTabRequested(type: post, id: post_id, intent: preview) if click_type = double: ensures: OpenTabRequested(type: post, id: post_id, intent: pin) } -- ─── 2. Pages view ─────────────────────────────────────────── -- Identical to PostsView but: -- mode = "pages" -- Filters to posts with "page" category -- Create button auto-adds "page" category -- Category filter excludes "page" from chips but auto-merges into backend calls -- ─── 3. Media view ──────────────────────────────────────────── value MediaView { search_query: String? -- FTS via media.search(query) filter_panel_visible: Boolean calendar_filter: CalendarFilter? tag_filter: List -- tags only, no categories for media grid: List -- grid layout (not list) } value MediaGridItem { media_id: String has_thumbnail: Boolean -- image: bds-thumb://{id} protocol; non-image: file icon SVG name: String -- title truncated to 60 chars + "..." if over; fallback originalName (no truncation) file_size: String -- formatted (B / KB / MB) tooltip: String -- caption ?? originalName active: Boolean -- highlighted when activeTabId = media.id } -- Per-item data shown in grid cell: -- [thumbnail or file icon] -- [name ...CSS ellipsis] -- [file_size] -- Name: JS hard limit of 60 chars on title (substring + "..."), originalName not truncated. -- CSS also applies text-overflow:ellipsis as a safety net. config { media_title_max_length: Integer = 60 } rule MediaListClick { when: MediaGridItemClicked(media_id, click_type) if click_type = single: ensures: OpenTabRequested(type: media, id: media_id, intent: preview) if click_type = double: ensures: OpenTabRequested(type: media, id: media_id, intent: pin) } -- Import button: opens native file import dialog -- ─── 4. Scripts view ────────────────────────────────────────── -- Uses SidebarEntityList pattern. value ScriptsView { items: List } value ScriptListItem { script_id: String title: String -- CSS ellipsis on overflow date: String -- relative date format (see shared patterns) active: Boolean } -- Per-item data shown: -- [title ...CSS ellipsis] [delete x] -- [relative date] rule ScriptListClick { when: ScriptListItemClicked(script_id, click_type) if click_type = single: ensures: OpenTabRequested(type: scripts, id: script_id, intent: preview) if click_type = double: ensures: OpenTabRequested(type: scripts, id: script_id, intent: pin) } -- Keyboard: Enter = pin, Space = preview. -- Delete button: deletes script and closes its tab. -- Create defaults: kind=utility, content='print("new script")', entrypoint='render', enabled=true. -- Refreshes on scripts-changed event. -- ─── 5. Templates view ─────────────────────────────────────── -- Uses SidebarEntityList pattern. Same as scripts with: value TemplatesView { items: List } value TemplateListItem { template_id: String title: String -- CSS ellipsis on overflow date: String -- relative date format active: Boolean } -- Per-item data shown: same layout as ScriptListItem. rule TemplateListClick { when: TemplateListItemClicked(template_id, click_type) if click_type = single: ensures: OpenTabRequested(type: templates, id: template_id, intent: preview) if click_type = double: ensures: OpenTabRequested(type: templates, id: template_id, intent: pin) } -- Delete: if referenced by posts or tags, shows confirmation with counts before force-delete. -- Create defaults: kind=post, content='', enabled=true. -- Refreshes on templates-changed event. -- ─── 6. Settings view ──────────────────────────────────────── -- Navigation list that controls sections within the settings editor tab. -- 9 sections in order: -- 1. project (folder icon) -- 2. editor (notepad icon) -- 3. content (clipboard icon) -- 4. ai (robot icon) -- 5. technology (gear icon) -- 6. publishing (rocket icon) -- 7. data (database icon) -- 8. mcp (plug icon) -- 9. style (palette icon) -- special: opens style tab, not settings section value SettingsNav { active_section: String? -- persisted across sidebar switches } rule SettingsNavClick { when: SettingsNavEntryClicked(section) if section = style: ensures: OpenTabRequested(type: style, id: style, intent: pin) else: ensures: OpenTabRequested(type: settings, id: settings, intent: pin) ensures: ScrollToSection(section) ensures: active_section = section -- Active section is persisted across sidebar switches } -- ─── 7. Tags view ───────────────────────────────────────────── -- Navigation list that controls sections within the tags editor tab. -- 3 sections: -- 1. cloud (cloud icon) -- tag cloud visualization -- 2. manage (pencil icon) -- create/edit tags -- 3. merge (merge icon) -- merge duplicate tags value TagsNav { active_section: String? -- persisted } rule TagsNavClick { when: TagsNavEntryClicked(section) ensures: OpenTabRequested(type: tags, id: tags, intent: pin) ensures: ScrollToSection(section) ensures: active_section = section -- Active section is persisted across sidebar switches } -- ─── 8. Chat view ───────────────────────────────────────────── -- Uses SidebarEntityList pattern. value ChatView { api_ready: Boolean -- shows API key prompt if false items: List } value ChatListItem { conversation_id: String title: String -- live-updated via onTitleUpdated; CSS ellipsis on overflow date: String -- relative date format } -- Per-item data shown: -- [title ...CSS ellipsis] [delete x] -- [relative date] rule ChatListClick { when: ChatListItemClicked(conversation_id) ensures: OpenTabRequested(type: chat, id: conversation_id, intent: pin) -- Chat tabs are always pinned } -- Delete button: deletes conversation and closes its tab. -- ─── 9. Import view ────────────────────────────────────────── -- Uses SidebarEntityList pattern. value ImportView { items: List } value ImportListItem { definition_id: String name: String -- live-updated via onNameUpdated; CSS ellipsis on overflow date: String -- relative date format } rule ImportListClick { when: ImportListItemClicked(definition_id) ensures: OpenTabRequested(type: import, id: definition_id, intent: pin) -- Import tabs are always pinned } -- ─── 10. Git view ───────────────────────────────────────────── -- Full git interface, not the SidebarEntityList pattern. -- Three possible states: loading, not_a_repo, active_repo. -- State: not_a_repo -- Remote URL text input + "Initialize Git" button. -- Init progress with phase/percentage/detail, collapsible transcript. -- State: active_repo value GitActiveView { branch: String -- current branch name upstream: String? -- tracking info (local -> upstream) ahead: Integer behind: Integer status_files: List history_entries: List has_more_history: Boolean -- paginated, 20 per page } value GitStatusFile { path: String status: String -- modified, added, deleted, renamed, etc. } value GitHistoryEntry { short_hash: String -- 7 chars subject: String -- wraps (word-break), not truncated author: String date: String -- locale-formatted sync_status: String -- synced, local_only, remote_only (coloured dots) } -- Action buttons: fetch, pull, push, prune_lfs. All disabled while any action is loading. -- Changes section: file count, commit message input, commit button, file list. -- File paths in changes list: CSS ellipsis on overflow. -- Changes list polls every 2 seconds in background. -- Remote state refreshes every 30 seconds with auto-fetch. -- History entries: subject + shortHash + author + date; sync status with coloured legend dots. rule GitFileClick { when: GitStatusFileClicked(file_path, click_type) if click_type = single: ensures: OpenTabRequested(type: git_diff, id: "git-diff:" + file_path, intent: preview) if click_type = double: ensures: OpenTabRequested(type: git_diff, id: "git-diff:" + file_path, intent: pin) } rule GitHistoryClick { when: GitHistoryEntryClicked(commit_hash, click_type) if click_type = single: ensures: OpenTabRequested(type: git_diff, id: "git-diff:commit:" + commit_hash, intent: preview) if click_type = double: ensures: OpenTabRequested(type: git_diff, id: "git-diff:commit:" + commit_hash, intent: pin) } rule GitCommit { when: GitCommitRequested(message) ensures: git.commitAll(message) -- Also: all git_diff tabs are closed and git state is reloaded } -- ─── Calendar archive (shared widget) ───────────────────────── -- Collapsible year/month tree. -- Selecting a year loads all posts/media for that year. -- Selecting a month narrows to that month. value CalendarFilter { selected_year: Integer? selected_month: Integer? -- 1-12 } value CalendarYear { year: Integer months: List } value CalendarMonth { month: Integer -- 1-12 count: Integer -- number of items in this month }