Full-Screen Mode
The assistant supports three view modes: Closed, Sidebar (400px panel), and Full-Screen (viewport overlay). Users switch between them with header buttons or keyboard shortcuts.
View Modes
Section titled “View Modes”The AssistantViewMode enum drives all rendering decisions:
#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum AssistantViewMode { Closed, Sidebar, FullScreen,}
impl AssistantViewMode { pub fn toggle_to(mut signal: Signal<Self>, target: Self) { let current = *signal.peek(); signal.set(if current == target { Self::Closed } else { target }); }}toggle_to acts as a toggle — pressing the same shortcut twice returns to Closed.
Keyboard Shortcuts
Section titled “Keyboard Shortcuts”| Shortcut | Action |
|---|---|
Cmd/Ctrl+J | Toggle sidebar open/closed |
Cmd/Ctrl+Shift+J | Toggle full-screen open/closed |
Escape | Minimize full-screen → sidebar |
Shortcuts are registered via JS interop in AssistantProvider and communicate back to Rust through dioxus.send():
fn use_keyboard_shortcuts(mut view_mode: Signal<AssistantViewMode>) { use_hook(move || { spawn(async move { let mut handle = document::eval(r#" document.addEventListener('keydown', (e) => { const meta = e.metaKey || e.ctrlKey; if (meta && e.shiftKey && (e.key === 'j' || e.key === 'J')) { e.preventDefault(); dioxus.send('fullscreen'); } else if (meta && (e.key === 'j' || e.key === 'J')) { e.preventDefault(); dioxus.send('sidebar'); } else if (e.key === 'Escape') { dioxus.send('escape'); } }); "#); loop { match handle.recv::<String>().await { Ok("sidebar") => AssistantViewMode::toggle_to(view_mode, AssistantViewMode::Sidebar), Ok("fullscreen") => AssistantViewMode::toggle_to(view_mode, AssistantViewMode::FullScreen), Ok("escape") => { if *view_mode.peek() == AssistantViewMode::FullScreen { view_mode.set(AssistantViewMode::Sidebar); } } _ => {} } } }); });}Architecture
Section titled “Architecture”The assistant uses a provider + shared body pattern. AssistantProvider owns all state and the WebSocket connection. Both the sidebar panel and full-screen overlay render the same AssistantChatBody component — only the wrapper differs.
AppLayout└── AssistantProvider ← owns state, WebSocket, shortcuts ├── AppLayoutWithAssistant │ ├── Sidebar + Header + main │ ├── AssistantPanel ← 400px sliding sidebar │ │ └── AssistantChatBody │ └── AssistantFullScreen ← viewport overlay (conditional) │ └── AssistantChatBodyAssistantContext
Section titled “AssistantContext”The provider creates an AssistantContext shared via Dioxus context:
#[derive(Clone, Copy)]pub struct AssistantContext { pub view_mode: Signal<AssistantViewMode>, pub messages: Signal<Vec<DisplayMessage>>, pub input_value: Signal<String>, pub is_loading: Signal<bool>, pub current_title: Signal<Option<String>>, pub current_conversation_id: Signal<Option<uuid::Uuid>>, pub conversations: Signal<Vec<ConversationSummary>>, pub show_session_picker: Signal<bool>, pub page_ctx: Signal<PageContext>, pub quick_actions: Signal<Option<Vec<QuickAction>>>,}Messages, input text, and conversation state persist when switching between sidebar and full-screen.
Layout Details
Section titled “Layout Details”Sidebar Panel
Section titled “Sidebar Panel”The sidebar uses a width transition to slide in and out:
div { class: if props.is_open { "w-[400px] transition-all duration-300 overflow-hidden z-20" } else { "w-0 transition-all duration-300 overflow-hidden" }, div { class: "min-w-[400px] h-full bg-background border-l border-border flex flex-col", AssistantChatBody { on_expand: move |_| view_mode.set(AssistantViewMode::FullScreen), on_close: move |_| view_mode.set(AssistantViewMode::Closed), } }}The outer container transitions between w-[400px] and w-0. The inner container keeps a fixed min-w-[400px] so content doesn’t reflow during animation.
Full-Screen Overlay
Section titled “Full-Screen Overlay”The overlay renders a centered card over a blurred backdrop:
div { class: "fixed inset-0 z-50 flex items-center justify-center \ bg-background/80 backdrop-blur-sm", onclick: move |_| view_mode.set(AssistantViewMode::Sidebar),
div { class: "relative w-full max-w-3xl mx-4 \ h-[calc(100vh-6rem)] rounded-lg border \ border-border bg-card shadow-floating flex flex-col", onclick: move |e: MouseEvent| e.stop_propagation(),
AssistantChatBody { on_minimize: move |_| view_mode.set(AssistantViewMode::Sidebar), } }}Click the backdrop or press Escape to minimize back to the sidebar. The card itself stops click propagation.
Link Navigation
Section titled “Link Navigation”Links in assistant messages are intercepted via event delegation. Internal links (/path) trigger SPA navigation through window.__assistantNav(), which minimizes full-screen to sidebar before navigating. External links open in a new tab.
// src/mods/assistant/helpers.rs — intercept_assistant_links()// Attaches a delegated click handler to all #assistant-messages containers// Internal: e.preventDefault() → __assistantNav(href)// External: sets target="_blank" + rel="noopener noreferrer"Header Buttons
Section titled “Header Buttons”AssistantHeader conditionally renders expand/minimize/close buttons based on which callbacks are passed:
| Button | Icon | Shown in | Action |
|---|---|---|---|
| Expand | Maximize2 | Sidebar | Switch to full-screen |
| Minimize | Minimize2 | Full-screen | Switch to sidebar |
| Close | X | Sidebar | Close assistant |
File Reference
Section titled “File Reference”| File | Purpose |
|---|---|
types/assistant_view_mode.rs | AssistantViewMode enum |
components/assistant_provider_component.rs | State provider, shortcuts, link handler |
components/assistant_chat_body_component.rs | Shared chat UI |
components/assistant_panel_component.rs | 400px sidebar wrapper |
components/assistant_full_screen_component.rs | Viewport overlay wrapper |
components/assistant_header_component.rs | Header with mode-switching buttons |
helpers.rs | Focus, scroll, link interception |
All paths relative to src/mods/assistant/.