204 lines
8.3 KiB
Plaintext
204 lines
8.3 KiB
Plaintext
-- allium: 1
|
|
-- bDS UI Data Flow Model
|
|
-- Scope: cross-cutting (all waves)
|
|
-- Distilled from: appStore.ts, PostEditor.tsx, MediaEditor.tsx, Editor.tsx,
|
|
-- Sidebar.tsx, NotificationWatcher.ts
|
|
|
|
-- Describes the reactive data flows that connect sidebar, editor, tab bar,
|
|
-- and backend engine operations. Covers: sidebar->editor, editor->sidebar,
|
|
-- cross-tab coordination, and backend->UI event propagation.
|
|
|
|
use "./layout.allium" as layout
|
|
use "./tabs.allium" as tabs
|
|
use "./sidebar_views.allium" as sidebar
|
|
|
|
-- ─── External surfaces ──────────────────────────────────────
|
|
|
|
-- User-initiated navigation and entity management actions.
|
|
-- These triggers originate from sidebar clicks, tab operations,
|
|
-- dashboard interactions, and save/delete actions in editors.
|
|
|
|
surface UserNavigation {
|
|
provides: SidebarItemClicked(entity_type, entity_id, click_type)
|
|
provides: SidebarCreateRequested(entity_type)
|
|
provides: SidebarDeleteRequested(entity_type, entity_id)
|
|
provides: DashboardPostClicked(post_id, click_type)
|
|
provides: PostSaved(post_id, updated_post)
|
|
provides: PostStatusTransitioned(post_id, old_status, new_status)
|
|
provides: PostDeletedFromEditor(post_id)
|
|
provides: MediaSavedFromEditor(media_id, updated_media)
|
|
provides: MediaDeletedFromEditor(media_id)
|
|
provides: SettingsRebuildCompleted(entity_type, new_data)
|
|
provides: TransientTabBeingReplaced(old_tab, new_tab)
|
|
provides: TabClosed(tab)
|
|
}
|
|
|
|
-- ─── 1. Sidebar -> Editor flows ──────────────────────────────
|
|
|
|
-- All UI coordination flows through shared application state.
|
|
-- There are no direct calls between sidebar and editor.
|
|
-- Both read from and write to the same state model; changes propagate
|
|
-- reactively.
|
|
|
|
rule SidebarEntityClick {
|
|
when: SidebarItemClicked(entity_type, entity_id, click_type)
|
|
-- Single click: open as transient/preview tab
|
|
-- Double click: open as pinned tab
|
|
-- See sidebar_views.allium for per-view click rules
|
|
-- Effect: Editor mounts the matching view keyed by entity_id
|
|
-- Effect: Sidebar item highlights based on activeTabId match
|
|
-- If a prior transient tab of same type exists, it is replaced,
|
|
-- and the old editor unmounts (triggering auto-save if dirty)
|
|
}
|
|
|
|
rule SidebarCreateEntity {
|
|
when: SidebarCreateRequested(entity_type)
|
|
-- Posts/Pages: backend creates post, emits post:created,
|
|
-- store adds to posts list, sidebar re-renders with new item.
|
|
-- Does NOT open a tab automatically. User must click to open.
|
|
-- Sets selectedPostId for highlighting, optionally ensures sidebar visible.
|
|
-- Scripts/Templates: backend creates, emits event, opens tab immediately.
|
|
-- Chat: creates conversation, opens as pinned tab.
|
|
-- Import: creates definition, opens as pinned tab.
|
|
}
|
|
|
|
rule SidebarDeleteEntity {
|
|
when: SidebarDeleteRequested(entity_type, entity_id)
|
|
-- Scripts/Templates/Chat/Import: backend deletes entity,
|
|
-- then closeTab(entity_id) removes its tab,
|
|
-- activates adjacent tab or shows dashboard.
|
|
-- Posts/Media: deletion is triggered from the EDITOR (not sidebar).
|
|
-- See editor_post.allium PostDeleteAction / editor_media.allium MediaDeleteAction.
|
|
}
|
|
|
|
invariant SidebarFilterIsolation {
|
|
-- Sidebar search/filter state is local to the sidebar component.
|
|
-- Filtering never affects: active tab, editor content, selectedPostId.
|
|
-- Only the visible list of items changes.
|
|
}
|
|
|
|
-- ─── 2. Editor -> Sidebar flows ──────────────────────────────
|
|
|
|
rule PostTitleChanged {
|
|
when: PostSaved(post_id, updated_post)
|
|
-- Auto-save fires after 3s idle, or on Ctrl+S, or on unmount/tab switch
|
|
-- Store's posts array is updated with new title/metadata
|
|
-- Sidebar PostsList re-renders reactively showing new title
|
|
-- TabBar re-renders showing new title
|
|
ensures: dirtyPosts.remove(post_id)
|
|
}
|
|
|
|
rule PostStatusChanged {
|
|
when: PostStatusTransitioned(post_id, old_status, new_status)
|
|
-- Store's posts array is updated with new status
|
|
-- Sidebar detects status change (compares prev/current status maps)
|
|
-- and re-runs search/filter if active
|
|
-- Post moves between draft/published/archived sections in sidebar
|
|
}
|
|
|
|
rule PostEditorDelete {
|
|
when: PostDeletedFromEditor(post_id)
|
|
ensures: store.removePost(post_id)
|
|
-- Removes from posts array, clears selectedPostId if matching,
|
|
-- removes from dirtyPosts
|
|
ensures: closeTab(post_id)
|
|
-- Sidebar re-renders without the post
|
|
}
|
|
|
|
rule MediaEditorSave {
|
|
when: MediaSavedFromEditor(media_id, updated_media)
|
|
ensures: store.updateMedia(media_id, updated_media)
|
|
-- Sidebar MediaList re-renders with updated metadata
|
|
}
|
|
|
|
rule MediaEditorDelete {
|
|
when: MediaDeletedFromEditor(media_id)
|
|
ensures: store.removeMedia(media_id)
|
|
-- Editor.tsx safety net: detects active media tab references
|
|
-- non-existent item, calls closeTab(activeTab.id)
|
|
-- Sidebar re-renders without the media item
|
|
}
|
|
|
|
rule SettingsRebuild {
|
|
when: SettingsRebuildCompleted(entity_type, new_data)
|
|
-- Wholesale replacement of posts or media array in store
|
|
ensures: store.setPosts(new_data) or store.setMedia(new_data)
|
|
-- Sidebar re-renders entirely with fresh data
|
|
}
|
|
|
|
-- ─── 3. Cross-tab coordination ──────────────────────────────
|
|
|
|
invariant TabSwitchDoesNotChangeSidebarView {
|
|
-- Switching tabs does NOT change activeView in the sidebar.
|
|
-- The sidebar view is controlled exclusively by the Activity Bar.
|
|
-- Exception: Dashboard post click explicitly sets activeView = "posts".
|
|
}
|
|
|
|
invariant SidebarHighlightFollowsActiveTab {
|
|
-- Sidebar item highlight is based on activeTabId === entity.id,
|
|
-- NOT on selectedPostId/selectedMediaId (which are separate concepts
|
|
-- used only for post creation flow).
|
|
}
|
|
|
|
rule TransientTabReplacement {
|
|
when: TransientTabBeingReplaced(old_tab, new_tab)
|
|
-- Old editor unmounts -> cleanup auto-save fires if dirty
|
|
-- New editor mounts with new entity
|
|
-- Sidebar highlight shifts to newly active entity
|
|
}
|
|
|
|
rule TabCloseCleanup {
|
|
when: TabClosed(tab)
|
|
-- If post tab: PostEditor unmounts, auto-save fires if dirty
|
|
-- Store activates adjacent tab (prefer right, then left, then null)
|
|
-- Sidebar highlight updates to new active tab
|
|
-- If no tabs remain: dashboard shown
|
|
}
|
|
|
|
rule DashboardPostClick {
|
|
when: DashboardPostClicked(post_id, click_type)
|
|
-- This is the ONLY place where opening a tab also switches sidebar view
|
|
ensures: setActiveView("posts")
|
|
ensures: setSelectedPost(post_id)
|
|
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)
|
|
}
|
|
|
|
-- ─── 4. Backend -> UI event propagation ─────────────────────
|
|
|
|
-- Backend engines emit events via IPC. The renderer listens and updates
|
|
-- the shared store. Both sidebar and editor re-render reactively.
|
|
-- Events: post:created, post:updated, post:deleted,
|
|
-- media:imported, media:updated, media:deleted,
|
|
-- template:created/updated/deleted, script:created/updated/deleted,
|
|
-- entity:changed (from CLI/MCP mutations via NotificationWatcher)
|
|
|
|
-- TabBar also listens directly for:
|
|
-- post-updated (title refresh)
|
|
-- bds:scripts-changed (script title refresh)
|
|
-- BDS_EVENT_TEMPLATES_CHANGED (template title refresh)
|
|
-- chat.onTitleUpdated (chat conversation title refresh)
|
|
-- importDefinitions.onNameUpdated (import name refresh)
|
|
|
|
-- Editor.tsx has safety-net useEffect guards that:
|
|
-- Close media tabs when referenced media no longer exists in store
|
|
-- Clear selectedPostId/selectedMediaId when entity is gone
|
|
|
|
-- ─── 5. Keyboard shortcut map ─────────────────────────────
|
|
|
|
-- All shortcuts use Cmd on macOS, Ctrl on other platforms.
|
|
|
|
-- Global:
|
|
-- Ctrl/Cmd+B: toggle sidebar visibility
|
|
-- Ctrl/Cmd+W: close active tab
|
|
|
|
-- Post editor:
|
|
-- Ctrl/Cmd+S: save post immediately (resets auto-save timer)
|
|
-- Ctrl/Cmd+K: open InsertModal (insert post link)
|
|
|
|
-- Sidebar lists:
|
|
-- Enter: open selected item as pinned tab
|
|
-- Space: open selected item as preview/transient tab
|