chore: release v1.4.0
- Decentralized Room Registry pattern - Each room registers itself with central registry - Dynamic navigation tabs and settings dropdown - New Media Room and Parlor feature folders - Permission-based room filtering support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
40ba6a869d
commit
c3f6d27a52
+162
-9
@@ -28,14 +28,20 @@ lib/
|
||||
│ └── layouts/ # App scaffold, navigation
|
||||
├── features/ # Feature modules (rooms)
|
||||
│ ├── front_hall/ # Dashboard - estate overview
|
||||
│ │ └── router.dart # Room registration
|
||||
│ ├── control_room/ # Infrastructure
|
||||
│ │ ├── router.dart # Room registration
|
||||
│ │ ├── containers/ # Container management
|
||||
│ │ ├── stacks/ # Stack management
|
||||
│ │ ├── networks/ # Network management
|
||||
│ │ └── volumes/ # Volume management
|
||||
│ ├── parlor/ # Housekeeping - home automation
|
||||
│ ├── library/ # Knowledge management (future)
|
||||
│ └── study/ # Secretarial tasks (future)
|
||||
│ │ └── npm/ # Proxy hosts management
|
||||
│ ├── security/ # User & access management
|
||||
│ │ ├── router.dart # Room registration
|
||||
│ │ ├── users/ # User management
|
||||
│ │ └── groups/ # Group management
|
||||
│ ├── parlor/ # AI chat & automation hub
|
||||
│ │ └── router.dart # Room registration
|
||||
│ ├── media_room/ # Media management (future)
|
||||
│ │ └── router.dart # Room registration
|
||||
│ └── settings/ # User preferences
|
||||
└── chat/ # Tatlock chat - omnipresent, NOT a room
|
||||
```
|
||||
|
||||
@@ -45,13 +51,16 @@ lib/
|
||||
|---------|-----------|---------|
|
||||
| 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) |
|
||||
| Security | `features/security/` | User & access management |
|
||||
| Parlor | `features/parlor/` | AI chat & automation hub |
|
||||
| Media Room | `features/media_room/` | Media management (future) |
|
||||
| *(non-room)* | `features/settings/` | User preferences |
|
||||
| *(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.
|
||||
|
||||
Each room has a `router.dart` file that registers the room with the central registry. See [Room Registry Pattern](#room-registry-pattern) for details.
|
||||
|
||||
## Feature Structure
|
||||
|
||||
Each feature follows a three-layer architecture:
|
||||
@@ -570,3 +579,147 @@ Semantics(
|
||||
3. **Web Enabled** - Semantics tree exposed via `SemanticsBinding.instance.ensureSemantics()` in `main.dart`
|
||||
|
||||
For complete documentation on semantic patterns, automation queries, and best practices, see **[TESTING.md](./TESTING.md)**.
|
||||
|
||||
## Room Registry Pattern
|
||||
|
||||
The application uses a **decentralized room registry** pattern for navigation. Each feature/room registers itself with the central registry, providing:
|
||||
|
||||
- **Decoupled navigation** - Rooms define their own routes, icons, and metadata
|
||||
- **Permission-based filtering** - Rooms can specify required permissions
|
||||
- **Dynamic UI** - Settings dropdowns and tab bars build from registry
|
||||
- **Single source of truth** - All room metadata in one place per room
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
lib/routing/room_registry.dart # Central registry class
|
||||
lib/features/{room}/router.dart # Per-room registration
|
||||
```
|
||||
|
||||
### Room Definition
|
||||
|
||||
Each room's `router.dart` exports a `RoomDefinition` and register function:
|
||||
|
||||
```dart
|
||||
// lib/features/control_room/router.dart
|
||||
import 'package:tatlock_ui/routing/room_registry.dart';
|
||||
|
||||
/// Room definition with all metadata.
|
||||
final controlRoomRoom = RoomDefinition(
|
||||
id: 'control-room', // Preference value, URL segment
|
||||
label: 'Control Room', // Display name
|
||||
icon: Icons.dns_outlined, // Unselected icon
|
||||
selectedIcon: Icons.dns, // Selected icon
|
||||
defaultRoute: '/control-room/containers', // Landing route
|
||||
routes: controlRoomRoutes, // Function returning List<RouteBase>
|
||||
requiredPermissions: [], // Empty = accessible to all
|
||||
);
|
||||
|
||||
/// Register with the central registry.
|
||||
void registerControlRoom() {
|
||||
roomRegistry.register(controlRoomRoom);
|
||||
}
|
||||
|
||||
/// Routes for go_router.
|
||||
List<RouteBase> controlRoomRoutes() {
|
||||
return [
|
||||
GoRoute(path: '/control-room', ...),
|
||||
// Sub-routes...
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### Registration Order
|
||||
|
||||
Rooms are registered in `app_router.dart` in display order:
|
||||
|
||||
```dart
|
||||
void _initializeRoomRegistry() {
|
||||
if (roomRegistry.all.isNotEmpty) return; // Skip if initialized
|
||||
|
||||
// Registration order = tab order
|
||||
registerFrontHall();
|
||||
registerControlRoom();
|
||||
registerSecurity();
|
||||
registerParlor();
|
||||
registerMediaRoom();
|
||||
}
|
||||
```
|
||||
|
||||
### Using the Registry
|
||||
|
||||
**Navigation tabs** (`top_header_bar.dart`):
|
||||
```dart
|
||||
List<RoomDefinition> get _rooms => roomRegistry.all;
|
||||
|
||||
// Build tab for each room
|
||||
for (final room in _rooms) {
|
||||
IconButton(
|
||||
icon: Icon(isSelected ? room.selectedIcon : room.icon),
|
||||
onPressed: () => onRoomSelected(index),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Settings dropdown**:
|
||||
```dart
|
||||
DropdownButton<String>(
|
||||
items: roomRegistry.all
|
||||
.map((room) => DropdownMenuItem(
|
||||
value: room.id,
|
||||
child: Text(room.label),
|
||||
))
|
||||
.toList(),
|
||||
);
|
||||
```
|
||||
|
||||
**Router** - All routes from registry:
|
||||
```dart
|
||||
ShellRoute(
|
||||
routes: [
|
||||
...roomRegistry.allRoutes(),
|
||||
// Plus non-room routes like /settings
|
||||
],
|
||||
);
|
||||
```
|
||||
|
||||
**Route matching**:
|
||||
```dart
|
||||
int _selectedIndex(BuildContext context) {
|
||||
final location = GoRouterState.of(context).matchedLocation;
|
||||
return roomRegistry.indexOfRoute(location);
|
||||
}
|
||||
```
|
||||
|
||||
### Permission Filtering
|
||||
|
||||
Rooms can specify required permissions:
|
||||
|
||||
```dart
|
||||
final adminRoom = RoomDefinition(
|
||||
id: 'admin',
|
||||
requiredPermissions: ['admin:access'],
|
||||
// ...
|
||||
);
|
||||
|
||||
// Filter by user permissions
|
||||
final accessibleRooms = roomRegistry.accessibleTo(userPermissions);
|
||||
```
|
||||
|
||||
### Adding a New Room
|
||||
|
||||
1. Create feature folder: `lib/features/{room_name}/`
|
||||
2. Create `router.dart` with `RoomDefinition` and register function
|
||||
3. Create placeholder page in `presentation/pages/{room_name}_page.dart`
|
||||
4. Add `register{RoomName}()` call to `_initializeRoomRegistry()` in `app_router.dart`
|
||||
5. Import the router in `app_router.dart`
|
||||
|
||||
### Current Rooms
|
||||
|
||||
| Room | ID | Default Route |
|
||||
|------|-----|---------------|
|
||||
| Front Hall | `front-hall` | `/front-hall` |
|
||||
| Control Room | `control-room` | `/control-room/containers` |
|
||||
| Security | `security` | `/security/users` |
|
||||
| Parlor | `parlor` | `/parlor` |
|
||||
| Media Room | `media-room` | `/media-room` |
|
||||
|
||||
Reference in New Issue
Block a user