- 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>
699 lines
23 KiB
Markdown
699 lines
23 KiB
Markdown
# Tatlock UI - Comprehensive Implementation Plan
|
|
|
|
> **Session Continuity Note**: This document is designed to be self-contained. If starting a new session, read this entire document to understand the project scope and decisions made.
|
|
|
|
## Project Summary
|
|
|
|
**Tatlock UI** is a Flutter application that will serve as the unified homelab dashboard for the Tower of Joy infrastructure, replacing Organizr at `home.schweitz.net`.
|
|
|
|
### Key Facts
|
|
- **Project folder**: `/mnt/media/Projects/tatlock-ui` (renamed from tower-ui)
|
|
- **Git remote**: `ssh://git@git.schweitz.net:2222/jpmschweitzer/tatlock-ui.git` (already created)
|
|
- **Platforms**: web (primary), android, macos, linux, windows, ios
|
|
- **Primary backend**: Core API (port 8083) + Tatlock API (port 8000)
|
|
- **Authentication**: Authentik SSO via OIDC
|
|
- **Web deployment**: Docker container on port 8092, proxied via NPM at `home.schweitz.net`
|
|
|
|
### What It Replaces
|
|
- **Organizr** (current dashboard at home.schweitz.net) - See `/mnt/media/Projects/tower-ui/uploads/portainer-ui.png` for current UI
|
|
- **Eventually Open WebUI** - Custom LLM chat interface for Tatlock agents
|
|
|
|
---
|
|
|
|
## Architecture Decisions (Final)
|
|
|
|
### 1. Dogmatic Clean Architecture
|
|
**Decision**: Use strict Clean Architecture with documented requirements for consistency.
|
|
|
|
```
|
|
lib/features/{feature}/
|
|
├── presentation/ # UI Layer
|
|
│ ├── pages/ # Full screen widgets
|
|
│ ├── widgets/ # Feature-specific widgets
|
|
│ └── providers/ # Riverpod providers/controllers
|
|
├── domain/ # Business Logic Layer
|
|
│ ├── entities/ # Business objects (immutable)
|
|
│ ├── repositories/ # Abstract repository interfaces
|
|
│ └── usecases/ # Single-purpose business operations
|
|
└── data/ # Data Layer
|
|
├── models/ # JSON serializable DTOs
|
|
├── datasources/ # API clients, local storage
|
|
└── repositories/ # Repository implementations
|
|
```
|
|
|
|
**Rules**:
|
|
- Domain layer has NO dependencies on Flutter or external packages
|
|
- Data layer implements domain interfaces
|
|
- Presentation layer only depends on domain layer
|
|
- All cross-layer communication via dependency injection (Riverpod)
|
|
|
|
### 2. DataGrid Component System
|
|
**Decision**: Adapt the fframe ListGrid pattern for consistent table UIs across all features.
|
|
|
|
Based on: `https://github.com/postmeridiem/fframe/blob/main/fframe/lib/screens/listgrid_screen/`
|
|
|
|
Key components:
|
|
- `DataGridConfig<T>` - Declarative configuration
|
|
- `DataGridColumn<T>` - Column definitions with builders
|
|
- `DataGridAction<T>` - Per-row actions
|
|
- `DataGridBulkAction<T>` - Multi-select actions
|
|
- `DataGridSource<T>` - Abstract data source (Core API adapter)
|
|
- `DataGridNotifier<T>` - Riverpod state management
|
|
|
|
### 3. Theming
|
|
**Decision**: System preference + manual override, using Material3 colorScheme exclusively.
|
|
|
|
- Follow system dark/light mode by default
|
|
- Allow manual override that persists to SharedPreferences
|
|
- Never use raw colors in widgets - always `Theme.of(context).colorScheme.*`
|
|
- Use automatic theme generator (flex_color_scheme or similar)
|
|
|
|
### 4. State Management
|
|
**Decision**: Riverpod 2.x with code generation.
|
|
|
|
Packages: `flutter_riverpod`, `riverpod_annotation`, `riverpod_generator`
|
|
|
|
---
|
|
|
|
## Implementation Phases
|
|
|
|
### Phase 0: Documentation
|
|
**Goal**: Establish project documentation before any code.
|
|
|
|
**Files to create**:
|
|
```
|
|
tatlock-ui/
|
|
├── README.md # Project overview, setup instructions
|
|
├── AGENTS.md # LLM agent instructions (copy pattern from Tatlock)
|
|
├── CHANGELOG.md # Version history (start with 0.0.1)
|
|
├── docs/
|
|
│ ├── ARCHITECTURE.md # Clean Architecture rules and patterns
|
|
│ ├── API_INTEGRATION.md # Core API and Tatlock API endpoints
|
|
│ ├── DEPLOYMENT.md # Docker, NPM, Portainer setup
|
|
│ ├── DATAGRID.md # DataGrid component API specification
|
|
│ ├── THEMING.md # Theme system documentation
|
|
│ └── UI_LAYOUT.md # Wireframes, responsive breakpoints, room navigation
|
|
```
|
|
|
|
**Content for ARCHITECTURE.md** - Document:
|
|
- Clean Architecture layer rules
|
|
- File naming conventions
|
|
- Dependency injection patterns
|
|
- Testing requirements per layer
|
|
|
|
**Content for API_INTEGRATION.md** - Document:
|
|
- Core API endpoints (from `/mnt/media/Projects/portainer-core/CONTAINERS.md`)
|
|
- Tatlock API endpoints (chat completions, responses API)
|
|
- Authentication flow with Authentik
|
|
- Error handling patterns
|
|
|
|
**Content for DATAGRID.md** - Document:
|
|
- Full DataGrid API spec (see below)
|
|
- Usage examples for each feature
|
|
- Customization patterns
|
|
|
|
### Phase 1: Foundation
|
|
**Goal**: Project setup, core infrastructure, basic shell.
|
|
|
|
**Tasks**:
|
|
1. Initialize Flutter project with all platforms
|
|
2. Initialize git and push to Gitea
|
|
3. Set up project structure (Clean Architecture folders)
|
|
4. Configure dependencies (pubspec.yaml)
|
|
5. Implement core infrastructure:
|
|
- `lib/core/api/api_client.dart` - Dio HTTP client
|
|
- `lib/core/api/api_interceptors.dart` - Auth, logging, error handling
|
|
- `lib/core/auth/auth_service.dart` - Authentik OIDC
|
|
- `lib/core/auth/auth_provider.dart` - Riverpod auth state
|
|
- `lib/core/config/app_config.dart` - Environment configuration
|
|
- `lib/core/config/service_locator.dart` - Dependency setup
|
|
- `lib/core/theme/app_theme.dart` - Material3 theme
|
|
- `lib/core/theme/theme_provider.dart` - Theme state (system + override)
|
|
6. Create app shell:
|
|
- `lib/app.dart` - MaterialApp.router setup
|
|
- `lib/routing/app_router.dart` - go_router configuration
|
|
- `lib/shared/layouts/app_scaffold.dart` - Main layout with sidebar
|
|
- `lib/shared/layouts/adaptive_navigation.dart` - Responsive nav
|
|
7. Set up CI/CD:
|
|
- `.gitea/workflows/test.yml`
|
|
- `.gitea/workflows/build-web.yml`
|
|
- `Dockerfile`
|
|
|
|
### Phase 2: DataGrid Component
|
|
**Goal**: Build the reusable DataGrid system before features.
|
|
|
|
**Files to create**:
|
|
```
|
|
lib/shared/components/data_grid/
|
|
├── data_grid.dart # Main widget
|
|
├── data_grid_config.dart # Configuration classes
|
|
├── data_grid_column.dart # Column definition
|
|
├── data_grid_action.dart # Action definitions
|
|
├── data_grid_source.dart # Abstract data source
|
|
├── data_grid_provider.dart # Riverpod state
|
|
├── data_grid_state.dart # Freezed state class
|
|
├── widgets/
|
|
│ ├── data_grid_header.dart # Header row with sort indicators
|
|
│ ├── data_grid_row.dart # Data row
|
|
│ ├── data_grid_cell.dart # Cell wrapper
|
|
│ ├── data_grid_checkbox.dart # Selection checkbox
|
|
│ ├── data_grid_actions_menu.dart # Row actions popup
|
|
│ ├── data_grid_bulk_actions.dart # Bulk action bar
|
|
│ ├── data_grid_search_bar.dart # Search input
|
|
│ ├── data_grid_footer.dart # Footer with count/pagination
|
|
│ └── data_grid_empty_state.dart # Empty state display
|
|
└── adapters/
|
|
└── core_api_source.dart # Core API data source adapter
|
|
```
|
|
|
|
### Phase 3: Front Hall (Organizr Replacement)
|
|
**Goal**: Replace Organizr homepage with Tatlock UI dashboard.
|
|
|
|
**Features to match** (from portainer-ui.png):
|
|
- System metrics gauges (CPU, Memory, Disk, Containers)
|
|
- Service quick links grid
|
|
- Recent activity timeline
|
|
- Chat dock expanded by default
|
|
|
|
**Files to create**:
|
|
```
|
|
lib/features/front_hall/
|
|
├── presentation/
|
|
│ ├── pages/front_hall_page.dart
|
|
│ └── widgets/
|
|
│ ├── stat_card.dart
|
|
│ ├── service_card.dart
|
|
│ ├── service_grid.dart
|
|
│ └── activity_timeline.dart
|
|
├── domain/
|
|
│ ├── entities/system_metrics.dart
|
|
│ └── repositories/front_hall_repository.dart
|
|
└── data/
|
|
├── datasources/core_api_front_hall_source.dart
|
|
└── repositories/front_hall_repository_impl.dart
|
|
```
|
|
|
|
**Core API endpoints used**:
|
|
- `GET /infrastructure/resources/system` - System metrics
|
|
- `GET /infrastructure/widget-data` - Services + groups
|
|
- `GET /health` - Overall health status
|
|
|
|
### Phase 4: Control Room - Container Management
|
|
**Goal**: Full container CRUD and monitoring.
|
|
|
|
**Files to create**:
|
|
```
|
|
lib/features/control_room/
|
|
├── shared/ # Shared within Control Room
|
|
│ └── widgets/
|
|
│ └── resource_chart.dart
|
|
├── containers/
|
|
│ ├── presentation/
|
|
│ │ ├── pages/
|
|
│ │ │ ├── containers_list_page.dart
|
|
│ │ │ └── container_detail_page.dart
|
|
│ │ ├── widgets/
|
|
│ │ │ ├── container_status_badge.dart
|
|
│ │ │ └── container_logs_viewer.dart
|
|
│ │ └── providers/
|
|
│ │ └── containers_provider.dart
|
|
│ ├── domain/
|
|
│ │ ├── entities/container.dart
|
|
│ │ ├── repositories/container_repository.dart
|
|
│ │ └── usecases/
|
|
│ │ ├── get_containers.dart
|
|
│ │ ├── start_container.dart
|
|
│ │ ├── stop_container.dart
|
|
│ │ ├── restart_container.dart
|
|
│ │ └── get_container_logs.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**:
|
|
- `GET /infrastructure/containers`
|
|
- `GET /infrastructure/containers/{id}`
|
|
- `GET /infrastructure/containers/{id}/logs`
|
|
- `POST /infrastructure/containers/{id}/{action}`
|
|
- `GET /infrastructure/resources/containers`
|
|
|
|
### Phase 5: Chat Dock (Omnipresent)
|
|
**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**:
|
|
```
|
|
lib/core/api/
|
|
└── sse_client.dart # Platform-aware SSE streaming
|
|
|
|
lib/chat/ # Top-level, not under features/
|
|
├── presentation/
|
|
│ ├── chat_dock.dart # Collapsible dock widget
|
|
│ ├── chat_fullscreen.dart # Fullscreen overlay (future)
|
|
│ └── widgets/
|
|
│ ├── message_list.dart
|
|
│ ├── message_bubble.dart
|
|
│ ├── reasoning_block.dart # Collapsible think blocks
|
|
│ ├── streaming_text.dart
|
|
│ ├── chat_input.dart
|
|
│ ├── model_selector.dart
|
|
│ └── agent_indicator.dart # Shows which agent is responding
|
|
├── domain/
|
|
│ ├── entities/
|
|
│ │ ├── message.dart
|
|
│ │ ├── conversation.dart
|
|
│ │ └── chat_completion.dart
|
|
│ └── repositories/chat_repository.dart
|
|
└── data/
|
|
├── models/
|
|
│ ├── chat_completion_request.dart
|
|
│ ├── chat_completion_response.dart
|
|
│ └── chat_completion_chunk.dart
|
|
├── datasources/tatlock_api_datasource.dart
|
|
└── repositories/chat_repository_impl.dart
|
|
```
|
|
|
|
**Tatlock API endpoints used**:
|
|
- `POST /v1/chat/completions` (with `stream: true`)
|
|
- `GET /v1/models`
|
|
|
|
**SSE Implementation Notes**:
|
|
- Use platform-conditional imports
|
|
- Web: XMLHttpRequest or fetch API
|
|
- Mobile/Desktop: eventsource or http package with stream parsing
|
|
- Handle `reasoning_content` field for think blocks (DeepSeek R1 format)
|
|
|
|
### Phase 6: Parlor (Home Automation)
|
|
**Goal**: Control Home Assistant via Core API.
|
|
|
|
**Files to create**:
|
|
```
|
|
lib/features/parlor/
|
|
├── presentation/
|
|
│ ├── pages/
|
|
│ │ ├── parlor_page.dart
|
|
│ │ └── area_detail_page.dart
|
|
│ └── widgets/
|
|
│ ├── device_tile.dart
|
|
│ ├── device_control_sheet.dart
|
|
│ ├── scene_card.dart
|
|
│ └── area_grid.dart
|
|
├── domain/
|
|
│ ├── entities/
|
|
│ │ ├── device.dart
|
|
│ │ ├── area.dart
|
|
│ │ └── scene.dart
|
|
│ └── repositories/parlor_repository.dart
|
|
└── data/
|
|
├── models/
|
|
│ ├── device_model.dart
|
|
│ └── device_control_request.dart
|
|
├── datasources/parlor_datasource.dart
|
|
└── repositories/parlor_repository_impl.dart
|
|
```
|
|
|
|
**Core API endpoints used**:
|
|
- `GET /housekeeping/devices`
|
|
- `GET /housekeeping/areas`
|
|
- `GET /housekeeping/scenes`
|
|
- `POST /housekeeping/devices/{entity_id}/control`
|
|
- `POST /housekeeping/scenes/{scene_id}/activate`
|
|
|
|
### Phase 7: Polish & Deployment
|
|
**Goal**: Production-ready release.
|
|
|
|
**Tasks**:
|
|
1. Flutter web optimization (deferred loading, tree shaking)
|
|
2. Desktop platform testing and fixes
|
|
3. Offline resilience patterns
|
|
4. Error handling and user feedback
|
|
5. Performance profiling
|
|
6. Docker build and Portainer stack deployment
|
|
7. NPM proxy configuration update (home.schweitz.net → tatlock-ui)
|
|
|
|
---
|
|
|
|
## DataGrid API Specification
|
|
|
|
### Configuration Classes
|
|
|
|
```dart
|
|
/// Main configuration for a data grid
|
|
class DataGridConfig<T> {
|
|
const DataGridConfig({
|
|
required this.columns,
|
|
this.actions = const [],
|
|
this.bulkActions = const [],
|
|
this.rowsSelectable = false,
|
|
this.showHeader = true,
|
|
this.showFooter = true,
|
|
this.enableSearch = false,
|
|
this.searchableColumns = const [],
|
|
this.defaultSortColumn,
|
|
this.defaultSortDescending = false,
|
|
this.emptyStateBuilder,
|
|
this.loadingBuilder,
|
|
this.errorBuilder,
|
|
this.onRowTap,
|
|
this.cellPadding = const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
this.headerHeight = 48.0,
|
|
this.rowHeight,
|
|
this.dataMode = const DataGridDataMode.all(),
|
|
});
|
|
|
|
final List<DataGridColumn<T>> columns;
|
|
final List<DataGridAction<T>> actions;
|
|
final List<DataGridBulkAction<T>> bulkActions;
|
|
final bool rowsSelectable;
|
|
final bool showHeader;
|
|
final bool showFooter;
|
|
final bool enableSearch;
|
|
final List<int> searchableColumns;
|
|
final int? defaultSortColumn;
|
|
final bool defaultSortDescending;
|
|
final Widget Function(BuildContext)? emptyStateBuilder;
|
|
final Widget Function(BuildContext)? loadingBuilder;
|
|
final Widget Function(BuildContext, Object error)? errorBuilder;
|
|
final void Function(T item)? onRowTap;
|
|
final EdgeInsetsGeometry cellPadding;
|
|
final double headerHeight;
|
|
final double? rowHeight;
|
|
final DataGridDataMode dataMode;
|
|
}
|
|
|
|
/// Column definition
|
|
class DataGridColumn<T> {
|
|
const DataGridColumn({
|
|
required this.header,
|
|
required this.valueBuilder,
|
|
this.cellBuilder,
|
|
this.cellControlsBuilder,
|
|
this.width = const DataGridColumnWidth.flex(1),
|
|
this.alignment = DataGridColumnAlignment.start,
|
|
this.sortable = false,
|
|
this.sortField,
|
|
this.searchable = false,
|
|
this.visible = true,
|
|
this.tooltip,
|
|
});
|
|
|
|
final String header;
|
|
final String Function(T item) valueBuilder;
|
|
final Widget Function(BuildContext, T item)? cellBuilder;
|
|
final Widget Function(BuildContext, T item)? cellControlsBuilder;
|
|
final DataGridColumnWidth width;
|
|
final DataGridColumnAlignment alignment;
|
|
final bool sortable;
|
|
final String? sortField;
|
|
final bool searchable;
|
|
final bool visible;
|
|
final String Function(T item)? tooltip;
|
|
}
|
|
|
|
/// Column width - sealed class with variants
|
|
sealed class DataGridColumnWidth {
|
|
const DataGridColumnWidth._();
|
|
const factory DataGridColumnWidth.fixed(double width) = _FixedWidth;
|
|
const factory DataGridColumnWidth.flex(int flex) = _FlexWidth;
|
|
const factory DataGridColumnWidth.fraction(double fraction) = _FractionWidth;
|
|
}
|
|
|
|
/// Per-row action
|
|
class DataGridAction<T> {
|
|
const DataGridAction({
|
|
required this.icon,
|
|
required this.label,
|
|
required this.onTap,
|
|
this.showWhen,
|
|
this.destructive = false,
|
|
this.requiresConfirmation = false,
|
|
this.confirmationMessage,
|
|
});
|
|
|
|
final IconData icon;
|
|
final String label;
|
|
final Future<void> Function(T item) onTap;
|
|
final bool Function(T item)? showWhen;
|
|
final bool destructive;
|
|
final bool requiresConfirmation;
|
|
final String? confirmationMessage;
|
|
}
|
|
|
|
/// Bulk action on selected rows
|
|
class DataGridBulkAction<T> {
|
|
const DataGridBulkAction({
|
|
required this.icon,
|
|
required this.label,
|
|
required this.onTap,
|
|
this.minSelected = 1,
|
|
this.maxSelected,
|
|
this.destructive = false,
|
|
this.requiresConfirmation = false,
|
|
});
|
|
|
|
final IconData icon;
|
|
final String label;
|
|
final Future<void> Function(List<T> items) onTap;
|
|
final int minSelected;
|
|
final int? maxSelected;
|
|
final bool destructive;
|
|
final bool requiresConfirmation;
|
|
}
|
|
|
|
/// Data loading mode
|
|
sealed class DataGridDataMode {
|
|
const DataGridDataMode._();
|
|
const factory DataGridDataMode.all() = _AllDataMode;
|
|
const factory DataGridDataMode.paginated({int pageSize}) = _PaginatedDataMode;
|
|
const factory DataGridDataMode.infinite({int initialLoad, int loadMoreThreshold}) = _InfiniteDataMode;
|
|
}
|
|
```
|
|
|
|
### Data Source
|
|
|
|
```dart
|
|
/// Abstract data source
|
|
abstract class DataGridSource<T> {
|
|
Future<DataGridResult<T>> fetch({
|
|
String? searchQuery,
|
|
String? sortField,
|
|
bool sortDescending = false,
|
|
int? offset,
|
|
int? limit,
|
|
});
|
|
|
|
Future<int> count({String? searchQuery});
|
|
}
|
|
|
|
/// Result wrapper
|
|
class DataGridResult<T> {
|
|
const DataGridResult({
|
|
required this.items,
|
|
required this.totalCount,
|
|
this.hasMore = false,
|
|
});
|
|
|
|
final List<T> items;
|
|
final int totalCount;
|
|
final bool hasMore;
|
|
}
|
|
|
|
/// Core API adapter
|
|
class CoreApiDataSource<T> extends DataGridSource<T> {
|
|
CoreApiDataSource({
|
|
required this.endpoint,
|
|
required this.fromJson,
|
|
this.searchParam = 'search',
|
|
this.sortParam = 'sort',
|
|
this.orderParam = 'order',
|
|
});
|
|
|
|
final String endpoint;
|
|
final T Function(Map<String, dynamic>) fromJson;
|
|
final String searchParam;
|
|
final String sortParam;
|
|
final String orderParam;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Web Deployment Architecture
|
|
|
|
```
|
|
Internet → NPM (home.schweitz.net:443) → tatlock-ui container (port 8092) → Flutter web
|
|
```
|
|
|
|
**Dockerfile**:
|
|
```dockerfile
|
|
FROM ghcr.io/cirruslabs/flutter:3.27.0 AS build
|
|
WORKDIR /app
|
|
COPY pubspec.* ./
|
|
RUN flutter pub get
|
|
COPY . .
|
|
RUN flutter build web --release
|
|
|
|
FROM nginx:alpine
|
|
COPY --from=build /app/build/web /usr/share/nginx/html
|
|
EXPOSE 80
|
|
```
|
|
|
|
**Portainer Stack (docker-compose.yml)**:
|
|
```yaml
|
|
version: '3.8'
|
|
services:
|
|
tatlock-ui:
|
|
image: git.schweitz.net/jpmschweitzer/tatlock-ui:latest
|
|
container_name: tatlock-ui
|
|
restart: unless-stopped
|
|
ports:
|
|
- "8092:80"
|
|
networks:
|
|
- docker-dataplane
|
|
|
|
networks:
|
|
docker-dataplane:
|
|
external: true
|
|
```
|
|
|
|
**NPM Proxy Configuration**:
|
|
- Domain: `home.schweitz.net`
|
|
- Forward to: `localhost:8092`
|
|
- SSL: Let's Encrypt, Force SSL
|
|
|
|
---
|
|
|
|
## Key Dependencies (pubspec.yaml)
|
|
|
|
```yaml
|
|
dependencies:
|
|
flutter:
|
|
sdk: flutter
|
|
|
|
# State Management
|
|
flutter_riverpod: ^2.5.0
|
|
riverpod_annotation: ^2.3.0
|
|
hooks_riverpod: ^2.5.0
|
|
flutter_hooks: ^0.20.0
|
|
|
|
# Code Generation
|
|
freezed_annotation: ^2.4.0
|
|
json_annotation: ^4.8.0
|
|
|
|
# Networking
|
|
dio: ^5.4.0
|
|
retrofit: ^4.1.0
|
|
|
|
# Authentication
|
|
flutter_appauth: ^6.0.0
|
|
flutter_secure_storage: ^9.0.0
|
|
|
|
# Routing
|
|
go_router: ^13.0.0
|
|
|
|
# UI
|
|
flex_color_scheme: ^7.3.0
|
|
flutter_adaptive_scaffold: ^0.1.0
|
|
flutter_markdown: ^0.6.0
|
|
flutter_highlight: ^0.7.0
|
|
fl_chart: ^0.65.0
|
|
|
|
dev_dependencies:
|
|
build_runner: ^2.4.0
|
|
freezed: ^2.4.0
|
|
json_serializable: ^6.7.0
|
|
riverpod_generator: ^2.4.0
|
|
retrofit_generator: ^8.1.0
|
|
mocktail: ^1.0.0
|
|
```
|
|
|
|
---
|
|
|
|
## Reference Documents
|
|
|
|
- **UI Layout Spec**: `docs/UI_LAYOUT.md` - Wireframes, breakpoints, responsive behavior
|
|
- **Infrastructure**: `/mnt/media/Projects/portainer-core/CONTAINERS.md`
|
|
- **Tatlock Philosophy**: `/mnt/media/Projects/tatlock/PHILOSOPHY.md`
|
|
- **Core API**: `http://192.168.86.149:8083/docs`
|
|
- **Tatlock API**: `http://192.168.86.149:8000/docs`
|
|
- **fframe ListGrid**: `https://github.com/postmeridiem/fframe/tree/main/fframe/lib/screens/listgrid_screen`
|
|
- **Current Organizr UI**: `/mnt/media/Projects/tower-ui/uploads/portainer-ui.png`
|
|
- **Tatlock Logo**: `/mnt/media/Projects/tower-ui/uploads/logo-tatlock.png`
|
|
|
|
---
|
|
|
|
## 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.
|
|
|
|
```
|
|
Header Tabs:
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Front Hall │ Control Room │ Parlor │ Library │ │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
Room Contents:
|
|
├── Front Hall (Dashboard)
|
|
│ ├── Stat cards (CPU, Memory, Disk, Containers)
|
|
│ ├── Quick access service grid (external links)
|
|
│ ├── Recent activity timeline
|
|
│ └── Chat dock EXPANDED by default
|
|
│
|
|
├── Control Room (Infrastructure)
|
|
│ ├── Context sidebar: Containers, Stacks, Networks, Volumes, Images
|
|
│ ├── External links: Netdata, Portainer, NPM
|
|
│ ├── DataGrid views for each section
|
|
│ └── 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
|
|
```
|
|
|
|
---
|
|
|
|
## Authentik Configuration
|
|
|
|
Create application in Authentik admin:
|
|
- **Name**: Tatlock UI
|
|
- **Slug**: `tatlock-ui`
|
|
- **Provider**: OAuth2/OIDC
|
|
- **Client Type**: Public
|
|
- **Redirect URIs**:
|
|
- `https://home.schweitz.net/callback`
|
|
- `net.schweitz.tatlock://callback`
|
|
- `http://localhost:*/callback`
|
|
|
|
---
|
|
|
|
## Next Steps After Session Resume
|
|
|
|
1. **Verify folder rename**: Confirm `/mnt/media/Projects/tatlock-ui` exists
|
|
2. **Start Phase 0**: Create documentation files
|
|
3. **Initialize Flutter**: `flutter create . --project-name tatlock_ui --org net.schweitz --platforms=web,android,ios,macos,linux,windows`
|
|
4. **Initialize Git**: `git init && git remote add origin ssh://git@git.schweitz.net:2222/jpmschweitzer/tatlock-ui.git`
|
|
5. **Continue with Phase 1**: Core infrastructure setup
|
|
|
|
---
|
|
|
|
*Last updated: 2024-12-30*
|
|
*Plan version: 2.0*
|