# UI Layout Specification This document defines the layout structure, responsive behavior, and component constraints for Tatlock UI. It implements the "Rooms of the Estate" metaphor from [PHILOSOPHY.md](../PHILOSOPHY.md). ## Navigation Structure The UI uses a **tabbed room navigation** in the header rather than a traditional sidebar. Each "room" represents a distinct usage context, reducing cognitive clutter. | Room | Purpose | Chat Dock Default | |------|---------|-------------------| | **Front Hall** | Dashboard - estate overview, quick access, activity | Expanded | | **Control Room** | Infrastructure - containers, stacks, monitoring | Collapsed | | **Parlor** | Housekeeping - home automation, devices, scenes | Collapsed | | **Library** | Knowledge - docs, notes, bookmarks (future) | Collapsed | **Hidden for now:** - **Study** - Secretarial tasks (email, calendaring) - future implementation --- ## Layout Structure ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ HEADER BAR (56px, with logo bulge overlay) │ │ [◯Logo◯] [Room Icons] ─────────────────────────────────── [Profile Menu] │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────────────────────────┐ ┌───────────────────┐ │ │ │ NAV PANEL │ │ PRIMARY CONTENT │ │ CHAT DOCK │ │ │ │ │ │ │ │ │ │ │ │ Room-level │ │ ┌──────────┐ ┌──────────────┐ │ │ Tatlock AI │ │ │ │ navigation │ │ │ FILTER │ │ DATA GRID │ │ │ assistant │ │ │ │ (sections) │ │ │ PANEL │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Persistent │ │ │ │ 280px fixed │ │ │ Optional │ │ flex: 1 │ │ │ across rooms │ │ │ │ │ │ │ 280px │ │ │ │ │ │ │ │ └─────────────┘ │ └──────────┘ └──────────────┘ │ │ 280px-33vw │ │ │ │ │ │ │ │ │ │ flex: 1 │ │ flex: 0 │ │ │ └─────────────────────────────────┘ └───────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` --- ## Panel Taxonomy All panels share common styling patterns but serve different purposes. ### Panel Types | Panel | Position | Purpose | Width | Content Alignment | |-------|----------|---------|-------|-------------------| | **Header Bar** | Top | Room nav, profile | 56px height | Logo in bulge, rest at bottom | | **Nav Panel** | Left | Room-level section nav | 280px fixed | Header docked to bottom | | **Filter Panel** | Left (inside content) | Data filtering/search | 280px fixed | Header docked to bottom | | **Detail Panel** | Right (inside content) | Selected item details | 320-400px | Standard header | | **Chat Dock** | Right | AI assistant | 280px-33vw | Standard header | ### Panel Header Behavior All left-side panels (Nav Panel, Filter Panel) dock their header content to the **bottom** of the header area. This accommodates the logo bulge that overlays into their space. ``` ┌─────────────────────────┐ │ │ ← Logo bulge overlays this space │ (empty) │ │ │ │ [Icon] Title [Action]│ ← Content docked to bottom (8px margin) ├─────────────────────────┤ │ Panel content... │ ``` Right-side panels (Detail Panel, Chat Dock) use standard vertically-centered headers since the logo bulge doesn't reach them. ### Nav Panel Sections Nav items can be organized into **sections**. Section headers are **conditionally visible** - they only appear when multiple sections exist. #### Single Section (headers hidden) When all items belong to one section, no headers are shown: ``` ┌─────────────────────────┐ │ [≡] Sections [···] │ ← Panel header ├─────────────────────────┤ │ ▸ Containers │ │ Networks │ │ Volumes │ │ Images │ │ │ └─────────────────────────┘ ``` #### Multiple Sections (headers visible) When items span multiple sections, section headers appear: ``` ┌─────────────────────────┐ │ [≡] Sections [···] │ ← Panel header ├─────────────────────────┤ │ ▌Portainer │ ← Section header (subtle bg, left accent) │ ▸ Containers │ │ Networks │ │ Volumes │ │ Images │ │ │ │ ▌NPM │ ← Section header │ Proxy Hosts │ │ Redirections │ │ Streams │ │ │ │ ▌Authentik │ ← Section header │ Users │ │ Groups │ │ Applications │ │ │ └─────────────────────────┘ ``` #### Section Header Styling ``` ┌─────────────────────────┐ │▌SECTION NAME │ ← Left accent bar (2px, primary color) └─────────────────────────┘ ← Background: surfaceContainerHigh ← Text: labelSmall, onSurfaceVariant ← Padding: 8px horizontal, 6px vertical ← All caps, letter-spacing: 0.5 ``` #### Data Model ```dart /// NavItem model (in nav_panel.dart) class NavItem { final String id; final String label; final IconData icon; final String? section; // null = ungrouped } // Section headers auto-generate from unique section values // Visibility: items.map((i) => i.section).toSet().length > 1 ``` #### Route Configuration Driven Sections are defined in the feature's route configuration, not the widget: ```dart /// In lib/features/control_room/router.dart enum ControlRoomNav { // Portainer section containers('containers', 'Containers', Icons.dns, 'Portainer'), networks('networks', 'Networks', Icons.hub, 'Portainer'), volumes('volumes', 'Volumes', Icons.storage, 'Portainer'), images('images', 'Images', Icons.photo_library, 'Portainer'), // NPM section (future) proxyHosts('proxy-hosts', 'Proxy Hosts', Icons.public, 'NPM'), redirections('redirections', 'Redirections', Icons.alt_route, 'NPM'), // Authentik section (future) users('users', 'Users', Icons.people, 'Authentik'), groups('groups', 'Groups', Icons.group_work, 'Authentik'); const ControlRoomNav(this.id, this.label, this.icon, this.section); final String id; final String label; final IconData icon; final String section; NavItem toNavItem() => NavItem( id: id, label: label, icon: icon, section: section, ); } ``` The NavPanel widget receives items and auto-generates section headers based on unique section values in the list. No section logic lives in the widget - it just renders what the route config provides. --- ## Panel Configurations by Room ### Front Hall ``` ┌────────────────────────────────────────────────────────────────┬────────────┐ │ PRIMARY CONTENT (no nav panel) │ CHAT DOCK │ │ Dashboard cards, activity feed │ (expanded) │ └────────────────────────────────────────────────────────────────┴────────────┘ ``` ### Control Room ``` ┌───────────┬──────────────────────────────────────────────────────┬──────────┐ │ NAV PANEL │ PRIMARY CONTENT │ CHAT │ │ │ ┌────────────┬────────────────────────────────────┐ │ DOCK │ │ Sections: │ │ FILTER │ DATA GRID │ │ │ │ • Contai. │ │ PANEL │ Container/Stack list │ │ (collap- │ │ • Stacks │ │ │ │ │ sed) │ │ • Network │ │ Stack list │ │ │ │ │ • Volumes │ │ + search │ │ │ │ └───────────┴──┴────────────┴────────────────────────────────────┴─┴──────────┘ ``` ### Parlor ``` ┌───────────┬────────────────────────────────────────────────────┬────────────┐ │ NAV PANEL │ PRIMARY CONTENT │ CHAT DOCK │ │ │ Device controls, scenes │ (collapsed)│ │ Areas: │ │ │ │ • Living │ │ │ │ • Bedroom │ │ │ │ • Kitchen │ │ │ └───────────┴────────────────────────────────────────────────────┴────────────┘ ``` --- ## Component Roles | Component | Flex | Description | |-----------|------|-------------| | **Header Bar** | intrinsic (56px) | Logo bulge, room icons, profile menu | | **Nav Panel** | `flex: 0`, 280px | Room-level section navigation | | **Filter Panel** | `flex: 0`, 280px | Data filtering within a section | | **Primary Content** | `flex: 1` | Main working area | | **Detail Panel** | `flex: 0`, 320-400px | Selected item details (optional) | | **Chat Dock** | `flex: 0`, intrinsic | Tatlock assistant, collapsible | --- ## Responsive Breakpoints ### Wide (>= 1200px) ``` ┌───────────┬─────────────────────────────────────────────┬────────────────────┐ │ NAV PANEL │ [Filter Panel] [═══ Data Grid ═══] │ CHAT DOCK │ │ (280px) │ (280px) (flex: 1) │ (expanded, 320px) │ └───────────┴─────────────────────────────────────────────┴────────────────────┘ ``` - All panels visible - Chat dock expanded by default on Front Hall - Nav panel visible with full labels - Filter panel visible (where applicable) - Full data grid columns ### Medium (>= 800px, < 1200px) ``` ┌───────────┬───────────────────────────────────────────────────────────┬────┐ │ NAV PANEL │ [Filter Panel] [═══════════ Data Grid ═══════════] │ 💬 │ │ (280px) │ (280px) (flex: 1) │48px│ └───────────┴───────────────────────────────────────────────────────────┴────┘ ``` - Chat dock collapsed to icon rail (48px) - Click chat icon to expand as overlay - Nav panel still visible - Filter panel still visible - Data grid may hide some columns ### Compact (>= 600px, < 800px) ``` ┌────┬───────────────────────────────────────────────────────────────────┬────┐ │ ≡ │ [═══════════════════════ Content ═══════════════════════] │ 💬 │ │48px│ Filter panel becomes top bar or collapsible │48px│ └────┴───────────────────────────────────────────────────────────────────┴────┘ ``` - Nav panel collapses to icon rail (48px), expands as drawer on tap - Filter panel moves to top of content or becomes collapsible - Chat dock remains collapsed (48px) - Data grid shows essential columns only ### Mobile (< 600px) ``` ┌────────────────────────────────────────────────────────────────────────┐ │ HEADER: [≡] [Room Icons scroll] ─────────────────────────────── [💬] │ ├────────────────────────────────────────────────────────────────────────┤ │ │ │ [═══════════════════════ Content ══════════════════════] │ │ Filter as expandable section at top │ │ │ └────────────────────────────────────────────────────────────────────────┘ ``` - Single column layout - Nav panel opens as full-screen drawer (hamburger menu) - Filter panel becomes expandable section at top of content - Chat opens as bottom sheet (60vh max) - Data grid becomes card list or single-column table --- ## Panel Folding Summary | Panel | Wide (≥1200) | Medium (≥800) | Compact (≥600) | Mobile (<600) | |-------|--------------|---------------|----------------|---------------| | **Nav Panel** | 280px visible | 280px visible | 48px rail → drawer | Hidden → drawer | | **Filter Panel** | 280px visible | 280px visible | Collapsed/top bar | Expandable section | | **Chat Dock** | 320px expanded | 48px rail → overlay | 48px rail → overlay | Icon → bottom sheet | | **Detail Panel** | 320-400px slide-in | 320px slide-in | Full-width overlay | Full-screen | --- ## Component Constraints | Component | Min Width | Max Width | Collapse Behavior | |-----------|-----------|-----------|-------------------| | Chat Dock (expanded) | 280px | 33vw | Collapses to icon rail | | Chat Dock (collapsed) | 48px | 48px | - | | Context Sidebar | 200px | - | Collapses to 56px icon rail, then drawer | | Stat Card | 180px | 1fr | Grid reflows | | Service Card | 120px | 1fr | Grid reflows | | Data Grid | 400px | - | Horizontal scroll if needed | --- ## Room Wireframes ### Front Hall (Dashboard) Primary landing page. Chat dock expanded by default. ``` ┌─────────────────────────────────────────────────────────────┬─────────────────┐ │ │ │ │ STAT CARDS (auto-fit grid, min 180px) │ TATLOCK CHAT │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────┐ │ │ │ │ CPU │ │ Memory │ │ Disk │ │Containe│ │ [conversation] │ │ │ 45% │ │ 62% │ │ 78% │ │ 34/40 │ │ │ │ │ ████████░░░ │ │ ██████████░ │ │ ███████████ │ │ ██████ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ └────────┘ │ │ │ │ │ │ QUICK ACCESS (auto-fill grid, min 120px) │ │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │ │ 🎬 │ │ 🔍 │ │ 📚 │ │ 🎮 │ │ 🌐 │ │ │ │ │Jellyfin│ │SearXNG │ │ Wiki │ │ AMP │ │Requests│ │ │ │ │● Online│ │● Online│ │● Online│ │● Online│ │● Online│ │ │ │ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ │ │ │ │ │ │ RECENT ACTIVITY │ [input field] │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ │ 📦 Container 'ollama' restarted 15 min ago │ │ │ │ │ 🏠 Living room set to evening mode 1 hr ago │ │ │ │ │ 🔄 System backup completed 2 hrs ago │ │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ │ │ └─────────────────────────────────────────────────────────────┴─────────────────┘ ``` **Components:** - Stat Cards: CPU, Memory, Disk, Containers - Quick Access: Service cards grid (external links) - Recent Activity: Timeline of system events --- ### Control Room (Infrastructure) Nav panel with grouped section navigation. Chat collapsed. **Current state** (single grouper - headers hidden): ``` ┌───────────────┬───────────────────────────────────────────────────────┬──────┐ │ │ │ │ │ SECTIONS │ CONTAINERS [Search] [+ New] │ 💬 │ │ │ ─────────────────────────────────────────────────── │ │ │ ▸ Containers │ ☑ NAME STATUS CPU MEM IMAGE ⋮ │ │ │ Networks │ ☐ jellyfin ● Run 2.3% 1.2GB latest ⋮ │ │ │ Volumes │ ☐ ollama ● Run 45% 8.0GB 0.1.32 ⋮ │ │ │ Images │ ☐ postgres ● Run 1.1% 512MB 16-alp ⋮ │ │ │ │ ☐ redis ● Run 0.2% 128MB 7-alp ⋮ │ │ │ │ ─────────────────────────────────────────────────── │ │ │ │ Showing 5 of 40 < 1 2 3 4 5 > │ │ │ │ │ │ └───────────────┴───────────────────────────────────────────────────────┴──────┘ ``` **Future state** (multiple groupers - headers visible): ``` ┌───────────────┬───────────────────────────────────────────────────────┬──────┐ │ │ │ │ │ SECTIONS │ CONTAINERS [Search] [+ New] │ 💬 │ │ │ ─────────────────────────────────────────────────── │ │ │ ▌PORTAINER │ ☑ NAME STATUS CPU MEM IMAGE ⋮ │ │ │ ▸ Containers │ ☐ jellyfin ● Run 2.3% 1.2GB latest ⋮ │ │ │ Networks │ ☐ ollama ● Run 45% 8.0GB 0.1.32 ⋮ │ │ │ Volumes │ ─────────────────────────────────────────────────── │ │ │ Images │ Showing 3 of 40 < 1 2 3 4 5 > │ │ │ │ │ │ │ ▌NPM │ │ │ │ Proxy Hosts│ │ │ │ Redirects │ │ │ │ │ │ │ │ ▌AUTHENTIK │ │ │ │ Users │ │ │ │ Groups │ │ │ │ │ │ │ └───────────────┴───────────────────────────────────────────────────────┴──────┘ ``` **Components:** - Nav Panel: Grouped section navigation (grouper headers conditional) - Filter Panel: Stack/item filtering within section - DataGrid: Container list with bulk actions - Detail Panel: Opens on row selection (replaces grid or slides in) --- ### Parlor (Housekeeping) Context sidebar with areas and scenes. Chat collapsed. ``` ┌───────────────┬───────────────────────────────────────────────────────┬──────┐ │ │ │ │ │ AREAS │ LIVING ROOM │ 💬 │ │ │ ─────────────────────────────────────────────────── │ │ │ ▸ Living Rm │ │ │ │ Bedroom │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Kitchen │ │ 💡 │ │ 📺 │ │ 🌡️ │ │ 🔌 │ │ │ │ Office │ │ Ceiling │ │ Samsung │ │ Thermo │ │ Outlet 1 │ │ │ │ Garage │ │ 75% │ │ OFF │ │ 72°F │ │ ON │ │ │ │ │ │ ○─────● │ │ [ ] │ │ [-] [+] │ │ [ ] │ │ │ │ ──────────── │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ SCENES │ │ │ │ 🌅 Morning │ SCENES │ │ │ ☀️ Day │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │ │ 🌆 Evening │ │ 🌅 │ │ ☀️ │ │ 🌆 │ │ 🌙 │ │ 🎬 │ │ │ │ 🌙 Night │ │ Morn │ │ Day │ │ Eve │ │Night │ │Movie │ │ │ │ │ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │ │ └───────────────┴───────────────────────────────────────────────────────┴──────┘ ``` **Components:** - Context Sidebar: Area list + scene shortcuts - Device Grid: Control tiles for current area - Scene Bar: Quick activation buttons --- ## Grid Specifications ### Stat Cards ```css grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1rem; ``` | Viewport Width | Cards per Row | |----------------|---------------| | >= 900px | 4 | | >= 720px | 3 | | >= 540px | 2 | | < 540px | 1 | ### Service Cards ```css grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 0.75rem; ``` Reflows naturally based on available width. --- ## Chat Dock Behavior The Tatlock chat assistant is **omnipresent** - accessible from any room. | State | Width | Trigger | |-------|-------|---------| | Expanded | intrinsic (min 280px, max 33vw) | Default on Front Hall, click icon elsewhere | | Collapsed | 48px (icon rail) | Default on other rooms, viewport < 1200px | | Overlay | 90vw or 400px max | Expanded on medium viewports | | Bottom Sheet | 100vw, 60vh max | Mobile viewports | | **Fullscreen** | 100vw, 100vh | User-triggered overlay (future) | **Persistence:** Conversation state persists across room navigation. **Future:** Fullscreen mode provides an immersive chat experience as a modal overlay, independent of the current room. --- ## Flutter Implementation Notes ### Panel Widget Library All panels are built from shared components in `lib/shared/layouts/widgets/`: | Widget | File | Purpose | |--------|------|---------| | `PanelContainer` | `panel_container.dart` | Base container for all panels | | `PanelHeader` | `panel_header.dart` | Configurable header (bottom-docked or centered) | | `NavPanel` | `nav_panel.dart` | Left-side room navigation | | `FilterPanel` | `filter_panel.dart` | Left-side data filtering | | `DetailPanel` | `detail_panel.dart` | Right-side item details | | `ChatDock` | `chat_dock.dart` | Right-side AI assistant | ### PanelHeader Configuration ```dart /// Panel header with configurable content alignment. class PanelHeader extends StatelessWidget { const PanelHeader({ required this.title, required this.icon, this.actions, this.dockToBottom = false, // true for left-side panels }); } ``` **Left-side panels** (Nav, Filter) use `dockToBottom: true` to accommodate the logo bulge: - Header height: 56px (matches app header) - Content aligned to bottom with 8px margin - Empty space at top allows logo bulge overlay **Right-side panels** (Detail, Chat) use `dockToBottom: false`: - Standard vertically-centered content - No accommodation needed for logo bulge ### Recommended Widgets | Concept | Flutter Widget | |---------|----------------| | Breakpoint detection | `LayoutBuilder`, `MediaQuery` | | Main layout | `Row` with `Expanded` and `SizedBox` | | Collapsible dock | `AnimatedContainer` or `AnimatedSize` | | Grid layouts | `GridView.builder` with `SliverGridDelegateWithMaxCrossAxisExtent` | | Drawer fallback | `Scaffold.drawer` or custom `Drawer` | | Bottom sheet chat | `showModalBottomSheet` or `DraggableScrollableSheet` | ### Breakpoint Constants ```dart abstract class Breakpoints { static const double mobile = 600; static const double compact = 800; static const double medium = 1200; } ``` ### Panel Width Constants ```dart abstract class PanelWidths { static const double navPanel = 280; static const double filterPanel = 280; static const double detailPanel = 360; static const double chatDockExpanded = 320; static const double chatDockCollapsed = 48; static const double railWidth = 48; } ``` ### Layout Builder Pattern ```dart LayoutBuilder( builder: (context, constraints) { if (constraints.maxWidth >= Breakpoints.medium) { return WideLayout(...); } else if (constraints.maxWidth >= Breakpoints.compact) { return MediumLayout(...); } else { return CompactLayout(...); } }, ) ``` --- ## Related Documents - [PHILOSOPHY.md](../PHILOSOPHY.md) - "Rooms of the Estate" metaphor - [ARCHITECTURE.md](./ARCHITECTURE.md) - Clean Architecture patterns - [THEMING.md](./THEMING.md) - Material3 color scheme - [DATAGRID.md](./DATAGRID.md) - DataGrid component API