refactor: rename features to room-based directory structure

- Rename features/dashboard → features/front_hall
- Update routes: /, /control-room, /parlor, /settings
- Update navigation to 4 rooms (removed chat tab - will be omnipresent dock)
- Add docs/UI_LAYOUT.md with wireframes and responsive specs
- Update ARCHITECTURE.md and PLAN.md to reflect room structure

Directory structure now mirrors "Rooms of the Estate" UI navigation:
- front_hall/ (dashboard)
- control_room/ (infrastructure - containers, stacks, etc.)
- parlor/ (home automation)
- library/ (future)
- study/ (future, hidden)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-30 23:14:40 +01:00
co-authored by Claude Opus 4.5
parent adcb0e0427
commit a4aaea33b8
8 changed files with 505 additions and 126 deletions
+4 -1
View File
@@ -32,12 +32,15 @@ This document contains instructions and documentation references for AI assistan
* **Health check**: `curl http://192.168.86.149:8000/health` * **Health check**: `curl http://192.168.86.149:8000/health`
### 🛡️ Git Discipline ### 🛡️ Git Discipline
* **NEVER commit to `main` or `master` directly.** Always create a feature branch: `feature/your-feature-name` or `fix/issue-description`.
* **Commit Messages:** Use the [Conventional Commits](https://www.conventionalcommits.org/) format. * **Commit Messages:** Use the [Conventional Commits](https://www.conventionalcommits.org/) format.
* `feat: add user login endpoint` * `feat: add user login endpoint`
* `fix: resolve database connection timeout` * `fix: resolve database connection timeout`
* `refactor: split monolith dependency file` * `refactor: split monolith dependency file`
* **Atomic Commits:** Keep commits small. One logical change = one commit. * **Atomic Commits:** Keep commits small. One logical change = one commit.
* **Version Tagging:** Every version increment (major.minor.patch, not build count) must have a corresponding git tag.
* Format: `v{major}.{minor}.{patch}` (e.g., `v0.3.0`)
* Tag after updating `pubspec.yaml` version and CHANGELOG
* Push tags with `git push --tags`
### 🧪 Testing Requirements ### 🧪 Testing Requirements
* **Always add tests for new code before committing.** No exceptions. * **Always add tests for new code before committing.** No exceptions.
+6
View File
@@ -7,7 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added
- docs/UI_LAYOUT.md - Responsive layout specification with "Rooms of the Estate" navigation
### Changed ### Changed
- Directory structure now mirrors room navigation (front_hall/, control_room/, parlor/, library/, study/)
- Chat moved to top-level `lib/chat/` (omnipresent, not a room)
- Updated ARCHITECTURE.md and PLAN.md to reflect room-based structure
- Version generation now automated via build_runner (replaces manual script) - Version generation now automated via build_runner (replaces manual script)
- `lib/version.g.dart` auto-generated from `pubspec.yaml` during build - `lib/version.g.dart` auto-generated from `pubspec.yaml` during build
+94 -66
View File
@@ -91,7 +91,8 @@ tatlock-ui/
│ ├── API_INTEGRATION.md # Core API and Tatlock API endpoints │ ├── API_INTEGRATION.md # Core API and Tatlock API endpoints
│ ├── DEPLOYMENT.md # Docker, NPM, Portainer setup │ ├── DEPLOYMENT.md # Docker, NPM, Portainer setup
│ ├── DATAGRID.md # DataGrid component API specification │ ├── DATAGRID.md # DataGrid component API specification
── THEMING.md # Theme system documentation ── THEMING.md # Theme system documentation
│ └── UI_LAYOUT.md # Wireframes, responsive breakpoints, room navigation
``` ```
**Content for ARCHITECTURE.md** - Document: **Content for ARCHITECTURE.md** - Document:
@@ -165,32 +166,31 @@ lib/shared/components/data_grid/
└── core_api_source.dart # Core API data source adapter └── core_api_source.dart # Core API data source adapter
``` ```
### Phase 3: Dashboard (Organizr Replacement) ### Phase 3: Front Hall (Organizr Replacement)
**Goal**: Replace Organizr homepage with Tatlock UI dashboard. **Goal**: Replace Organizr homepage with Tatlock UI dashboard.
**Features to match** (from portainer-ui.png): **Features to match** (from portainer-ui.png):
- Sidebar navigation with grouped items - System metrics gauges (CPU, Memory, Disk, Containers)
- System metrics gauges (CPU, Memory, Disk)
- Weather widget (optional - requires API)
- Air quality widget (optional)
- Service quick links grid - Service quick links grid
- Recent activity timeline
- Chat dock expanded by default
**Files to create**: **Files to create**:
``` ```
lib/features/dashboard/ lib/features/front_hall/
├── presentation/ ├── presentation/
│ ├── pages/dashboard_page.dart │ ├── pages/front_hall_page.dart
│ └── widgets/ │ └── widgets/
│ ├── metrics_gauge.dart │ ├── stat_card.dart
│ ├── service_card.dart │ ├── service_card.dart
│ ├── service_grid.dart │ ├── service_grid.dart
│ └── quick_actions.dart │ └── activity_timeline.dart
├── domain/ ├── domain/
│ ├── entities/system_metrics.dart │ ├── entities/system_metrics.dart
│ └── repositories/dashboard_repository.dart │ └── repositories/front_hall_repository.dart
└── data/ └── data/
├── datasources/core_api_dashboard_source.dart ├── datasources/core_api_front_hall_source.dart
└── repositories/dashboard_repository_impl.dart └── repositories/front_hall_repository_impl.dart
``` ```
**Core API endpoints used**: **Core API endpoints used**:
@@ -198,35 +198,41 @@ lib/features/dashboard/
- `GET /infrastructure/widget-data` - Services + groups - `GET /infrastructure/widget-data` - Services + groups
- `GET /health` - Overall health status - `GET /health` - Overall health status
### Phase 4: Container Management ### Phase 4: Control Room - Container Management
**Goal**: Full container CRUD and monitoring. **Goal**: Full container CRUD and monitoring.
**Files to create**: **Files to create**:
``` ```
lib/features/containers/ lib/features/control_room/
├── presentation/ ├── shared/ # Shared within Control Room
── pages/ ── widgets/
── containers_list_page.dart ── resource_chart.dart
│ │ └── container_detail_page.dart ── containers/
│ ├── widgets/ │ ├── presentation/
│ │ ├── container_status_badge.dart │ │ ├── pages/
│ │ ├── container_logs_viewer.dart │ │ ├── containers_list_page.dart
│ │ └── container_resource_chart.dart │ │ └── container_detail_page.dart
└── providers/ │ ├── widgets/
── containers_provider.dart │ ├── container_status_badge.dart
├── domain/ │ │ │ └── container_logs_viewer.dart
├── entities/container.dart │ └── providers/
├── repositories/container_repository.dart │ └── containers_provider.dart
── usecases/ ── domain/
├── get_containers.dart ├── entities/container.dart
├── start_container.dart ├── repositories/container_repository.dart
── stop_container.dart ── usecases/
│ ├── restart_container.dart │ ├── get_containers.dart
└── get_container_logs.dart ├── start_container.dart
└── data/ │ │ ├── stop_container.dart
├── models/container_model.dart │ │ ├── restart_container.dart
├── datasources/containers_datasource.dart │ └── get_container_logs.dart
└── repositories/container_repository_impl.dart └── data/
│ ├── models/container_model.dart
│ ├── datasources/containers_datasource.dart
│ └── repositories/container_repository_impl.dart
├── stacks/ # Future
├── networks/ # Future
└── volumes/ # Future
``` ```
**Core API endpoints used**: **Core API endpoints used**:
@@ -236,17 +242,20 @@ lib/features/containers/
- `POST /infrastructure/containers/{id}/{action}` - `POST /infrastructure/containers/{id}/{action}`
- `GET /infrastructure/resources/containers` - `GET /infrastructure/resources/containers`
### Phase 5: LLM Chat Interface ### Phase 5: Chat Dock (Omnipresent)
**Goal**: Custom chat UI for Tatlock agents with SSE streaming. **Goal**: Custom chat UI for Tatlock agents with SSE streaming. Injected at layout level, not a room.
**Future requirement**: Expand-to-fullscreen option as secondary UI overlay.
**Files to create**: **Files to create**:
``` ```
lib/core/api/ lib/core/api/
└── sse_client.dart # Platform-aware SSE streaming └── sse_client.dart # Platform-aware SSE streaming
lib/features/chat/ lib/chat/ # Top-level, not under features/
├── presentation/ ├── presentation/
│ ├── pages/chat_page.dart │ ├── chat_dock.dart # Collapsible dock widget
│ ├── chat_fullscreen.dart # Fullscreen overlay (future)
│ └── widgets/ │ └── widgets/
│ ├── message_list.dart │ ├── message_list.dart
│ ├── message_bubble.dart │ ├── message_bubble.dart
@@ -280,15 +289,15 @@ lib/features/chat/
- Mobile/Desktop: eventsource or http package with stream parsing - Mobile/Desktop: eventsource or http package with stream parsing
- Handle `reasoning_content` field for think blocks (DeepSeek R1 format) - Handle `reasoning_content` field for think blocks (DeepSeek R1 format)
### Phase 6: Home Automation ### Phase 6: Parlor (Home Automation)
**Goal**: Control Home Assistant via Core API. **Goal**: Control Home Assistant via Core API.
**Files to create**: **Files to create**:
``` ```
lib/features/housekeeping/ lib/features/parlor/
├── presentation/ ├── presentation/
│ ├── pages/ │ ├── pages/
│ │ ├── housekeeping_page.dart │ │ ├── parlor_page.dart
│ │ └── area_detail_page.dart │ │ └── area_detail_page.dart
│ └── widgets/ │ └── widgets/
│ ├── device_tile.dart │ ├── device_tile.dart
@@ -300,13 +309,13 @@ lib/features/housekeeping/
│ │ ├── device.dart │ │ ├── device.dart
│ │ ├── area.dart │ │ ├── area.dart
│ │ └── scene.dart │ │ └── scene.dart
│ └── repositories/housekeeping_repository.dart │ └── repositories/parlor_repository.dart
└── data/ └── data/
├── models/ ├── models/
│ ├── device_model.dart │ ├── device_model.dart
│ └── device_control_request.dart │ └── device_control_request.dart
├── datasources/housekeeping_datasource.dart ├── datasources/parlor_datasource.dart
└── repositories/housekeeping_repository_impl.dart └── repositories/parlor_repository_impl.dart
``` ```
**Core API endpoints used**: **Core API endpoints used**:
@@ -608,6 +617,7 @@ dev_dependencies:
## Reference Documents ## Reference Documents
- **UI Layout Spec**: `docs/UI_LAYOUT.md` - Wireframes, breakpoints, responsive behavior
- **Infrastructure**: `/mnt/media/Projects/portainer-core/CONTAINERS.md` - **Infrastructure**: `/mnt/media/Projects/portainer-core/CONTAINERS.md`
- **Tatlock Philosophy**: `/mnt/media/Projects/tatlock/PHILOSOPHY.md` - **Tatlock Philosophy**: `/mnt/media/Projects/tatlock/PHILOSOPHY.md`
- **Core API**: `http://192.168.86.149:8083/docs` - **Core API**: `http://192.168.86.149:8083/docs`
@@ -618,26 +628,44 @@ dev_dependencies:
--- ---
## Navigation Structure (matching Organizr) ## Navigation Structure ("Rooms of the Estate")
> **See [docs/UI_LAYOUT.md](docs/UI_LAYOUT.md) for detailed wireframes and responsive specifications.**
The UI uses **tabbed room navigation** in the header rather than a traditional sidebar. Each room is a focused context with its own layout. The Tatlock chat assistant is omnipresent as a collapsible right dock.
``` ```
├── Homepage (Dashboard) Header Tabs:
├── Tatlock (Chat) ┌─────────────────────────────────────────────────────────────────┐
├── Jellyfin (external link) │ Front Hall │ Control Room │ Parlor │ Library │ │
├── SearXNG (external link) └─────────────────────────────────────────────────────────────────┘
├── AMP
│ └── AMP Home Room Contents:
├── Coding ├── Front Hall (Dashboard)
│ ├── Cloud IDE (external) │ ├── Stat cards (CPU, Memory, Disk, Containers)
── Gitea (external) ── Quick access service grid (external links)
├── Infrastructure │ ├── Recent activity timeline
── Containers (main feature) ── Chat dock EXPANDED by default
├── Netdata (external link)
│ ├── Portainer (external link) ├── Control Room (Infrastructure)
── Proxy Manager (external link) ── Context sidebar: Containers, Stacks, Networks, Volumes, Images
├── Housekeeping (Home Automation) │ ├── External links: Netdata, Portainer, NPM
├── API Docs (external link to Core API) │ ├── DataGrid views for each section
└── Settings │ └── Chat dock collapsed
├── Parlor (Housekeeping)
│ ├── Context sidebar: Areas, Scenes
│ ├── Device control tiles per area
│ ├── Scene activation buttons
│ └── Chat dock collapsed
├── Library (Future)
│ ├── Knowledge management
│ ├── Bookmarks, notes, docs
│ └── Chat dock collapsed
└── Study (Hidden - Future)
└── Secretarial: email, calendar
``` ```
--- ---
+26 -5
View File
@@ -8,6 +8,8 @@ Tatlock UI follows **Clean Architecture** with feature-based organization. This
## Project Structure ## Project Structure
The directory structure mirrors the "Rooms of the Estate" UI navigation (see [UI_LAYOUT.md](./UI_LAYOUT.md)).
``` ```
lib/ lib/
├── main.dart # Entry point ├── main.dart # Entry point
@@ -23,13 +25,32 @@ lib/
├── shared/ # Reusable components ├── shared/ # Reusable components
│ ├── components/ # DataGrid, common widgets │ ├── components/ # DataGrid, common widgets
│ └── layouts/ # App scaffold, navigation │ └── layouts/ # App scaffold, navigation
── features/ # Feature modules ── features/ # Feature modules (rooms)
├── dashboard/ ├── front_hall/ # Dashboard - estate overview
├── chat/ ├── control_room/ # Infrastructure
├── containers/ │ │ ├── containers/ # Container management
── housekeeping/ │ │ ── stacks/ # Stack management
│ │ ├── networks/ # Network management
│ │ └── volumes/ # Volume management
│ ├── parlor/ # Housekeeping - home automation
│ ├── library/ # Knowledge management (future)
│ └── study/ # Secretarial tasks (future)
└── chat/ # Tatlock chat - omnipresent, NOT a room
``` ```
### Room-to-Directory Mapping
| UI Room | Directory | Purpose |
|---------|-----------|---------|
| Front Hall | `features/front_hall/` | Dashboard, overview, quick access |
| Control Room | `features/control_room/` | Infrastructure management |
| Parlor | `features/parlor/` | Home automation |
| Library | `features/library/` | Knowledge, docs, bookmarks |
| Study | `features/study/` | Email, calendar (hidden for now) |
| *(omnipresent)* | `chat/` | Tatlock assistant dock |
Note: `chat/` lives at the top level of `lib/` (not under `features/`) because it's not a navigable room - it's an omnipresent dock injected at the layout level.
## Feature Structure ## Feature Structure
Each feature follows a three-layer architecture: Each feature follows a three-layer architecture:
+322
View File
@@ -0,0 +1,322 @@
# 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 (intrinsic height) │
│ [Logo] [═══════ Room Tabs (scrollable) ═══════] [Notifications] [Profile] │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────┐ ┌───────────────────┐ │
│ │ │ │ │ │
│ │ MAIN CONTENT │ │ CHAT DOCK │ │
│ │ flex: 1 │ │ flex: 0 │ │
│ │ │ │ │ │
│ │ ┌─────────────┐ ┌────────────────────────────┐ │ │ Tatlock chat │ │
│ │ │ CONTEXT │ │ PRIMARY │ │ │ assistant, │ │
│ │ │ SIDEBAR │ │ CONTENT │ │ │ persistent │ │
│ │ │ │ │ │ │ │ across rooms │ │
│ │ │ flex: 0 │ │ flex: 1 │ │ │ │ │
│ │ │ intrinsic │ │ │ │ │ │ │
│ │ └─────────────┘ └────────────────────────────┘ │ │ │ │
│ │ │ │ │ │
│ └─────────────────────────────────────────────────┘ └───────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
### Component Roles
| Component | Flex | Description |
|-----------|------|-------------|
| **Header** | intrinsic | Logo, room tabs, action icons |
| **Main Content** | `flex: 1` | Fills remaining horizontal space |
| **Context Sidebar** | `flex: 0`, intrinsic | Room-specific navigation (Control Room, Parlor) |
| **Primary Content** | `flex: 1` | Room's main working area |
| **Chat Dock** | `flex: 0`, intrinsic | Tatlock assistant, collapsible |
---
## Responsive Breakpoints
### Wide (>= 1200px)
```
┌────────────────────────────────────────────────────────┬────────────────────┐
│ [Sidebar] [══════════ Content ══════════] │ Chat (expanded) │
└────────────────────────────────────────────────────────┴────────────────────┘
```
- Chat dock expanded by default on Front Hall
- Context sidebar visible with labels
- Full data grid columns
### Medium (>= 800px, < 1200px)
```
┌────────────────────────────────────────────────────────────────────────┬────┐
│ [Sidebar] [══════════════════ Content ══════════════════] │ 💬 │
└────────────────────────────────────────────────────────────────────────┴────┘
```
- Chat dock collapsed to icon rail
- Click to expand as overlay
- Context sidebar still visible
### Compact (>= 600px, < 800px)
```
┌────────────────────────────────────────────────────────────────────────┬────┐
│ [≡] [════════════════════ Content ════════════════════] │ 💬 │
└────────────────────────────────────────────────────────────────────────┴────┘
```
- Context sidebar becomes drawer (hamburger menu)
- Chat dock collapsed
- Reduced data grid columns
### Mobile (< 600px)
```
┌────────────────────────────────────────────────────────────────────────┐
│ [≡] [Tabs scroll horizontally] [💬] │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ [══════════════════════ Content ══════════════════════] │
│ │
└────────────────────────────────────────────────────────────────────────┘
```
- Single column layout
- Room tabs scroll horizontally
- Chat opens as bottom sheet
- Context sidebar as full-screen drawer
---
## 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)
Context sidebar with section navigation. Chat collapsed.
```
┌───────────────┬───────────────────────────────────────────────────────┬──────┐
│ │ │ │
│ SECTIONS │ CONTAINERS [Search] [+ New] │ 💬 │
│ │ ─────────────────────────────────────────────────── │ │
│ ▸ Containers │ ☑ NAME STATUS CPU MEM IMAGE ⋮ │ │
│ Stacks │ ☐ jellyfin ● Run 2.3% 1.2GB latest ⋮ │ │
│ Networks │ ☐ ollama ● Run 45% 8.0GB 0.1.32 ⋮ │ │
│ Volumes │ ☐ postgres ● Run 1.1% 512MB 16-alp ⋮ │ │
│ Images │ ☐ redis ● Run 0.2% 128MB 7-alp ⋮ │ │
│ │ ☐ authentik ○ Stop - - 2024.2 ⋮ │ │
│ ──────────── │ ─────────────────────────────────────────────────── │ │
│ Netdata ↗ │ Showing 5 of 40 < 1 2 3 4 5 > │ │
│ Portainer↗ │ │ │
│ NPM ↗ │ │ │
│ │ │ │
└───────────────┴───────────────────────────────────────────────────────┴──────┘
```
**Components:**
- Context Sidebar: Section nav + external links
- 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
### 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;
}
```
### 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
@@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
import '../../../../version.g.dart'; import '../../../../version.g.dart';
/// Dashboard home page - overview of the homelab. /// Front Hall - estate overview and quick access.
class DashboardPage extends StatelessWidget { class FrontHallPage extends StatelessWidget {
const DashboardPage({super.key}); const FrontHallPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -12,7 +12,7 @@ class DashboardPage extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Dashboard'), title: const Text('Front Hall'),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
+21 -18
View File
@@ -3,17 +3,17 @@ import 'package:go_router/go_router.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../shared/layouts/app_scaffold.dart'; import '../shared/layouts/app_scaffold.dart';
import '../features/dashboard/presentation/pages/dashboard_page.dart'; import '../features/front_hall/presentation/pages/front_hall_page.dart';
part 'app_router.g.dart'; part 'app_router.g.dart';
/// Route paths as constants. /// Route paths as constants.
abstract class AppRoutes { abstract class AppRoutes {
static const dashboard = '/'; static const frontHall = '/';
static const chat = '/chat'; static const controlRoom = '/control-room';
static const containers = '/containers'; static const containers = '/control-room/containers';
static const containerDetail = '/containers/:id'; static const containerDetail = '/control-room/containers/:id';
static const housekeeping = '/housekeeping'; static const parlor = '/parlor';
static const settings = '/settings'; static const settings = '/settings';
} }
@@ -21,24 +21,25 @@ abstract class AppRoutes {
@riverpod @riverpod
GoRouter appRouter(AppRouterRef ref) { GoRouter appRouter(AppRouterRef ref) {
return GoRouter( return GoRouter(
initialLocation: AppRoutes.dashboard, initialLocation: AppRoutes.frontHall,
debugLogDiagnostics: true, debugLogDiagnostics: true,
routes: [ routes: [
ShellRoute( ShellRoute(
builder: (context, state, child) => AppScaffold(child: child), builder: (context, state, child) => AppScaffold(child: child),
routes: [ routes: [
GoRoute( GoRoute(
path: AppRoutes.dashboard, path: AppRoutes.frontHall,
name: 'dashboard', name: 'frontHall',
builder: (context, state) => const DashboardPage(), builder: (context, state) => const FrontHallPage(),
), ),
GoRoute( GoRoute(
path: AppRoutes.chat, path: AppRoutes.controlRoom,
name: 'chat', name: 'controlRoom',
builder: (context, state) => const _PlaceholderPage(title: 'Chat'), builder: (context, state) =>
), const _PlaceholderPage(title: 'Control Room'),
routes: [
GoRoute( GoRoute(
path: AppRoutes.containers, path: 'containers',
name: 'containers', name: 'containers',
builder: (context, state) => builder: (context, state) =>
const _PlaceholderPage(title: 'Containers'), const _PlaceholderPage(title: 'Containers'),
@@ -53,11 +54,13 @@ GoRouter appRouter(AppRouterRef ref) {
), ),
], ],
), ),
],
),
GoRoute( GoRoute(
path: AppRoutes.housekeeping, path: AppRoutes.parlor,
name: 'housekeeping', name: 'parlor',
builder: (context, state) => builder: (context, state) =>
const _PlaceholderPage(title: 'Housekeeping'), const _PlaceholderPage(title: 'Parlor'),
), ),
GoRoute( GoRoute(
path: AppRoutes.settings, path: AppRoutes.settings,
+19 -23
View File
@@ -12,29 +12,27 @@ class AppScaffold extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// TODO: Replace AdaptiveScaffold with custom layout per UI_LAYOUT.md
// - Header with room tabs (not bottom/rail nav)
// - Chat dock on right side
return AdaptiveScaffold( return AdaptiveScaffold(
selectedIndex: _selectedIndex(context), selectedIndex: _selectedIndex(context),
onSelectedIndexChange: (index) => _onNavSelected(context, index), onSelectedIndexChange: (index) => _onNavSelected(context, index),
destinations: const [ destinations: const [
NavigationDestination( NavigationDestination(
icon: Icon(Icons.dashboard_outlined), icon: Icon(Icons.door_front_door_outlined),
selectedIcon: Icon(Icons.dashboard), selectedIcon: Icon(Icons.door_front_door),
label: 'Dashboard', label: 'Front Hall',
),
NavigationDestination(
icon: Icon(Icons.chat_outlined),
selectedIcon: Icon(Icons.chat),
label: 'Tatlock',
), ),
NavigationDestination( NavigationDestination(
icon: Icon(Icons.dns_outlined), icon: Icon(Icons.dns_outlined),
selectedIcon: Icon(Icons.dns), selectedIcon: Icon(Icons.dns),
label: 'Containers', label: 'Control Room',
), ),
NavigationDestination( NavigationDestination(
icon: Icon(Icons.home_outlined), icon: Icon(Icons.weekend_outlined),
selectedIcon: Icon(Icons.home), selectedIcon: Icon(Icons.weekend),
label: 'Housekeeping', label: 'Parlor',
), ),
NavigationDestination( NavigationDestination(
icon: Icon(Icons.settings_outlined), icon: Icon(Icons.settings_outlined),
@@ -51,21 +49,19 @@ class AppScaffold extends StatelessWidget {
int _selectedIndex(BuildContext context) { int _selectedIndex(BuildContext context) {
final location = GoRouterState.of(context).matchedLocation; final location = GoRouterState.of(context).matchedLocation;
if (location.startsWith(AppRoutes.containers)) return 2; if (location.startsWith(AppRoutes.controlRoom)) return 1;
if (location.startsWith(AppRoutes.chat)) return 1; if (location.startsWith(AppRoutes.parlor)) return 2;
if (location.startsWith(AppRoutes.housekeeping)) return 3; if (location.startsWith(AppRoutes.settings)) return 3;
if (location.startsWith(AppRoutes.settings)) return 4; return 0; // Front Hall
return 0; // Dashboard
} }
void _onNavSelected(BuildContext context, int index) { void _onNavSelected(BuildContext context, int index) {
final route = switch (index) { final route = switch (index) {
0 => AppRoutes.dashboard, 0 => AppRoutes.frontHall,
1 => AppRoutes.chat, 1 => AppRoutes.controlRoom,
2 => AppRoutes.containers, 2 => AppRoutes.parlor,
3 => AppRoutes.housekeeping, 3 => AppRoutes.settings,
4 => AppRoutes.settings, _ => AppRoutes.frontHall,
_ => AppRoutes.dashboard,
}; };
context.go(route); context.go(route);
} }