@tduncan / Cortana / commits / dbc295c

chore: stop tracking plans/ directory

abdulmominsakib committed Apr 15, 2026 at 21:41 UTC dbc295cea0822b61ffeb7d7fca9f6cad4fa24b1d
12 files changed -4095
plans/00_project_setup.md deleted
-417
@@ -1,417 +0,0 @@
1 -# Feature 00 — Project Setup & Scaffolding
2 -
3 -> **Phase:** 0 — Setup
4 -> **Status:** Pending
5 -> **Est. Time:** 1 week
6 -
7 ----
8 -
9 -## Goal
10 -
11 -Transform the default Flutter counter app into a production-ready project shell with:
12 -- Feature-based folder structure
13 -- ShadCN UI theme integration
14 -- Riverpod provider scope
15 -- Hive CE local storage initialization
16 -- App-wide routing (GoRouter)
17 -- Base app shell (navigation scaffold)
18 -
19 ----
20 -
21 -## Dependencies Used
22 -
23 -| Package | Purpose |
24 -| -------------------- | -------------------------------- |
25 -| `flutter_riverpod` | State management (ProviderScope) |
26 -| `hive_ce` | Local encrypted storage |
27 -| `hive_ce_flutter` | Hive Flutter initialization |
28 -| `shadcn_ui` | Component library & theming |
29 -| `path_provider` | App directory paths |
30 -| `go_router` | Declarative routing |
31 -
32 ----
33 -
34 -## Folder Structure
35 -
36 -Create the full directory skeleton under `lib/`:
37 -
38 -```
39 -lib/
40 -├── main.dart # Entry point — init Hive, wrap with ProviderScope
41 -├── app.dart # MaterialApp / ShadCN App root with routing
42 -│
43 -├── core/ # SHARED, CROSS-CUTTING CONCERNS
44 -│ ├── constants/
45 -│ │ └── app_constants.dart
46 -│ ├── models/
47 -│ │ ├── server.dart
48 -│ │ ├── message.dart
49 -│ │ ├── conversation.dart
50 -│ │ ├── persona.dart
51 -│ │ ├── model_info.dart
52 -│ │ ├── chat_parameters.dart
53 -│ │ └── app_settings.dart
54 -│ ├── providers/
55 -│ │ └── app_providers.dart
56 -│ ├── repository/
57 -│ │ └── storage/
58 -│ │ ├── hive_initializer.dart
59 -│ │ ├── hive_keys.dart
60 -│ │ └── hive_boxes.dart
61 -│ ├── routes/
62 -│ │ └── app_router.dart
63 -│ ├── theme/
64 -│ │ ├── app_theme.dart
65 -│ │ ├── colors.dart
66 -│ │ └── typography.dart
67 -│ ├── components/
68 -│ │ ├── app_button.dart
69 -│ │ ├── app_card.dart
70 -│ │ └── app_text_field.dart
71 -│ └── utils/
72 -│ └── uuid.dart
73 -│
74 -├── features/ # FEATURE MODULES (vertical slices)
75 -│ ├── server_connection/
76 -│ │ ├── data/
77 -│ │ │ └── models/
78 -│ │ ├── providers/
79 -│ │ │ └── server_connection_providers.dart
80 -│ │ └── views/
81 -│ │ ├── server_list_page.dart
82 -│ │ ├── add_server_page.dart
83 -│ │ └── components/
84 -│ │ ├── server_card.dart
85 -│ │ ├── connection_status_indicator.dart
86 -│ │ └── server_type_selector.dart
87 -│ │
88 -│ ├── chat/
89 -│ │ ├── data/
90 -│ │ │ └── models/
91 -│ │ ├── providers/
92 -│ │ │ └── chat_providers.dart
93 -│ │ └── views/
94 -│ │ ├── chat_page.dart
95 -│ │ └── components/
96 -│ │ ├── chat_bubble.dart
97 -│ │ ├── chat_input_bar.dart
98 -│ │ ├── typing_indicator.dart
99 -│ │ ├── message_action_bar.dart
100 -│ │ └── code_block.dart
101 -│ │
102 -│ ├── model_management/
103 -│ │ ├── data/
104 -│ │ │ └── models/
105 -│ │ ├── providers/
106 -│ │ │ └── model_management_providers.dart
107 -│ │ └── views/
108 -│ │ ├── model_picker_sheet.dart
109 -│ │ └── components/
110 -│ │ ├── model_tile.dart
111 -│ │ └── model_metadata_chip.dart
112 -│ │
113 -│ ├── conversation/
114 -│ │ ├── data/
115 -│ │ │ └── models/
116 -│ │ ├── providers/
117 -│ │ │ └── conversation_providers.dart
118 -│ │ └── views/
119 -│ │ ├── conversation_drawer.dart
120 -│ │ └── components/
121 -│ │ ├── conversation_tile.dart
122 -│ │ ├── conversation_search_bar.dart
123 -│ │ └── date_section_header.dart
124 -│ │
125 -│ ├── persona/
126 -│ │ ├── data/
127 -│ │ │ └── models/
128 -│ │ ├── providers/
129 -│ │ │ └── persona_providers.dart
130 -│ │ └── views/
131 -│ │ ├── persona_list_page.dart
132 -│ │ ├── create_persona_page.dart
133 -│ │ └── components/
134 -│ │ ├── persona_card.dart
135 -│ │ └── persona_category_chips.dart
136 -│ │
137 -│ └── settings/
138 -│ ├── data/
139 -│ │ └── models/
140 -│ ├── providers/
141 -│ │ └── settings_providers.dart
142 -│ └── views/
143 -│ ├── settings_page.dart
144 -│ └── components/
145 -│ ├── settings_section.dart
146 -│ ├── settings_slider.dart
147 -│ ├── settings_toggle.dart
148 -│ ├── settings_dropdown.dart
149 -│ ├── theme_toggle.dart
150 -│ └── font_size_preview.dart
151 -```
152 -
153 ----
154 -
155 -## Implementation Tasks
156 -
157 -### 1. `main.dart` — Entry Point
158 -
159 -**Responsibilities:**
160 -1. Initialize Hive CE with encryption
161 -2. Open required Hive boxes (servers, conversations, personas, settings, messages)
162 -3. Wrap app with `ProviderScope` (Riverpod)
163 -4. Launch `App` widget from `app.dart`
164 -
165 -**Implementation details:**
166 -
167 -- Call `WidgetsFlutterBinding.ensureInitialized()` before any async work.
168 -- Initialize Hive via `HiveFlutter.init()` using `path_provider`'s app documents directory.
169 -- Generate or retrieve an encryption key stored in device secure storage (for encrypted Hive boxes).
170 -- Register all Hive type adapters for custom data models.
171 -- Open boxes: `serversBox`, `conversationsBox`, `personasBox`, `settingsBox`, `messagesBox`.
172 -- Wrap the `App()` widget with `ProviderScope()`.
173 -
174 -### 2. `app.dart` — App Root
175 -
176 -**Responsibilities:**
177 -1. Configure ShadCN theme (dark-first, with light mode support)
178 -2. Set up GoRouter routing
179 -3. Define the app shell (drawer-based navigation)
180 -
181 -**Routes to define:**
182 -
183 -| Route | Screen |
184 -| ---------------------- | ------------------------- |
185 -| `/` | ChatPage (home) |
186 -| `/conversations` | ConversationDrawer |
187 -| `/servers` | ServerListPage |
188 -| `/servers/add` | AddServerPage |
189 -| `/servers/edit/:id` | AddServerPage (edit mode) |
190 -| `/models` | ModelPickerSheet |
191 -| `/personas` | PersonaListPage |
192 -| `/personas/create` | CreatePersonaPage |
193 -| `/personas/edit/:id` | CreatePersonaPage (edit) |
194 -| `/settings` | SettingsPage |
195 -
196 -**Navigation approach:**
197 -- Use a **drawer / sidebar** for conversation list (like ChatGPT).
198 -- Primary view is always the Chat screen.
199 -- Settings, servers, personas accessible from drawer or app bar actions.
200 -
201 -### 3. ShadCN Theme Setup
202 -
203 -Create `core/theme/colors.dart`:
204 -
205 -```dart
206 -class AppColors {
207 - // --- Dark Theme (Default) ---
208 - static const Color darkBackground = Color(0xFF0A0A0A);
209 - static const Color darkSurface = Color(0xFF171717);
210 - static const Color darkBorder = Color(0xFF262626);
211 - static const Color darkPrimaryText = Color(0xFFFAFAFA);
212 - static const Color darkMutedText = Color(0xFFA1A1AA);
213 - static const Color darkAccent = Color(0xFF3B82F6);
214 -
215 - // --- Light Theme ---
216 - static const Color lightBackground = Color(0xFFFAFAFA);
217 - static const Color lightSurface = Color(0xFFFFFFFF);
218 - static const Color lightBorder = Color(0xFFE5E5E5);
219 - static const Color lightPrimaryText = Color(0xFF0A0A0A);
220 - static const Color lightMutedText = Color(0xFF71717A);
221 - static const Color lightAccent = Color(0xFF2563EB);
222 -}
223 -```
224 -
225 -Create `core/theme/app_theme.dart`:
226 -- **Dark theme (primary):** Background `#0A0A0A`, Surface `#171717`, Border `#262626`, Primary `#FAFAFA`, Muted `#A1A1AA`, Accent blue.
227 -- **Light theme (secondary):** Background `#FAFAFA`, Surface `#FFFFFF`, Border `#E5E5E5`, Primary `#0A0A0A`, Muted `#71717A`.
228 -- **Typography:** Use `Inter` font family.
229 -- **Corner radius:** Default `8px` for cards/buttons, `12px` for sheets/dialogs.
230 -- **Transitions:** Default 200ms ease-in-out.
231 -
232 -### 4. Riverpod Provider Structure
233 -
234 -Create `core/providers/app_providers.dart`:
235 -
236 -```dart
237 -// Theme provider
238 -final themeModeProvider = StateNotifierProvider<ThemeModeNotifier, ThemeMode>((ref) {
239 - return ThemeModeNotifier();
240 -});
241 -
242 -// Current active server
243 -final activeServerProvider = StateNotifierProvider<ActiveServerNotifier, Server?>((ref) {
244 - return ActiveServerNotifier();
245 -});
246 -
247 -// Current active conversation
248 -final activeConversationProvider = StateNotifierProvider<ActiveConversationNotifier, Conversation?>((ref) {
249 - return ActiveConversationNotifier();
250 -});
251 -
252 -// Hive boxes (initialized in main.dart, overridden here)
253 -final hiveBoxesProvider = Provider<HiveBoxes>((ref) {
254 - throw UnimplementedError('Must be overridden with actual HiveBoxes');
255 -});
256 -```
257 -
258 -### 5. Hive Storage Initialization
259 -
260 -Create `core/repository/storage/hive_keys.dart`:
261 -
262 -```dart
263 -class HiveKeys {
264 - static const String servers = 'servers';
265 - static const String messages = 'messages';
266 - static const String conversations = 'conversations';
267 - static const String personas = 'personas';
268 - static const String settings = 'settings';
269 -}
270 -```
271 -
272 -Create `core/repository/storage/hive_boxes.dart`:
273 -
274 -```dart
275 -class HiveBoxes {
276 - final Box<Server> servers;
277 - final Box<Message> messages;
278 - final Box<Conversation> conversations;
279 - final Box<Persona> personas;
280 - final Box<AppSettings> settings;
281 -
282 - const HiveBoxes({
283 - required this.servers,
284 - required this.messages,
285 - required this.conversations,
286 - required this.personas,
287 - required this.settings,
288 - });
289 -}
290 -```
291 -
292 -Create `core/repository/storage/hive_initializer.dart`:
293 -
294 -```dart
295 -class HiveInitializer {
296 - static Future<HiveBoxes> initialize() async {
297 - final appDir = await getApplicationDocumentsDirectory();
298 - await Hive.initFlutter(appDir.path);
299 -
300 - // Register adapters (Server, Message, Conversation, Persona, AppSettings)
301 - // ...
302 -
303 - final servers = await Hive.openBox<Server>(HiveKeys.servers);
304 - final messages = await Hive.openBox<Message>(HiveKeys.messages);
305 - final conversations = await Hive.openBox<Conversation>(HiveKeys.conversations);
306 - final personas = await Hive.openBox<Persona>(HiveKeys.personas);
307 - final settings = await Hive.openBox<AppSettings>(HiveKeys.settings);
308 -
309 - return HiveBoxes(
310 - servers: servers,
311 - messages: messages,
312 - conversations: conversations,
313 - personas: personas,
314 - settings: settings,
315 - );
316 - }
317 -}
318 -```
319 -
320 -### 6. App Constants
321 -
322 -Create `core/constants/app_constants.dart`:
323 -
324 -```dart
325 -class AppConstants {
326 - static const String appName = 'LocalMind';
327 - static const String appTagline = 'Your AI. Your Device. Your Rules.';
328 - static const String version = '1.0.0';
329 -
330 - // Default chat parameters
331 - static const double defaultTemperature = 0.7;
332 - static const double defaultTopP = 0.9;
333 - static const int defaultMaxTokens = 2048;
334 - static const int defaultContextLength = 4096;
335 -
336 - // API defaults
337 - static const int connectionTimeoutMs = 5000;
338 - static const int receiveTimeoutMs = 60000;
339 -}
340 -```
341 -
342 -### 7. GoRouter Setup
343 -
344 -Create `core/routes/app_router.dart`:
345 -
346 -```dart
347 -final appRouter = GoRouter(
348 - initialLocation: '/',
349 - routes: [
350 - GoRoute(
351 - path: '/',
352 - builder: (context, state) => const ChatPage(),
353 - routes: [
354 - GoRoute(path: 'servers', builder: (context, state) => const ServerListPage()),
355 - GoRoute(path: 'servers/add', builder: (context, state) => const AddServerPage()),
356 - GoRoute(path: 'personas', builder: (context, state) => const PersonaListPage()),
357 - GoRoute(path: 'personas/create', builder: (context, state) => const CreatePersonaPage()),
358 - GoRoute(path: 'settings', builder: (context, state) => const SettingsPage()),
359 - ],
360 - ),
361 - ],
362 -);
363 -```
364 -
365 ----
366 -
367 -## Core Models (Stubs)
368 -
369 -Create stub model files in `core/models/` with minimal implementations. Full implementations come in Feature 07.
370 -
371 -**Models to create (stubs):**
372 -- `server.dart` — ServerType enum, ConnectionStatus enum, Server class
373 -- `message.dart` — MessageRole enum, MessageStatus enum, Message class
374 -- `conversation.dart` — Conversation class
375 -- `persona.dart` — Persona class
376 -- `model_info.dart` — ModelInfo class
377 -- `chat_parameters.dart` — ChatParameters class
378 -- `app_settings.dart` — AppSettings class
379 -
380 -Each stub should have:
381 -- All required fields
382 -- `fromMap()` / `toMap()` methods
383 -- `copyWith()` method
384 -- Hive annotations (`@HiveType`, `@HiveField`)
385 -
386 ----
387 -
388 -## Core Components (Stubs)
389 -
390 -Create stub reusable components in `core/components/`:
391 -
392 -- `app_button.dart` — PrimaryButton, OutlineButton variants
393 -- `app_card.dart` — Card with standard styling
394 -- `app_text_field.dart` — Standard text input
395 -
396 -These get fully implemented in Feature 08 (Design System).
397 -
398 ----
399 -
400 -## Acceptance Criteria
401 -
402 -- [ ] `flutter run` launches the app with ShadCN dark theme (no counter app).
403 -- [ ] App shell shows a chat screen placeholder with a working drawer/sidebar.
404 -- [ ] Hive initializes without errors and boxes are accessible.
405 -- [ ] Riverpod `ProviderScope` wraps the entire app.
406 -- [ ] All feature folders exist with placeholder files.
407 -- [ ] Routing works — navigating to `/settings` shows a settings placeholder.
408 -- [ ] Both dark and light themes render correctly when toggled.
409 -
410 ----
411 -
412 -## Notes
413 -
414 -- Do NOT implement any feature logic in this phase. Only scaffolding.
415 -- The ShadCN theme should be the single source of truth for all colors/typography.
416 -- Prefer `ConsumerWidget` / `ConsumerStatefulWidget` for all screens from the start.
417 -- Feature imports are **forbidden** — features must not import from other features.
plans/01_server_connection.md deleted
-251
@@ -1,251 +0,0 @@
1 -# Feature 01 — Server Connection & Management
2 -
3 -> **Phase:** 1 — Core
4 -> **Status:** Pending
5 -> **Depends on:** `00_project_setup`, `07_core_architecture`
6 -
7 ----
8 -
9 -## Goal
10 -
11 -Allow users to configure and manage connections to local LLM servers (LM Studio, Ollama) and cloud providers (OpenRouter). This is the foundational feature — no chat works without a connected server.
12 -
13 ----
14 -
15 -## Feature Folder Structure
16 -
17 -```
18 -features/server_connection/
19 -├── data/
20 -│ └── models/ # (Uses core/models/server.dart)
21 -│
22 -├── providers/
23 -│ └── server_connection_providers.dart
24 -│
25 -└── views/
26 - ├── server_list_page.dart # Main page
27 - ├── add_server_page.dart # Add/Edit server
28 - └── components/
29 - ├── server_card.dart
30 - ├── connection_status_indicator.dart
31 - └── server_type_selector.dart
32 -```
33 -
34 ----
35 -
36 -## Dependencies Used
37 -
38 -| Package | Purpose |
39 -| ------- | ---------------------------------------------------- |
40 -| `dio` | HTTP client for REST API calls and connection testing |
41 -| `hive_ce` | Persist server profiles locally |
42 -| `flutter_riverpod` | State management for server list & active server |
43 -
44 ----
45 -
46 -## Data Model
47 -
48 -The `Server` model and enums (`ServerType`, `ConnectionStatus`) are defined in `core/models/server.dart`.
49 -
50 ----
51 -
52 -## API Service
53 -
54 -`ServerRepository` is defined in `core/repository/server/server_repository.dart`.
55 -
56 -```dart
57 -class ServerRepository {
58 - final Dio _dio;
59 -
60 - Future<bool> testConnection(Server server);
61 - Future<List<ModelInfo>> fetchModels(Server server);
62 - Future<int?> pingServer(Server server);
63 -}
64 -```
65 -
66 -**LM Studio specifics:**
67 -- Models: `GET /v1/models` → OpenAI-compatible model list
68 -- Health: `GET /v1/models` returning 200 = healthy
69 -
70 -**Ollama specifics:**
71 -- Models: `GET /api/tags` → `{ models: [...] }`
72 -- Health: `GET /api/tags` returning 200 = healthy
73 -
74 -**OpenRouter specifics:**
75 -- Models: `GET /models` with `Authorization: Bearer {apiKey}`
76 -- Health: `GET /models` returning 200 = healthy
77 -
78 ----
79 -
80 -## Providers
81 -
82 -Location: `features/server_connection/providers/server_connection_providers.dart`
83 -
84 -### Provider Organization
85 -
86 -```dart
87 -// 1. Repository Provider
88 -final serverRepositoryProvider = Provider<ServerRepository>((ref) {
89 - return ServerRepository(ref.read(dioProvider));
90 -});
91 -
92 -// 2. All saved servers
93 -final serversProvider = StateNotifierProvider<ServersNotifier, List<Server>>((ref) {
94 - final repository = ref.watch(serverRepositoryProvider);
95 - return ServersNotifier(repository, ref.read(hiveBoxesProvider).servers);
96 -});
97 -
98 -// 3. Active server
99 -final activeServerProvider = StateNotifierProvider<ActiveServerNotifier, Server?>((ref) {
100 - return ActiveServerNotifier(ref.read(hiveBoxesProvider).settings);
101 -});
102 -
103 -// 4. Connection status for a specific server
104 -final connectionStatusProvider = FutureProvider.family<ConnectionStatus, String>((ref, serverId) async {
105 - final server = ref.read(serversProvider).firstWhere((s) => s.id == serverId);
106 - final repo = ref.read(serverRepositoryProvider);
107 - final isConnected = await repo.testConnection(server);
108 - return isConnected ? ConnectionStatus.connected : ConnectionStatus.disconnected;
109 -});
110 -```
111 -
112 -### `ServersNotifier` StateNotifier
113 -
114 -```dart
115 -class ServersNotifier extends StateNotifier<List<Server>> {
116 - final ServerRepository _repository;
117 - final Box<Server> _box;
118 -
119 - ServersNotifier(this._repository, this._box) : super([]) {
120 - loadServers();
121 - }
122 -
123 - void loadServers() {
124 - state = _box.values.toList();
125 - }
126 -
127 - Future<void> addServer(Server server) async { ... }
128 - Future<void> updateServer(Server server) async { ... }
129 - Future<void> deleteServer(String serverId) async { ... }
130 - Future<void> setDefault(String serverId) async { ... }
131 - Future<ConnectionStatus> testConnection(String serverId) async { ... }
132 -}
133 -```
134 -
135 -### `ActiveServerNotifier` StateNotifier
136 -
137 -```dart
138 -class ActiveServerNotifier extends StateNotifier<Server?> {
139 - final Box<AppSettings> _settingsBox;
140 -
141 - ActiveServerNotifier(this._settingsBox) : super(null) {
142 - _loadActiveServer();
143 - }
144 -
145 - void _loadActiveServer() { ... }
146 - Future<void> setActiveServer(String? serverId) async { ... }
147 -}
148 -```
149 -
150 ----
151 -
152 -## Views
153 -
154 -### `server_list_page.dart`
155 -
156 -**Layout:**
157 -- App bar with title "Servers" and "+" add button
158 -- List of server cards showing:
159 - - Server name (bold)
160 - - Server type icon (LM Studio / Ollama / OpenRouter)
161 - - Host:Port or "Cloud" for OpenRouter
162 - - Connection status indicator
163 - - "Default" badge if marked as default
164 -- Empty state: illustration + "Add your first server" CTA
165 -- Pull-to-refresh to re-test all connections
166 -
167 -**Interactions:**
168 -- Tap card → navigate to edit server
169 -- Long-press → context menu (Edit, Delete, Set Default, Test Connection)
170 -- Swipe left → delete with confirmation
171 -
172 -### `add_server_page.dart`
173 -
174 -**Layout:**
175 -- Server type selector (3 segmented buttons: LM Studio, Ollama, OpenRouter)
176 -- Form fields that change based on type:
177 -
178 -**For LM Studio / Ollama:**
179 -| Field | Type | Validation |
180 -| ----------- | ----------- | ----------------------------- |
181 -| Name | Text | Required, 1–50 chars |
182 -| Host/IP | Text | Required, valid IP or hostname|
183 -| Port | Number | Required, 1–65535 |
184 -| API Key | Text (opt) | Optional |
185 -
186 -**For OpenRouter:**
187 -| Field | Type | Validation |
188 -| ----------- | ----------- | ----------------------------- |
189 -| Name | Text | Required, 1–50 chars |
190 -| API Key | Text | Required, starts with `sk-` |
191 -
192 -- "Test Connection" button → shows result toast
193 -- "Save" button → validates, tests connection, saves to Hive
194 -- Auto-fill defaults: LM Studio port = 1234, Ollama port = 11434
195 -
196 -**Error handling:**
197 -- Connection refused → "Cannot reach server. Is it running?"
198 -- Timeout → "Server took too long to respond. Check the address."
199 -- 401 → "Invalid API key."
200 -- Generic → show raw error message
201 -
202 ----
203 -
204 -## Components
205 -
206 -### `server_card.dart`
207 -
208 -A card showing:
209 -- Leading: Server type icon
210 -- Title: Server name
211 -- Subtitle: `host:port` or `openrouter.ai`
212 -- Trailing: `ConnectionStatusIndicator` + chevron
213 -- Default badge (if applicable)
214 -
215 -### `connection_status_indicator.dart`
216 -
217 -Animated dot:
218 -- Green (pulsing) = Connected
219 -- Red (static) = Error
220 -- Grey = Not checked
221 -- Loading spinner = Checking
222 -
223 -### `server_type_selector.dart`
224 -
225 -Three-segment toggle:
226 -- LM Studio (terminal icon)
227 -- Ollama (llama emoji)
228 -- OpenRouter (cloud icon)
229 -
230 ----
231 -
232 -## Background Connection Monitoring
233 -
234 -- When app is in foreground, periodically ping the active server every 30 seconds.
235 -- Update `connectionStatusProvider` accordingly.
236 -- Show a subtle banner at top of chat if connection is lost.
237 -
238 ----
239 -
240 -## Acceptance Criteria
241 -
242 -- [ ] User can add an LM Studio server with IP + port and test connection.
243 -- [ ] User can add an Ollama server with IP + port and test connection.
244 -- [ ] User can add an OpenRouter server with API key and test connection.
245 -- [ ] Server list displays all saved servers with connection status.
246 -- [ ] User can edit and delete servers.
247 -- [ ] User can set a default server.
248 -- [ ] Connection status auto-updates with visual indicator.
249 -- [ ] Server profiles persist across app restarts (Hive).
250 -- [ ] Connection errors show meaningful error messages.
251 -- [ ] Empty state is handled gracefully.
plans/02_chat_interface.md deleted
-362
@@ -1,362 +0,0 @@
1 -# Feature 02 — Chat Interface
2 -
3 -> **Phase:** 1–2 — Core + Chat Polish
4 -> **Status:** Pending
5 -> **Depends on:** `00_project_setup`, `01_server_connection`, `07_core_architecture`
6 -
7 ----
8 -
9 -## Goal
10 -
11 -Build a ChatGPT/Claude-quality chat interface with:
12 -- Beautiful message bubbles with markdown rendering
13 -- Real-time streaming responses (SSE / chunked transfer)
14 -- Rich input bar with actions
15 -- Message interactions (copy, retry, delete, edit)
16 -- Typing indicators and loading states
17 -
18 ----
19 -
20 -## Feature Folder Structure
21 -
22 -```
23 -features/chat/
24 -├── data/
25 -│ └── models/ # (Uses core/models/message.dart)
26 -│
27 -├── providers/
28 -│ └── chat_providers.dart
29 -│
30 -└── views/
31 - ├── chat_page.dart # Main chat screen
32 - └── components/
33 - ├── chat_bubble.dart
34 - ├── chat_input_bar.dart
35 - ├── typing_indicator.dart
36 - ├── message_action_bar.dart
37 - └── code_block.dart
38 -```
39 -
40 ----
41 -
42 -## Dependencies Used
43 -
44 -| Package | Purpose |
45 -| ------------------------ | ------------------------------- |
46 -| `dio` | HTTP client with streaming |
47 -| `flutter_markdown` | Render markdown in messages |
48 -| `flutter_highlight` | Code block syntax highlighting |
49 -| `haptic_feedback` | Tactile feedback on actions |
50 -
51 ----
52 -
53 -## Data Model
54 -
55 -The `Message` model and enums (`MessageRole`, `MessageStatus`) are defined in `core/models/message.dart`.
56 -
57 ----
58 -
59 -## API Integration — Streaming Chat Completions
60 -
61 -### LM Studio & OpenRouter (OpenAI-compatible)
62 -
63 -```
64 -POST /v1/chat/completions
65 -Headers:
66 - Content-Type: application/json
67 - Authorization: Bearer {apiKey} // OpenRouter only
68 -Body:
69 -{
70 - "model": "model-id",
71 - "messages": [
72 - {"role": "system", "content": "..."},
73 - {"role": "user", "content": "..."},
74 - {"role": "assistant", "content": "..."}
75 - ],
76 - "temperature": 0.7,
77 - "top_p": 0.9,
78 - "max_tokens": 2048,
79 - "stream": true
80 -}
81 -```
82 -
83 -**Streaming response format (SSE):**
84 -```
85 -data: {"choices":[{"delta":{"content":"Hello"}}]}
86 -data: {"choices":[{"delta":{"content":" world"}}]}
87 -data: [DONE]
88 -```
89 -
90 -### Ollama
91 -
92 -```
93 -POST /api/chat
94 -Body:
95 -{
96 - "model": "model-name",
97 - "messages": [
98 - {"role": "system", "content": "..."},
99 - {"role": "user", "content": "..."}
100 - ],
101 - "stream": true,
102 - "options": {
103 - "temperature": 0.7,
104 - "top_p": 0.9,
105 - "num_predict": 2048
106 - }
107 -}
108 -```
109 -
110 -**Streaming response format (NDJSON):**
111 -```json
112 -{"message":{"role":"assistant","content":"Hello"},"done":false}
113 -{"message":{"role":"assistant","content":" world"},"done":false}
114 -{"message":{"role":"assistant","content":""},"done":true,"total_duration":...}
115 -```
116 -
117 ----
118 -
119 -## Chat Services
120 -
121 -`ChatService` and implementations are defined in `core/repository/chat/`.
122 -
123 -```dart
124 -abstract class ChatService {
125 - Stream<String> sendMessage({
126 - required Server server,
127 - required String modelId,
128 - required List<Message> messages,
129 - required ChatParameters params,
130 - });
131 -
132 - void cancelStream();
133 -}
134 -```
135 -
136 -### Streaming Implementation Details
137 -
138 -1. **Context window management:** Truncate message history to fit within `contextLength`. Keep system prompt + last N messages.
139 -2. **Token estimation:** Use 4 chars ≈ 1 token heuristic.
140 -3. **Error recovery:** If stream breaks, save partial and mark as error.
141 -4. **Cancel token:** Pass `CancelToken` to Dio for user cancellation.
142 -
143 ----
144 -
145 -## Providers
146 -
147 -Location: `features/chat/providers/chat_providers.dart`
148 -
149 -### Provider Organization
150 -
151 -```dart
152 -// 1. Chat Service (factory based on active server type)
153 -final chatServiceProvider = Provider<ChatService>((ref) {
154 - final server = ref.watch(activeServerProvider);
155 - if (server == null) throw StateError('No active server');
156 - return ChatService.forServer(server.type, ref.read(dioProvider));
157 -});
158 -
159 -// 2. Chat Parameters (from settings)
160 -final chatParamsProvider = Provider<ChatParameters>((ref) {
161 - final settings = ref.watch(settingsProvider);
162 - return ChatParameters(
163 - temperature: settings.temperature,
164 - topP: settings.topP,
165 - maxTokens: settings.maxTokens,
166 - contextLength: settings.contextLength,
167 - );
168 -});
169 -
170 -// 3. Selected model for current chat
171 -final selectedModelProvider = StateProvider<ModelInfo?>((ref) => null);
172 -
173 -// 4. Streaming state
174 -final isStreamingProvider = StateProvider<bool>((ref) => false);
175 -
176 -// 5. Messages for current conversation
177 -final messagesProvider = StateNotifierProvider<MessagesNotifier, List<Message>>((ref) {
178 - final repository = ref.watch(messageRepositoryProvider);
179 - final conversation = ref.watch(activeConversationProvider);
180 - return MessagesNotifier(repository, conversation?.id);
181 -});
182 -
183 -// 6. Message list state
184 -class MessagesNotifier extends StateNotifier<List<Message>> {
185 - final MessageRepository _repository;
186 - final String? _conversationId;
187 -
188 - MessagesNotifier(this._repository, this._conversationId) : super([]) {
189 - if (_conversationId != null) loadMessages();
190 - }
191 -
192 - Future<void> loadMessages() async { ... }
193 - Future<void> sendMessage(String content) async { ... }
194 - Future<void> retryLastMessage() async { ... }
195 - Future<void> editMessage(String messageId, String newContent) async { ... }
196 - Future<void> deleteMessage(String messageId) async { ... }
197 - void cancelStream() { ... }
198 - Future<void> clearConversation() async { ... }
199 -}
200 -```
201 -
202 ----
203 -
204 -## Views
205 -
206 -### `chat_page.dart`
207 -
208 -**Layout (top to bottom):**
209 -
210 -```
211 -┌──────────────────────────────────────┐
212 -│ AppBar │
213 -│ [☰ Drawer] [Model: GPT-4] [⋮ Menu]│
214 -├──────────────────────────────────────┤
215 -│ │
216 -│ Message List (scrollable) │
217 -│ │
218 -│ ┌─────────────────────────────┐ │
219 -│ │ 👤 User message │ │
220 -│ └─────────────────────────────┘ │
221 -│ │
222 -│ ┌─────────────────────────────┐ │
223 -│ │ 🤖 Assistant response │ │
224 -│ │ (markdown rendered) │ │
225 -│ │ [Copy] [Retry] [⋮] │ │
226 -│ └─────────────────────────────┘ │
227 -│ │
228 -│ ┌─────────────────────────────┐ │
229 -│ │ ⏳ Streaming indicator... │ │
230 -│ └─────────────────────────────┘ │
231 -│ │
232 -├──────────────────────────────────────┤
233 -│ Input Bar │
234 -│ [📎] [ Type a message... ] [▶] │
235 -│ [Stop Generating] (if streaming) │
236 -└──────────────────────────────────────┘
237 -```
238 -
239 -**App bar details:**
240 -- Left: Hamburger menu to open conversation drawer
241 -- Center: Current model name (tappable → opens model picker)
242 -- Right: overflow menu (New Chat, Clear, Chat Settings)
243 -
244 -**Empty state (new conversation):**
245 -- Show LocalMind logo centered
246 -- "Start a conversation" subtitle
247 -- Quick-start prompt suggestions as tappable chips
248 -
249 ----
250 -
251 -## Components
252 -
253 -### `chat_bubble.dart`
254 -
255 -**User messages:**
256 -- Right-aligned, filled background
257 -- Plain text or light markdown
258 -- Timestamp on bottom-right
259 -
260 -**Assistant messages:**
261 -- Left-aligned, transparent/subtle surface background
262 -- Full markdown rendering (headers, bold, italic, lists, code blocks, links, tables)
263 -- Action buttons: Copy | Retry | ⋮
264 -
265 -**System messages:**
266 -- Centered, muted text, italic
267 -- Toggleable in settings
268 -
269 -**Streaming state:**
270 -- Show text as it arrives
271 -- Blinking cursor at end
272 -- Auto-scroll to bottom
273 -
274 -### `chat_input_bar.dart`
275 -
276 -**Layout:**
277 -- Multi-line text field (max 6 lines, expanding)
278 -- Left: attachment button (📎) — future multimodal
279 -- Right: send button (▶) → stop button (⏹) when streaming
280 -- Placeholder: "Message LocalMind..."
281 -
282 -**Behavior:**
283 -- Send on button tap or keyboard send action
284 -- Disable when input empty or disconnected
285 -- Character/token count near limits
286 -- Haptic feedback on send
287 -
288 -### `typing_indicator.dart`
289 -
290 -- Three bouncing dots animation
291 -- Shown while waiting for first token
292 -
293 -### `message_action_bar.dart`
294 -
295 -- **Copy** — copy full text to clipboard
296 -- **Retry** — regenerate response
297 -- **Edit** (user messages) — open edit mode
298 -- **Delete** — remove with confirmation
299 -- **More (⋮)** — bottom sheet:
300 - - "Copy as Markdown"
301 - - "Share"
302 - - "View raw"
303 - - "Token count: {n}"
304 -
305 -### `code_block.dart`
306 -
307 -- Language detection from fence info string
308 -- Syntax highlighting via `flutter_highlight`
309 -- Header bar: language name + copy button
310 -- Dark background, horizontal scroll
311 -- Monospace font (Fira Code / JetBrains Mono)
312 -
313 ----
314 -
315 -## Scroll Behavior
316 -
317 -- Auto-scroll to bottom on new message (if user already near bottom)
318 -- If user scrolled up, show "New messages ↓" floating button
319 -- Smooth scroll animation
320 -- Scroll-to-bottom FAB when away from bottom
321 -
322 ----
323 -
324 -## Error Handling
325 -
326 -| Error | User-facing message |
327 -| ---------------------------- | -------------------------------------------------------- |
328 -| Server unreachable | "Can't reach {server name}. Check your connection." |
329 -| Model not found | "Model '{name}' is no longer available." |
330 -| Context length exceeded | "Conversation too long. Some older messages were trimmed."|
331 -| Stream interrupted | "Response was interrupted. Tap retry to try again." |
332 -| Rate limit (OpenRouter) | "Rate limit reached. Please wait a moment." |
333 -| Generic API error | Show raw error + "Tap to retry" |
334 -
335 ----
336 -
337 -## Performance Considerations
338 -
339 -- **ListView.builder** for message list — only render visible
340 -- **Markdown rendering** — lazy, only visible messages
341 -- **Syntax highlighting** — cache highlighted blocks
342 -- **Long conversations** — for 500+ messages, consider pagination
343 -
344 ----
345 -
346 -## Acceptance Criteria
347 -
348 -- [ ] User can type and send a message.
349 -- [ ] Response streams in real-time (LM Studio).
350 -- [ ] Response streams in real-time (Ollama).
351 -- [ ] Response streams in real-time (OpenRouter).
352 -- [ ] Markdown renders correctly.
353 -- [ ] Code blocks have syntax highlighting and copy button.
354 -- [ ] User can copy a full message.
355 -- [ ] User can retry a failed response.
356 -- [ ] User can delete individual messages.
357 -- [ ] Typing indicator shows while waiting.
358 -- [ ] "Stop generating" button cancels stream.
359 -- [ ] Scroll behavior follows spec.
360 -- [ ] Long conversations (100+ messages) scroll smoothly.
361 -- [ ] Errors display user-friendly messages with retry.
362 -- [ ] Haptic feedback on send.
plans/03_model_management.md deleted
-273
@@ -1,273 +0,0 @@
1 -# Feature 03 — Model Management
2 -
3 -> **Phase:** 2 — Chat Polish
4 -> **Status:** Pending
5 -> **Depends on:** `01_server_connection`, `07_core_architecture`
6 -
7 ----
8 -
9 -## Goal
10 -
11 -Let users browse, select, and swap LLM models from their connected servers. Display model metadata and allow mid-conversation model switching.
12 -
13 ----
14 -
15 -## Feature Folder Structure
16 -
17 -```
18 -features/model_management/
19 -├── data/
20 -│ └── models/ # (Uses core/models/model_info.dart)
21 -│
22 -├── providers/
23 -│ └── model_management_providers.dart
24 -│
25 -└── views/
26 - ├── model_picker_sheet.dart # Bottom sheet for model selection
27 - └── components/
28 - ├── model_tile.dart
29 - └── model_metadata_chip.dart
30 -```
31 -
32 ----
33 -
34 -## Dependencies Used
35 -
36 -| Package | Purpose |
37 -| ------------------- | ---------------------------- |
38 -| `dio` | Fetch model lists from APIs |
39 -| `flutter_riverpod` | Model state management |
40 -
41 ----
42 -
43 -## Data Model
44 -
45 -`ModelInfo` is defined in `core/models/model_info.dart`.
46 -
47 -```dart
48 -class ModelInfo {
49 - final String id;
50 - final String name;
51 - final String? description;
52 - final int? parameterCount;
53 - final int? contextLength;
54 - final int? fileSize;
55 - final String? quantization;
56 - final String? architecture;
57 - final ServerType serverType;
58 - final String serverId;
59 - final DateTime? modifiedAt;
60 -}
61 -```
62 -
63 ----
64 -
65 -## API Response Parsing
66 -
67 -### LM Studio (`GET /v1/models`)
68 -
69 -```json
70 -{
71 - "data": [
72 - {
73 - "id": "llama-3.2-3b-instruct",
74 - "object": "model",
75 - "owned_by": "organization-owner"
76 - }
77 - ]
78 -}
79 -```
80 -
81 -- LM Studio provides minimal metadata — only `id`
82 -- Parse model name from ID (replace hyphens/underscores with spaces, capitalize)
83 -- Try to extract parameter count from name (e.g., "3b" → 3 billion)
84 -
85 -### Ollama (`GET /api/tags`)
86 -
87 -```json
88 -{
89 - "models": [
90 - {
91 - "name": "llama3.2:3b",
92 - "model": "llama3.2:3b",
93 - "size": 2019393189,
94 - "digest": "...",
95 - "details": {
96 - "parent_model": "",
97 - "format": "gguf",
98 - "family": "llama",
99 - "families": ["llama"],
100 - "parameter_size": "3.2B",
101 - "quantization_level": "Q4_K_M"
102 - },
103 - "modified_at": "2024-..."
104 - }
105 - ]
106 -}
107 -```
108 -
109 -- Ollama provides rich metadata
110 -- Parse `parameter_size` to extract number
111 -- Use `details.family` for architecture
112 -- Use `size` for file size display
113 -
114 -### OpenRouter (`GET /v1/models`)
115 -
116 -```json
117 -{
118 - "data": [
119 - {
120 - "id": "google/gemma-2-9b-it",
121 - "name": "Google: Gemma 2 9B",
122 - "description": "...",
123 - "context_length": 8192,
124 - "pricing": { "prompt": "0.00003", "completion": "0.00006" },
125 - "architecture": { "modality": "text->text", "tokenizer": "Gemini" }
126 - }
127 - ]
128 -}
129 -```
130 -
131 -- OpenRouter provides most metadata including pricing
132 -- Filter to text-only models
133 -
134 ----
135 -
136 -## Providers
137 -
138 -Location: `features/model_management/providers/model_management_providers.dart`
139 -
140 -### Provider Organization
141 -
142 -```dart
143 -// 1. Available models for active server
144 -final availableModelsProvider = FutureProvider<List<ModelInfo>>((ref) async {
145 - final server = ref.watch(activeServerProvider);
146 - if (server == null) return [];
147 - return ref.read(serverRepositoryProvider).fetchModels(server);
148 -});
149 -
150 -// 2. Currently selected model (stored in conversation or globally)
151 -final selectedModelProvider = StateProvider<ModelInfo?>((ref) => null);
152 -
153 -// 3. Search/filter query
154 -final modelSearchQueryProvider = StateProvider<String>((ref) => '');
155 -
156 -// 4. Filtered models based on search
157 -final filteredModelsProvider = Provider<List<ModelInfo>>((ref) {
158 - final models = ref.watch(availableModelsProvider).value ?? [];
159 - final query = ref.watch(modelSearchQueryProvider).toLowerCase();
160 - if (query.isEmpty) return models;
161 - return models.where((m) =>
162 - m.name.toLowerCase().contains(query) ||
163 - m.id.toLowerCase().contains(query)
164 - ).toList();
165 -});
166 -
167 -// 5. Model cache (5 minute TTL per server)
168 -final modelCacheProvider = StateNotifierProvider<ModelCacheNotifier, Map<String, CachedModels>>((ref) {
169 - return ModelCacheNotifier();
170 -});
171 -
172 -class ModelCacheNotifier extends StateNotifier<Map<String, CachedModels>> {
173 - // Cache management with 5-minute TTL
174 -}
175 -```
176 -
177 ----
178 -
179 -## Views
180 -
181 -### `model_picker_sheet.dart`
182 -
183 -**Trigger:** Tap on model name in chat app bar.
184 -
185 -**Layout:**
186 -
187 -```
188 -┌──────────────────────────────────────┐
189 -│ ═══ (drag handle) │
190 -│ │
191 -│ Select Model │
192 -│ [🔍 Search models... ] │
193 -│ │
194 -│ ── Active Server: My Desktop ── │
195 -│ │
196 -│ ┌──────────────────────────────┐ │
197 -│ │ ✅ Llama 3.2 3B Instruct │ │
198 -│ │ 3.2B params · Q4_K_M │ │
199 -│ │ 1.9 GB · 4096 context │ │
200 -│ └──────────────────────────────┘ │
201 -│ │
202 -│ ┌──────────────────────────────┐ │
203 -│ │ Mistral 7B Instruct │ │
204 -│ │ 7B params · Q4_K_M │ │
205 -│ │ 3.8 GB · 8192 context │ │
206 -│ └──────────────────────────────┘ │
207 -│ │
208 -│ [Refresh Models] │
209 -└──────────────────────────────────────┘
210 -```
211 -
212 -**Features:**
213 -- Search/filter bar at top
214 -- Selected model has checkmark
215 -- Each tile: name, parameter count, quantization, size, context length
216 -- Gracefully omit unavailable metadata
217 -- "Refresh Models" button to re-fetch
218 -- Loading skeleton while fetching
219 -- Error state with retry
220 -
221 ----
222 -
223 -## Components
224 -
225 -### `model_tile.dart`
226 -
227 -- Leading: Model architecture icon or first letter avatar
228 -- Title: Model display name
229 -- Subtitle: Metadata chips
230 -- Trailing: Checkmark if selected
231 -- Tap → select, dismiss sheet
232 -
233 -### `model_metadata_chip.dart`
234 -
235 -Small pill labels:
236 -- `3.2B` (parameter count)
237 -- `Q4_K_M` (quantization)
238 -- `1.9 GB` (file size)
239 -- `4096 ctx` (context length)
240 -
241 ----
242 -
243 -## Mid-Chat Model Swapping
244 -
245 -1. Update `selectedModelProvider` with new model
246 -2. Show system divider: "Switched to {model name}"
247 -3. Subsequent messages use new model
248 -4. `Message.modelId` records which model generated each message
249 -5. No conversation reset — continue with existing history
250 -
251 ----
252 -
253 -## Model Caching
254 -
255 -- Cache model list per server (in-memory)
256 -- Cache duration: 5 minutes
257 -- Force refresh via "Refresh Models" button
258 -- When switching servers, clear and re-fetch
259 -
260 ----
261 -
262 -## Acceptance Criteria
263 -
264 -- [ ] Model list fetched for LM Studio servers.
265 -- [ ] Model list fetched for Ollama servers (full metadata).
266 -- [ ] Model list fetched for OpenRouter servers.
267 -- [ ] User can search/filter models by name.
268 -- [ ] Selected model is visually indicated.
269 -- [ ] Tapping a model selects and closes picker.
270 -- [ ] Model can be swapped mid-conversation.
271 -- [ ] Model metadata displays correctly.
272 -- [ ] Loading and error states handled.
273 -- [ ] Model list is cached and can be force-refreshed.
plans/04_conversation_management.md deleted
-271
@@ -1,271 +0,0 @@
1 -# Feature 04 — Conversation Management
2 -
3 -> **Phase:** 3 — Features
4 -> **Status:** Pending
5 -> **Depends on:** `00_project_setup`, `02_chat_interface`, `07_core_architecture`
6 -
7 ----
8 -
9 -## Goal
10 -
11 -Provide a full conversation lifecycle: create, rename, delete, search, and pin conversations. Build a ChatGPT-style sidebar/drawer for browsing conversation history.
12 -
13 ----
14 -
15 -## Feature Folder Structure
16 -
17 -```
18 -features/conversation/
19 -├── data/
20 -│ └── models/ # (Uses core/models/conversation.dart)
21 -│
22 -├── providers/
23 -│ └── conversation_providers.dart
24 -│
25 -└── views/
26 - ├── conversation_drawer.dart # Sidebar navigation
27 - └── components/
28 - ├── conversation_tile.dart
29 - ├── conversation_search_bar.dart
30 - └── date_section_header.dart
31 -```
32 -
33 ----
34 -
35 -## Dependencies Used
36 -
37 -| Package | Purpose |
38 -| ------------------- | -------------------------------- |
39 -| `hive_ce` | Persist conversations locally |
40 -| `flutter_riverpod` | Conversation state management |
41 -
42 ----
43 -
44 -## Data Model
45 -
46 -The `Conversation` model is defined in `core/models/conversation.dart`.
47 -
48 ----
49 -
50 -## Providers
51 -
52 -Location: `features/conversation/providers/conversation_providers.dart`
53 -
54 -### Provider Organization
55 -
56 -```dart
57 -// 1. All conversations
58 -final conversationsProvider = StateNotifierProvider<ConversationsNotifier, List<Conversation>>((ref) {
59 - final box = ref.watch(hiveBoxesProvider).conversations;
60 - return ConversationsNotifier(box);
61 -});
62 -
63 -// 2. Currently active conversation
64 -final activeConversationProvider = StateNotifierProvider<ActiveConversationNotifier, Conversation?>((ref) {
65 - final conversationsBox = ref.watch(hiveBoxesProvider).conversations;
66 - final settingsBox = ref.watch(hiveBoxesProvider).settings;
67 - return ActiveConversationNotifier(conversationsBox, settingsBox);
68 -});
69 -
70 -// 3. Search query
71 -final conversationSearchProvider = StateProvider<String>((ref) => '');
72 -
73 -// 4. Filtered conversations based on search
74 -final filteredConversationsProvider = Provider<List<Conversation>>((ref) {
75 - final convos = ref.watch(conversationsProvider);
76 - final query = ref.watch(conversationSearchProvider).toLowerCase();
77 - if (query.isEmpty) return convos;
78 - return convos.where((c) =>
79 - c.title.toLowerCase().contains(query) ||
80 - (c.lastMessagePreview?.toLowerCase().contains(query) ?? false)
81 - ).toList();
82 -});
83 -```
84 -
85 -### `ConversationsNotifier`
86 -
87 -```dart
88 -class ConversationsNotifier extends StateNotifier<List<Conversation>> {
89 - final Box<Conversation> _box;
90 -
91 - ConversationsNotifier(this._box) : super([]) {
92 - loadConversations();
93 - }
94 -
95 - Future<void> loadConversations();
96 - Future<Conversation> createConversation({String? title, String? personaId, String? systemPrompt});
97 - Future<void> renameConversation(String id, String newTitle);
98 - Future<void> deleteConversation(String id);
99 - Future<void> togglePin(String id);
100 - Future<void> updatePreview(String id, String preview, DateTime updatedAt);
101 - Future<void> deleteAll();
102 -}
103 -```
104 -
105 -### `ActiveConversationNotifier`
106 -
107 -```dart
108 -class ActiveConversationNotifier extends StateNotifier<Conversation?> {
109 - final Box<Conversation> _conversationsBox;
110 - final Box<AppSettings> _settingsBox;
111 -
112 - ActiveConversationNotifier(this._conversationsBox, this._settingsBox) : super(null) {
113 - _loadActiveConversation();
114 - }
115 -
116 - void _loadActiveConversation() { ... }
117 - Future<void> setActiveConversation(String? id) async { ... }
118 -}
119 -```
120 -
121 ----
122 -
123 -## Auto-Title Generation
124 -
125 -When a new conversation is started:
126 -
127 -1. First message sent with title "New Chat"
128 -2. After first assistant response, auto-generate title
129 -3. **Method A (preferred):** Send lightweight title generation request:
130 - ```
131 - System: Generate a short title (max 6 words) for the following conversation.
132 - Respond with ONLY the title, no quotes or punctuation.
133 - User: {first user message}
134 - Assistant: {first assistant response (truncated to 200 chars)}
135 - ```
136 -4. **Method B (fallback):** Use first 50 chars of first user message
137 -5. Update conversation title in Hive
138 -
139 ----
140 -
141 -## Views
142 -
143 -### `conversation_drawer.dart`
144 -
145 -Primary navigation element, opened from hamburger menu.
146 -
147 -**Layout:**
148 -
149 -```
150 -┌──────────────────────────────────────┐
151 -│ LocalMind [⚙️] │
152 -│ │
153 -│ [+ New Chat] │
154 -│ │
155 -│ [🔍 Search conversations... ] │
156 -│ │
157 -│ ── PINNED ── │
158 -│ 📌 Project Brainstorm │
159 -│ "Let me help you with..." │
160 -│ 3 hours ago │
161 -│ │
162 -│ ── TODAY ── │
163 -│ 💬 How to cook pasta │
164 -│ "Here's a simple recipe..." │
165 -│ 2 hours ago │
166 -│ │
167 -│ ── YESTERDAY ── │
168 -│ 💬 Explain quantum computing │
169 -│ "At its core, quantum..." │
170 -│ Yesterday │
171 -│ │
172 -│ ───────────────────── │
173 -│ [🖥 Servers] [👤 Personas] │
174 -│ [⚙️ Settings] │
175 -└──────────────────────────────────────┘
176 -```
177 -
178 -**Section grouping:**
179 -- Pinned (always on top)
180 -- Today
181 -- Yesterday
182 -- Previous 7 Days
183 -- Previous 30 Days
184 -- Older
185 -
186 -**Interactions:**
187 -- Tap → load and switch to conversation
188 -- Long-press → context menu: Rename, Pin/Unpin, Delete
189 -- Swipe left → delete with undo snackbar
190 -- Active conversation highlighted
191 -
192 ----
193 -
194 -## Components
195 -
196 -### `conversation_tile.dart`
197 -
198 -- Leading: Pin icon (if pinned) or message icon
199 -- Title: Conversation title (single line, ellipsis)
200 -- Subtitle: Last message preview (single line, muted)
201 -- Trailing: Relative timestamp ("2h ago", "Yesterday")
202 -- Active state: subtle accent background
203 -- Slidable: swipe-to-delete
204 -
205 -### `conversation_search_bar.dart`
206 -
207 -- ShadCN-styled search input
208 -- Clear button when text present
209 -- Debounced search (300ms)
210 -
211 -### `date_section_header.dart`
212 -
213 -- Uppercase, small text, muted color
214 -- "TODAY", "YESTERDAY", "PREVIOUS 7 DAYS", etc.
215 -
216 ----
217 -
218 -## Conversation Lifecycle
219 -
220 -### Create
221 -1. User taps "+ New Chat"
222 -2. Create new `Conversation` with title "New Chat"
223 -3. Save to Hive
224 -4. Set as active conversation
225 -5. Navigate to chat screen
226 -6. Auto-title after first exchange
227 -
228 -### Switch
229 -1. User taps conversation in drawer
230 -2. Set as active conversation
231 -3. Load messages from Hive
232 -4. Restore model selection if stored
233 -5. Close drawer, show chat
234 -
235 -### Delete
236 -1. User swipes or selects delete
237 -2. Show confirmation dialog
238 -3. Delete conversation + all messages
239 -4. If deleting active, switch to most recent or create new
240 -5. Show undo snackbar (5 seconds)
241 -
242 -### Rename
243 -1. Select rename from context menu
244 -2. Show inline edit or dialog
245 -3. Save on confirm
246 -
247 ----
248 -
249 -## Storage
250 -
251 -- Conversations in `Hive.box<Conversation>('conversations')`
252 -- Messages in `Hive.box<Message>('messages')` with `conversationId` field
253 -- Cascade delete messages on conversation delete
254 -- Order by: `isPinned DESC, updatedAt DESC`
255 -
256 ----
257 -
258 -## Acceptance Criteria
259 -
260 -- [ ] User can create a new conversation.
261 -- [ ] User can switch between conversations.
262 -- [ ] User can rename a conversation.
263 -- [ ] User can delete a conversation (with confirmation).
264 -- [ ] User can pin/unpin a conversation.
265 -- [ ] Conversations grouped by date in sidebar.
266 -- [ ] Search filters by title and message preview.
267 -- [ ] Auto-title generates after first exchange.
268 -- [ ] Active conversation highlighted in sidebar.
269 -- [ ] Empty state shows "No conversations yet" CTA.
270 -- [ ] Deleting active conversation switches or creates new.
271 -- [ ] Conversation data persists across restarts.
plans/05_templates_personas.md deleted
-279
@@ -1,279 +0,0 @@
1 -# Feature 05 — Templates & Personas
2 -
3 -> **Phase:** 3 — Features
4 -> **Status:** Pending
5 -> **Depends on:** `00_project_setup`, `04_conversation_management`, `07_core_architecture`
6 -
7 ----
8 -
9 -## Goal
10 -
11 -Allow users to define reusable AI personas (system prompts + identity) that shape model behavior. Include built-in presets and let users create custom personas.
12 -
13 ----
14 -
15 -## Feature Folder Structure
16 -
17 -```
18 -features/persona/
19 -├── data/
20 -│ └── models/ # (Uses core/models/persona.dart)
21 -│
22 -├── providers/
23 -│ └── persona_providers.dart
24 -│
25 -└── views/
26 - ├── persona_list_page.dart # Browse personas
27 - ├── create_persona_page.dart # Create/edit persona
28 - └── components/
29 - ├── persona_card.dart
30 - └── persona_category_chips.dart
31 -```
32 -
33 ----
34 -
35 -## Dependencies Used
36 -
37 -| Package | Purpose |
38 -| ------------------- | ----------------------------- |
39 -| `hive_ce` | Persist personas locally |
40 -| `flutter_riverpod` | Persona state management |
41 -
42 ----
43 -
44 -## Data Model
45 -
46 -The `Persona` model is defined in `core/models/persona.dart`.
47 -
48 ----
49 -
50 -## Built-in Presets
51 -
52 -These ship with the app and cannot be deleted (but can be cloned):
53 -
54 -### 1. General Assistant 🤖
55 -```
56 -You are a helpful, knowledgeable AI assistant. Provide clear, accurate, and concise responses. When you're not sure about something, say so. Use markdown formatting for structured responses.
57 -```
58 -Category: General
59 -
60 -### 2. Code Assistant 🧑‍💻
61 -```
62 -You are an expert software engineer. Help with coding questions, debugging, code reviews, and architecture decisions. Always provide code examples with proper syntax highlighting. Explain your reasoning. Follow best practices and mention potential pitfalls. When writing code, include comments for complex logic.
63 -```
64 -Category: Coding
65 -
66 -### 3. Math Tutor 📐
67 -```
68 -You are a patient and thorough math tutor. Explain concepts step by step, starting from fundamentals. Use examples to illustrate abstract concepts. When solving problems, show your work clearly. Use LaTeX notation for mathematical expressions when appropriate. Encourage the student and offer practice problems.
69 -```
70 -Category: Education
71 -
72 -### 4. Story Writer ✍️
73 -```
74 -You are a creative fiction writer. Help craft engaging stories, develop characters, build worlds, and write dialogue. Match the tone and style the user requests. Offer constructive suggestions to improve narratives. Be creative and take risks with your writing while staying true to the user's vision.
75 -```
76 -Category: Creative
77 -
78 -### 5. General Tutor 📚
79 -```
80 -You are an educational tutor skilled in all subjects. Explain complex topics in simple terms. Use analogies, examples, and visual descriptions. Break down topics into digestible pieces. Check understanding by posing questions. Adapt your explanation style to the student's level.
81 -```
82 -Category: Education
83 -
84 -### 6. Writing Editor ✏️
85 -```
86 -You are a professional writing editor. Help improve text clarity, grammar, style, and structure. Provide specific suggestions with explanations. Maintain the author's voice while improving readability. Offer alternatives rather than dictating changes. Format feedback clearly with before/after examples.
87 -```
88 -Category: Creative
89 -
90 -### 7. Summarizer 📋
91 -```
92 -You are a concise summarizer. When given text, provide clear, accurate summaries that capture the key points. Use bullet points for multiple items. Maintain the original meaning without adding interpretation. Adjust summary length based on the input — shorter inputs get shorter summaries.
93 -```
94 -Category: General
95 -
96 ----
97 -
98 -## Providers
99 -
100 -Location: `features/persona/providers/persona_providers.dart`
101 -
102 -### Provider Organization
103 -
104 -```dart
105 -// 1. All personas (built-in + user-created)
106 -final personasProvider = StateNotifierProvider<PersonasNotifier, List<Persona>>((ref) {
107 - final box = ref.watch(hiveBoxesProvider).personas;
108 - return PersonasNotifier(box);
109 -});
110 -
111 -// 2. Currently selected persona for new chat
112 -final selectedPersonaProvider = StateProvider<Persona?>((ref) => null);
113 -
114 -// 3. Category filter
115 -final personaCategoryFilterProvider = StateProvider<String?>((ref) => null);
116 -
117 -// 4. Filtered personas based on category
118 -final filteredPersonasProvider = Provider<List<Persona>>((ref) {
119 - final personas = ref.watch(personasProvider);
120 - final category = ref.watch(personaCategoryFilterProvider);
121 - if (category == null) return personas;
122 - return personas.where((p) => p.category == category).toList();
123 -});
124 -```
125 -
126 -### `PersonasNotifier`
127 -
128 -```dart
129 -class PersonasNotifier extends StateNotifier<List<Persona>> {
130 - final Box<Persona> _box;
131 -
132 - PersonasNotifier(this._box) : super([]) {
133 - loadPersonas();
134 - }
135 -
136 - Future<void> loadPersonas();
137 - Future<void> seedBuiltInPersonas();
138 - Future<void> createPersona(Persona persona);
139 - Future<void> updatePersona(Persona persona);
140 - Future<void> deletePersona(String id);
141 - Future<Persona> clonePersona(String id);
142 -}
143 -```
144 -
145 ----
146 -
147 -## Views
148 -
149 -### `persona_list_page.dart`
150 -
151 -**Layout:**
152 -
153 -```
154 -┌──────────────────────────────────────┐
155 -│ Personas [+ ] │
156 -│ │
157 -│ [All] [Coding] [Education] [Creat.] │
158 -│ │
159 -│ ── BUILT-IN ── │
160 -│ ┌──────────────────────────────┐ │
161 -│ │ 🤖 General Assistant │ │
162 -│ │ Helpful, knowledgeable... │ │
163 -│ └──────────────────────────────┘ │
164 -│ ┌──────────────────────────────┐ │
165 -│ │ 🧑‍💻 Code Assistant │ │
166 -│ │ Expert software engineer... │ │
167 -│ └──────────────────────────────┘ │
168 -│ ... │
169 -│ │
170 -│ ── MY PERSONAS ── │
171 -│ ┌──────────────────────────────┐ │
172 -│ │ 🎯 Interview Prep │ │
173 -│ │ Practice tech interviews... │ │
174 -│ └──────────────────────────────┘ │
175 -│ │
176 -│ [Create Custom Persona] │
177 -└──────────────────────────────────────┘
178 -```
179 -
180 -**Features:**
181 -- Category filter chips (All, Coding, Education, Creative, General)
182 -- Sections: "Built-in" and "My Personas"
183 -- Tap card → apply to current/new conversation
184 -- Long-press built-in → "Clone & Edit"
185 -- Long-press custom → Edit, Delete, Clone
186 -
187 ----
188 -
189 -### `create_persona_page.dart`
190 -
191 -**Form fields:**
192 -
193 -| Field | Type | Validation |
194 -| -------------- | --------- | ----------------------------------- |
195 -| Name | Text | Required, 1–50 chars |
196 -| Emoji | Picker | Required, single emoji |
197 -| Category | Dropdown | Optional (Coding, Education, etc.) |
198 -| Description | Text | Optional, 1–200 chars |
199 -| System Prompt | Multiline | Required, 1–4000 chars |
200 -
201 -**System prompt field:**
202 -- Large multiline text area (min 6 lines)
203 -- Character count indicator
204 -- Preview mode toggle
205 -
206 -**Emoji picker:**
207 -- Grid of common emojis
208 -- Categories: People, Animals, Objects, Symbols
209 -
210 -**Optional parameter overrides:**
211 -- Expandable "Advanced Settings"
212 -- Temperature slider (0.0 – 2.0)
213 -- Top P slider (0.0 – 1.0)
214 -
215 ----
216 -
217 -## Components
218 -
219 -### `persona_card.dart`
220 -
221 -- Left: Large emoji avatar (40x40)
222 -- Title: Persona name
223 -- Subtitle: Description or first line of system prompt
224 -- Right: "Built-in" badge
225 -- Bottom: Category chip
226 -- Tap → apply persona
227 -
228 -### `persona_category_chips.dart`
229 -
230 -Horizontal scroll:
231 -- "All" (default)
232 -- "Coding" 🧑‍💻
233 -- "Education" 📚
234 -- "Creative" ✍️
235 -- "General" 🤖
236 -
237 ----
238 -
239 -## Applying a Persona
240 -
241 -### To a New Conversation
242 -
243 -1. Select persona from list
244 -2. Create conversation with `personaId` set
245 -3. Persona `systemPrompt` prepended as first system message
246 -4. If persona has `preferredParams`, override global chat params
247 -5. Chat app bar shows persona emoji
248 -
249 -### To an Existing Conversation
250 -
251 -1. Chat overflow menu → "Change Persona"
252 -2. Opens persona picker
253 -3. Selecting persona adds divider and uses new system prompt
254 -4. "Remove Persona" option
255 -
256 ----
257 -
258 -## First Launch Seeding
259 -
260 -On first launch (empty personas box):
261 -1. Seed all 7 built-in personas into Hive
262 -2. Mark with `isBuiltIn: true`
263 -3. Set "General Assistant" as default
264 -
265 ----
266 -
267 -## Acceptance Criteria
268 -
269 -- [ ] 7 built-in personas available on first launch.
270 -- [ ] User can browse personas with category filtering.
271 -- [ ] User can create custom persona.
272 -- [ ] User can edit and delete custom personas.
273 -- [ ] User can clone built-in persona for customization.
274 -- [ ] Applying persona to new chat sets system prompt.
275 -- [ ] Persona can be changed mid-conversation.
276 -- [ ] Persona emoji appears in chat app bar when active.
277 -- [ ] Parameter overrides from personas are respected.
278 -- [ ] Built-in personas cannot be deleted.
279 -- [ ] Persona data persists across restarts.
plans/06_settings.md deleted
-280
@@ -1,280 +0,0 @@
1 -# Feature 06 — Settings
2 -
3 -> **Phase:** 3 — Features
4 -> **Status:** Pending
5 -> **Depends on:** `00_project_setup`, `07_core_architecture`
6 -
7 ----
8 -
9 -## Goal
10 -
11 -Provide a comprehensive settings screen for configuring chat parameters, UI preferences, default server, and reviewing app/privacy information.
12 -
13 ----
14 -
15 -## Feature Folder Structure
16 -
17 -```
18 -features/settings/
19 -├── data/
20 -│ └── models/ # (Uses core/models/app_settings.dart)
21 -│
22 -├── providers/
23 -│ └── settings_providers.dart
24 -│
25 -└── views/
26 - ├── settings_page.dart # Main settings screen
27 - └── components/
28 - ├── settings_section.dart
29 - ├── settings_slider.dart
30 - ├── settings_toggle.dart
31 - ├── settings_dropdown.dart
32 - ├── theme_toggle.dart
33 - └── font_size_preview.dart
34 -```
35 -
36 ----
37 -
38 -## Dependencies Used
39 -
40 -| Package | Purpose |
41 -| ------------------- | --------------------------- |
42 -| `hive_ce` | Persist settings locally |
43 -| `flutter_riverpod` | Settings state management |
44 -
45 ----
46 -
47 -## Data Model
48 -
49 -`AppSettings` is defined in `core/models/app_settings.dart`.
50 -
51 ----
52 -
53 -## Providers
54 -
55 -Location: `features/settings/providers/settings_providers.dart`
56 -
57 -### Provider Organization
58 -
59 -```dart
60 -// 1. Settings state
61 -final settingsProvider = StateNotifierProvider<SettingsNotifier, AppSettings>((ref) {
62 - return SettingsNotifier(ref.read(hiveBoxesProvider).settings);
63 -});
64 -
65 -// 2. Convenience providers
66 -final themeModeProvider = Provider<ThemeMode>((ref) {
67 - return ref.watch(settingsProvider).themeMode;
68 -});
69 -
70 -final fontSizeProvider = Provider<double>((ref) {
71 - return ref.watch(settingsProvider).fontSize;
72 -});
73 -
74 -final chatParamsProvider = Provider<ChatParameters>((ref) {
75 - final settings = ref.watch(settingsProvider);
76 - return ChatParameters(
77 - temperature: settings.temperature,
78 - topP: settings.topP,
79 - maxTokens: settings.maxTokens,
80 - contextLength: settings.contextLength,
81 - );
82 -});
83 -```
84 -
85 -### `SettingsNotifier`
86 -
87 -```dart
88 -class SettingsNotifier extends StateNotifier<AppSettings> {
89 - final Box<AppSettings> _box;
90 -
91 - SettingsNotifier(this._box) : super(AppSettings()) {
92 - loadSettings();
93 - }
94 -
95 - Future<void> loadSettings();
96 - void setTemperature(double value);
97 - void setTopP(double value);
98 - void setMaxTokens(int value);
99 - void setContextLength(int value);
100 - void setThemeMode(ThemeMode mode);
101 - void setFontSize(double size);
102 - void setHapticFeedback(bool enabled);
103 - void setSendOnEnter(bool enabled);
104 - void setShowSystemMessages(bool show);
105 - void setDefaultServer(String? serverId);
106 - void setAutoGenerateTitle(bool enabled);
107 - void setStreamingEnabled(bool enabled);
108 - void setDefaultPersona(String? personaId);
109 - void setShowDataIndicator(bool show);
110 - Future<void> resetToDefaults();
111 - Future<void> _persist();
112 -}
113 -```
114 -
115 ----
116 -
117 -## Views
118 -
119 -### `settings_page.dart`
120 -
121 -**Layout:**
122 -
123 -```
124 -┌──────────────────────────────────────┐
125 -│ ← Settings │
126 -│ │
127 -│ ── CHAT PARAMETERS ── │
128 -│ │
129 -│ Temperature 0.7 │
130 -│ ├──────────●──────────────────┤ │
131 -│ Controls randomness. Lower = │
132 -│ focused, Higher = creative. │
133 -│ │
134 -│ Top P 0.9 │
135 -│ ├──────────────────●──────────┤ │
136 -│ Nucleus sampling threshold. │
137 -│ │
138 -│ Max Tokens 2048 │
139 -│ [ 2048 ] │
140 -│ Maximum response length. │
141 -│ │
142 -│ Context Length 4096 │
143 -│ [ 4096 ] │
144 -│ Conversation history window. │
145 -│ │
146 -│ ── APPEARANCE ── │
147 -│ │
148 -│ Theme │
149 -│ [System] [Light] [Dark ✓] │
150 -│ │
151 -│ Font Size 16 │
152 -│ A ├────────●──────────────┤ A │
153 -│ Preview: The quick brown fox... │
154 -│ │
155 -│ ── BEHAVIOR ── │
156 -│ │
157 -│ Streaming Responses [✓] │
158 -│ Auto-generate Titles [✓] │
159 -│ Send on Enter [ ] │
160 -│ Show System Messages [ ] │
161 -│ Haptic Feedback [✓] │
162 -│ │
163 -│ ── DEFAULT SERVER ── │
164 -│ │
165 -│ [My Desktop LM Studio ▼] │
166 -│ │
167 -│ ── DEFAULT PERSONA ── │
168 -│ │
169 -│ [🤖 General Assistant ▼] │
170 -│ │
171 -│ ── PRIVACY ── │
172 -│ │
173 -│ Show Data Indicator [✓] │
174 -│ "LocalMind never sees your data" │
175 -│ │
176 -│ ── DATA MANAGEMENT ── │
177 -│ │
178 -│ [Export All Conversations] │
179 -│ [Delete All Conversations] ⚠️ │
180 -│ [Reset Settings to Defaults] │
181 -│ │
182 -│ ── ABOUT ── │
183 -│ │
184 -│ LocalMind v1.0.0 │
185 -│ Your AI. Your Device. Your Rules. │
186 -│ │
187 -│ [Privacy Policy] │
188 -│ [Open Source Licenses] │
189 -│ │
190 -└──────────────────────────────────────┘
191 -```
192 -
193 ----
194 -
195 -## Components
196 -
197 -### `settings_section.dart`
198 -
199 -Groups related settings with uppercase section header.
200 -
201 -### `settings_slider.dart`
202 -
203 -- Label with current value display
204 -- Slider control
205 -- Description text below
206 -- Used for: Temperature, Top P, Font Size
207 -
208 -### `settings_toggle.dart`
209 -
210 -- Label on left
211 -- Switch on right
212 -- Used for: Streaming, Auto-title, Send on Enter, etc.
213 -
214 -### `settings_dropdown.dart`
215 -
216 -- Label on left
217 -- Dropdown selector on right
218 -- Used for: Default Server, Default Persona
219 -
220 -### `theme_toggle.dart`
221 -
222 -Visual toggle between System, Light, Dark with icons (🌐 ☀️ 🌙).
223 -
224 -### `font_size_preview.dart`
225 -
226 -Live preview text that updates as font size slider moves.
227 -
228 ----
229 -
230 -## Chat Parameter Explanations
231 -
232 -| Parameter | Description |
233 -| --------------- | ------------------------------------------------------------ |
234 -| Temperature | Controls randomness. 0 = deterministic, 2 = very random. |
235 -| Top P | Nucleus sampling. 0.1 = conservative, 1.0 = all options. |
236 -| Max Tokens | Maximum number of tokens in the response. |
237 -| Context Length | How many tokens of conversation history to send. |
238 -
239 ----
240 -
241 -## Dangerous Actions
242 -
243 -### Delete All Conversations
244 -
245 -1. Tap "Delete All Conversations"
246 -2. Show confirmation with red accent
247 -3. On confirm: delete all, clear providers, navigate to empty chat
248 -
249 -### Reset Settings
250 -
251 -1. Tap "Reset Settings to Defaults"
252 -2. Show confirmation dialog
253 -3. On confirm: reset to factory defaults
254 -
255 ----
256 -
257 -## Settings Persistence
258 -
259 -- All settings saved immediately (no "Save" button)
260 -- `_persist()` called after every state update
261 -- On launch, `loadSettings()` reads from Hive or creates defaults
262 -
263 ----
264 -
265 -## Acceptance Criteria
266 -
267 -- [ ] Temperature slider works and updates chat behavior.
268 -- [ ] Top P slider works and updates chat behavior.
269 -- [ ] Max Tokens input validates and restricts to bounds.
270 -- [ ] Context Length input validates and restricts to bounds.
271 -- [ ] Theme toggle switches System/Light/Dark and persists.
272 -- [ ] Font size slider adjusts chat text size with live preview.
273 -- [ ] All toggle switches work and persist.
274 -- [ ] Default server dropdown shows all saved servers.
275 -- [ ] Default persona dropdown shows all personas.
276 -- [ ] "Delete All Conversations" works with confirmation.
277 -- [ ] "Reset Settings" restores all defaults.
278 -- [ ] All settings persist across restarts.
279 -- [ ] Settings screen scrolls smoothly.
280 -- [ ] Each parameter has helpful description.
plans/07_core_architecture.md deleted
-578
@@ -1,578 +0,0 @@
1 -# Feature 07 — Core Architecture (Models, Services, Storage, Providers)
2 -
3 -> **Phase:** 0–1
4 -> **Status:** Pending
5 -> **Depends on:** None (foundational)
6 -> **Replaces:** Original `core/` structure
7 -
8 ----
9 -
10 -## Goal
11 -
12 -Define the core data models, service interfaces, storage layer, and global providers that all features depend on. This is the application's backbone.
13 -
14 ----
15 -
16 -## Overview
17 -
18 -This document details the `core/` layer — code that is shared across multiple features. Each feature's internal structure is defined in its respective plan file.
19 -
20 -### Core Folder Structure
21 -
22 -```
23 -core/
24 -├── constants/
25 -│ └── app_constants.dart # App-wide constants
26 -├── models/
27 -│ ├── server.dart # Server model + enums
28 -│ ├── message.dart # Message model + enums
29 -│ ├── conversation.dart # Conversation model
30 -│ ├── persona.dart # Persona model
31 -│ ├── model_info.dart # LLM model metadata
32 -│ ├── chat_parameters.dart # Chat parameter config
33 -│ └── app_settings.dart # Settings model
34 -├── providers/
35 -│ └── app_providers.dart # Global Riverpod providers
36 -├── repository/
37 -│ ├── storage/
38 -│ │ ├── hive_initializer.dart
39 -│ │ ├── hive_keys.dart
40 -│ │ └── hive_boxes.dart
41 -│ ├── server/
42 -│ │ └── server_repository.dart
43 -│ └── chat/
44 -│ ├── chat_service.dart
45 -│ ├── lm_studio_chat_service.dart
46 -│ ├── ollama_chat_service.dart
47 -│ └── openrouter_chat_service.dart
48 -├── routes/
49 -│ └── app_router.dart
50 -├── theme/
51 -│ ├── app_theme.dart
52 -│ ├── colors.dart
53 -│ └── typography.dart
54 -├── components/
55 -│ ├── app_button.dart
56 -│ ├── app_card.dart
57 -│ └── app_text_field.dart
58 -└── utils/
59 - ├── uuid.dart
60 - └── date_utils.dart
61 -```
62 -
63 ----
64 -
65 -## Hive Type IDs
66 -
67 -| Type ID | Model |
68 -| ------- | --------------- |
69 -| 0 | Server |
70 -| 1 | Message |
71 -| 2 | Conversation |
72 -| 3 | Persona |
73 -| 4 | AppSettings |
74 -| 10 | ServerType |
75 -| 11 | ConnectionStatus|
76 -| 12 | MessageRole |
77 -| 13 | MessageStatus |
78 -
79 ----
80 -
81 -## Models — Full Specifications
82 -
83 -### `core/models/server.dart`
84 -
85 -```dart
86 -@HiveType(typeId: 10)
87 -enum ServerType { lmStudio, ollama, openRouter }
88 -
89 -@HiveType(typeId: 11)
90 -enum ConnectionStatus { connected, disconnected, checking, error }
91 -
92 -@HiveType(typeId: 0)
93 -class Server extends HiveObject {
94 - @HiveField(0)
95 - String id; // UUID
96 -
97 - @HiveField(1)
98 - String name; // User-defined label
99 -
100 - @HiveField(2)
101 - ServerType type;
102 -
103 - @HiveField(3)
104 - String host; // IP address or hostname
105 -
106 - @HiveField(4)
107 - int port; // Port number
108 -
109 - @HiveField(5)
110 - String? apiKey; // Required for OpenRouter
111 -
112 - @HiveField(6)
113 - bool isDefault;
114 -
115 - @HiveField(7)
116 - DateTime createdAt;
117 -
118 - @HiveField(8)
119 - DateTime lastConnectedAt;
120 -
121 - @HiveField(9)
122 - ConnectionStatus status;
123 -
124 - String get baseUrl {
125 - if (type == ServerType.openRouter) return 'https://openrouter.ai/api/v1';
126 - return 'http://$host:$port';
127 - }
128 -
129 - String get chatEndpoint {
130 - switch (type) {
131 - case ServerType.lmStudio: return '$baseUrl/v1/chat/completions';
132 - case ServerType.ollama: return '$baseUrl/api/chat';
133 - case ServerType.openRouter: return '$baseUrl/chat/completions';
134 - }
135 - }
136 -
137 - String get modelsEndpoint {
138 - switch (type) {
139 - case ServerType.lmStudio: return '$baseUrl/v1/models';
140 - case ServerType.ollama: return '$baseUrl/api/tags';
141 - case ServerType.openRouter: return '$baseUrl/models';
142 - }
143 - }
144 -
145 - // ... fromMap(), toMap(), copyWith()
146 -}
147 -```
148 -
149 -### `core/models/message.dart`
150 -
151 -```dart
152 -@HiveType(typeId: 12)
153 -enum MessageRole { user, assistant, system }
154 -
155 -@HiveType(typeId: 13)
156 -enum MessageStatus { sending, streaming, complete, error }
157 -
158 -@HiveType(typeId: 1)
159 -class Message extends HiveObject {
160 - @HiveField(0)
161 - String id; // UUID
162 -
163 - @HiveField(1)
164 - String conversationId; // FK to Conversation
165 -
166 - @HiveField(2)
167 - MessageRole role;
168 -
169 - @HiveField(3)
170 - String content; // Message text (markdown)
171 -
172 - @HiveField(4)
173 - DateTime createdAt;
174 -
175 - @HiveField(5)
176 - MessageStatus status;
177 -
178 - @HiveField(6)
179 - String? modelId; // Which model generated this
180 -
181 - @HiveField(7)
182 - int? tokenCount;
183 -
184 - @HiveField(8)
185 - String? errorMessage;
186 -
187 - @HiveField(9)
188 - List<String>? attachmentPaths;
189 -
190 - @HiveField(10)
191 - int? generationTimeMs;
192 -
193 - // ... fromMap(), toMap(), copyWith()
194 -}
195 -```
196 -
197 -### `core/models/conversation.dart`
198 -
199 -```dart
200 -@HiveType(typeId: 2)
201 -class Conversation extends HiveObject {
202 - @HiveField(0)
203 - String id;
204 -
205 - @HiveField(1)
206 - String title;
207 -
208 - @HiveField(2)
209 - DateTime createdAt;
210 -
211 - @HiveField(3)
212 - DateTime updatedAt;
213 -
214 - @HiveField(4)
215 - bool isPinned;
216 -
217 - @HiveField(5)
218 - String? personaId;
219 -
220 - @HiveField(6)
221 - String? serverId;
222 -
223 - @HiveField(7)
224 - String? modelId;
225 -
226 - @HiveField(8)
227 - int messageCount;
228 -
229 - @HiveField(9)
230 - String? lastMessagePreview;
231 -
232 - @HiveField(10)
233 - String? systemPrompt;
234 -
235 - // ... fromMap(), toMap(), copyWith()
236 -}
237 -```
238 -
239 -### `core/models/persona.dart`
240 -
241 -```dart
242 -@HiveType(typeId: 3)
243 -class Persona extends HiveObject {
244 - @HiveField(0)
245 - String id;
246 -
247 - @HiveField(1)
248 - String name;
249 -
250 - @HiveField(2)
251 - String emoji;
252 -
253 - @HiveField(3)
254 - String systemPrompt;
255 -
256 - @HiveField(4)
257 - String? description;
258 -
259 - @HiveField(5)
260 - bool isBuiltIn;
261 -
262 - @HiveField(6)
263 - DateTime createdAt;
264 -
265 - @HiveField(7)
266 - DateTime updatedAt;
267 -
268 - @HiveField(8)
269 - String? category;
270 -
271 - @HiveField(9)
272 - Map<String, dynamic>? preferredParams;
273 -
274 - // ... fromMap(), toMap(), copyWith()
275 -}
276 -```
277 -
278 -### `core/models/app_settings.dart`
279 -
280 -```dart
281 -@HiveType(typeId: 4)
282 -class AppSettings extends HiveObject {
283 - @HiveField(0)
284 - double temperature;
285 -
286 - @HiveField(1)
287 - double topP;
288 -
289 - @HiveField(2)
290 - int maxTokens;
291 -
292 - @HiveField(3)
293 - int contextLength;
294 -
295 - @HiveField(4)
296 - ThemeMode themeMode;
297 -
298 - @HiveField(5)
299 - double fontSize;
300 -
301 - @HiveField(6)
302 - bool showSystemMessages;
303 -
304 - @HiveField(7)
305 - bool hapticFeedbackEnabled;
306 -
307 - @HiveField(8)
308 - bool sendOnEnter;
309 -
310 - @HiveField(9)
311 - String? defaultServerId;
312 -
313 - @HiveField(10)
314 - bool showDataIndicator;
315 -
316 - @HiveField(11)
317 - bool autoGenerateTitle;
318 -
319 - @HiveField(12)
320 - bool streamingEnabled;
321 -
322 - @HiveField(13)
323 - String? defaultPersonaId;
324 -
325 - // ... fromMap(), toMap(), copyWith()
326 -}
327 -```
328 -
329 -### `core/models/model_info.dart` (Non-Hive)
330 -
331 -```dart
332 -class ModelInfo {
333 - final String id;
334 - final String name;
335 - final String? description;
336 - final int? parameterCount;
337 - final int? contextLength;
338 - final int? fileSize;
339 - final String? quantization;
340 - final String? architecture;
341 - final ServerType serverType;
342 - final String serverId;
343 - final DateTime? modifiedAt;
344 -
345 - // ... fromMap(), toMap(), copyWith()
346 -}
347 -```
348 -
349 -### `core/models/chat_parameters.dart` (Non-Hive)
350 -
351 -```dart
352 -class ChatParameters {
353 - final double temperature;
354 - final double topP;
355 - final int maxTokens;
356 - final int contextLength;
357 -
358 - factory ChatParameters.defaults() => ChatParameters(
359 - temperature: 0.7,
360 - topP: 0.9,
361 - maxTokens: 2048,
362 - contextLength: 4096,
363 - );
364 -
365 - // ... copyWith()
366 -}
367 -```
368 -
369 ----
370 -
371 -## Repository Layer
372 -
373 -### `core/repository/storage/hive_keys.dart`
374 -
375 -```dart
376 -class HiveKeys {
377 - static const String servers = 'servers';
378 - static const String messages = 'messages';
379 - static const String conversations = 'conversations';
380 - static const String personas = 'personas';
381 - static const String settings = 'settings';
382 -}
383 -```
384 -
385 -### `core/repository/storage/hive_boxes.dart`
386 -
387 -```dart
388 -class HiveBoxes {
389 - final Box<Server> servers;
390 - final Box<Message> messages;
391 - final Box<Conversation> conversations;
392 - final Box<Persona> personas;
393 - final Box<AppSettings> settings;
394 -
395 - const HiveBoxes({
396 - required this.servers,
397 - required this.messages,
398 - required this.conversations,
399 - required this.personas,
400 - required this.settings,
401 - });
402 -}
403 -```
404 -
405 -### `core/repository/storage/hive_initializer.dart`
406 -
407 -```dart
408 -class HiveInitializer {
409 - static Future<HiveBoxes> initialize() async {
410 - final appDir = await getApplicationDocumentsDirectory();
411 - await Hive.initFlutter(appDir.path);
412 -
413 - // Register ALL adapters
414 - Hive.registerAdapter(ServerAdapter());
415 - Hive.registerAdapter(ServerTypeAdapter());
416 - Hive.registerAdapter(ConnectionStatusAdapter());
417 - Hive.registerAdapter(MessageAdapter());
418 - Hive.registerAdapter(MessageRoleAdapter());
419 - Hive.registerAdapter(MessageStatusAdapter());
420 - Hive.registerAdapter(ConversationAdapter());
421 - Hive.registerAdapter(PersonaAdapter());
422 - Hive.registerAdapter(AppSettingsAdapter());
423 -
424 - final servers = await Hive.openBox<Server>(HiveKeys.servers);
425 - final messages = await Hive.openBox<Message>(HiveKeys.messages);
426 - final conversations = await Hive.openBox<Conversation>(HiveKeys.conversations);
427 - final personas = await Hive.openBox<Persona>(HiveKeys.personas);
428 - final settings = await Hive.openBox<AppSettings>(HiveKeys.settings);
429 -
430 - return HiveBoxes(
431 - servers: servers,
432 - messages: messages,
433 - conversations: conversations,
434 - personas: personas,
435 - settings: settings,
436 - );
437 - }
438 -}
439 -```
440 -
441 ----
442 -
443 -## Services
444 -
445 -### `core/repository/chat/chat_service.dart`
446 -
447 -```dart
448 -abstract class ChatService {
449 - Stream<String> sendMessage({
450 - required Server server,
451 - required String modelId,
452 - required List<Message> messages,
453 - required ChatParameters params,
454 - });
455 -
456 - void cancelStream();
457 -
458 - static ChatService forServer(ServerType type, Dio dio) {
459 - switch (type) {
460 - case ServerType.lmStudio: return LMStudioChatService(dio);
461 - case ServerType.ollama: return OllamaChatService(dio);
462 - case ServerType.openRouter: return OpenRouterChatService(dio);
463 - }
464 - }
465 -}
466 -```
467 -
468 -**Implementations:**
469 -- `lm_studio_chat_service.dart` — OpenAI-compatible SSE streaming
470 -- `ollama_chat_service.dart` — NDJSON streaming
471 -- `openrouter_chat_service.dart` — OpenAI-compatible SSE with API key auth
472 -
473 -### `core/repository/server/server_repository.dart`
474 -
475 -```dart
476 -class ServerRepository {
477 - final Dio _dio;
478 -
479 - Future<bool> testConnection(Server server);
480 - Future<List<ModelInfo>> fetchModels(Server server);
481 - Future<int?> pingServer(Server server);
482 -}
483 -```
484 -
485 ----
486 -
487 -## Providers
488 -
489 -### `core/providers/app_providers.dart`
490 -
491 -```dart
492 -// Hive boxes (initialized in main.dart)
493 -final hiveBoxesProvider = Provider<HiveBoxes>((ref) {
494 - throw UnimplementedError('Must be overridden with actual HiveBoxes');
495 -});
496 -
497 -// Dio HTTP client
498 -final dioProvider = Provider<Dio>((ref) {
499 - return Dio(BaseOptions(
500 - connectTimeout: Duration(milliseconds: AppConstants.connectionTimeoutMs),
501 - receiveTimeout: Duration(milliseconds: AppConstants.receiveTimeoutMs),
502 - ));
503 -});
504 -
505 -// Theme
506 -final themeModeProvider = StateNotifierProvider<ThemeModeNotifier, ThemeMode>((ref) {
507 - return ThemeModeNotifier();
508 -});
509 -
510 -// Active Server
511 -final activeServerProvider = StateNotifierProvider<ActiveServerNotifier, Server?>((ref) {
512 - return ActiveServerNotifier();
513 -});
514 -
515 -// Active Conversation
516 -final activeConversationProvider = StateNotifierProvider<ActiveConversationNotifier, Conversation?>((ref) {
517 - return ActiveConversationNotifier();
518 -});
519 -```
520 -
521 ----
522 -
523 -## Utilities
524 -
525 -### `core/utils/uuid.dart`
526 -
527 -```dart
528 -String generateUuid() {
529 - final random = Random.secure();
530 - final bytes = List<int>.generate(16, (_) => random.nextInt(256));
531 - bytes[6] = (bytes[6] & 0x0f) | 0x40;
532 - bytes[8] = (bytes[8] & 0x3f) | 0x80;
533 - return [
534 - bytes.sublist(0, 4), bytes.sublist(4, 6),
535 - bytes.sublist(6, 8), bytes.sublist(8, 10),
536 - bytes.sublist(10, 16),
537 - ].map((b) => b.map((e) => e.toRadixString(16).padLeft(2, '0')).join())
538 - .join('-');
539 -}
540 -```
541 -
542 ----
543 -
544 -## Error Handling Strategy
545 -
546 -```dart
547 -class AppException implements Exception {
548 - final String message;
549 - final String? technicalDetail;
550 - final AppErrorType type;
551 -}
552 -
553 -enum AppErrorType {
554 - connectionRefused,
555 - timeout,
556 - unauthorized,
557 - serverError,
558 - modelNotFound,
559 - contextOverflow,
560 - streamInterrupted,
561 - rateLimited,
562 - unknown,
563 -}
564 -```
565 -
566 -Each `AppErrorType` maps to a user-friendly message (see `02_chat_interface.md` error table).
567 -
568 ----
569 -
570 -## Acceptance Criteria
571 -
572 -- [ ] All Hive models compile and their adapters are generated.
573 -- [ ] `HiveInitializer` successfully opens all boxes on app launch.
574 -- [ ] `ChatService` correctly routes to the right implementation per server type.
575 -- [ ] `ServerRepository` can test connections and fetch models.
576 -- [ ] All providers are defined and accessible.
577 -- [ ] `AppException` provides user-friendly error messages.
578 -- [ ] UUID generation produces valid unique IDs.
plans/08_design_system.md deleted
-340
@@ -1,340 +0,0 @@
1 -# Feature 08 — Design System
2 -
3 -> **Phase:** 0–1
4 -> **Status:** Pending
5 -> **Depends on:** `00_project_setup`
6 -
7 ----
8 -
9 -## Goal
10 -
11 -Define the visual language and reusable components for LocalMind using ShadCN-inspired principles. Ensure a premium, dark-first, and highly polished user experience.
12 -
13 ----
14 -
15 -## Location
16 -
17 -Design system components live in the `core/` layer since they are shared across all features:
18 -
19 -```
20 -core/
21 -├── theme/
22 -│ ├── app_theme.dart
23 -│ ├── colors.dart
24 -│ └── typography.dart
25 -└── components/
26 - ├── app_button.dart
27 - ├── app_card.dart
28 - ├── app_text_field.dart
29 - ├── app_sheet.dart
30 - ├── app_dialog.dart
31 - └── app_loading_indicator.dart
32 -```
33 -
34 ----
35 -
36 -## Core Principles
37 -
38 -1. **High Contrast & Legibility**: Text must be readable against backgrounds. Use neutral grays, not pure black.
39 -2. **Density**: Compact enough for information density, spacious enough for touch targets.
40 -3. **Feedback**: Every interaction should have visual (hover, press) and tactile (haptic) feedback.
41 -4. **Consistency**: Use the same spacing, border radius, and typography scales everywhere.
42 -5. **ShadCN Aesthetic**: Clean borders, subtle surfaces, neutral colors with a single vibrant accent.
43 -
44 ----
45 -
46 -## Color Tokens
47 -
48 -Location: `core/theme/colors.dart`
49 -
50 -```dart
51 -class AppColors {
52 - // --- Dark Theme (Default) ---
53 - static const Color darkBackground = Color(0xFF0A0A0A);
54 - static const Color darkSurface = Color(0xFF171717);
55 - static const Color darkBorder = Color(0xFF262626);
56 - static const Color darkPrimaryText = Color(0xFFFAFAFA);
57 - static const Color darkMutedText = Color(0xFFA1A1AA);
58 - static const Color darkAccent = Color(0xFF3B82F6);
59 - static const Color darkError = Color(0xFFEF4444);
60 - static const Color darkSuccess = Color(0xFF22C55E);
61 -
62 - // --- Light Theme ---
63 - static const Color lightBackground = Color(0xFFFAFAFA);
64 - static const Color lightSurface = Color(0xFFFFFFFF);
65 - static const Color lightBorder = Color(0xFFE5E5E5);
66 - static const Color lightPrimaryText = Color(0xFF0A0A0A);
67 - static const Color lightMutedText = Color(0xFF71717A);
68 - static const Color lightAccent = Color(0xFF2563EB);
69 - static const Color lightError = Color(0xFFDC2626);
70 - static const Color lightSuccess = Color(0xFF16A34A);
71 -}
72 -```
73 -
74 ----
75 -
76 -## Typography
77 -
78 -Location: `core/theme/typography.dart`
79 -
80 -Font Family: `Inter`
81 -Code Font: `Fira Code` or `JetBrains Mono`
82 -
83 -```dart
84 -class AppTypography {
85 - static const String fontFamily = 'Inter';
86 - static const String codeFontFamily = 'FiraCode';
87 -
88 - // Text styles using Flutter's TextTheme
89 - static TextTheme get textTheme => TextTheme(
90 - displayLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
91 - displayMedium: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
92 - displaySmall: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
93 - headlineMedium: TextStyle(fontSize: 20, fontWeight: FontWeight.w600),
94 - titleLarge: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
95 - titleMedium: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
96 - bodyLarge: TextStyle(fontSize: 16, fontWeight: FontWeight.normal),
97 - bodyMedium: TextStyle(fontSize: 14, fontWeight: FontWeight.normal),
98 - bodySmall: TextStyle(fontSize: 12, fontWeight: FontWeight.normal),
99 - labelLarge: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
100 - labelSmall: TextStyle(fontSize: 11, fontWeight: FontWeight.w500),
101 - );
102 -}
103 -```
104 -
105 ----
106 -
107 -## Theme Data
108 -
109 -Location: `core/theme/app_theme.dart`
110 -
111 -```dart
112 -class AppTheme {
113 - static ThemeData get darkTheme => ThemeData(
114 - useMaterial3: true,
115 - brightness: Brightness.dark,
116 - scaffoldBackgroundColor: AppColors.darkBackground,
117 - colorScheme: ColorScheme.dark(
118 - surface: AppColors.darkSurface,
119 - primary: AppColors.darkAccent,
120 - error: AppColors.darkError,
121 - ),
122 - textTheme: AppTypography.textTheme.apply(
123 - bodyColor: AppColors.darkPrimaryText,
124 - displayColor: AppColors.darkPrimaryText,
125 - ),
126 - cardTheme: CardTheme(
127 - color: AppColors.darkSurface,
128 - shape: RoundedRectangleBorder(
129 - borderRadius: BorderRadius.circular(8),
130 - side: BorderSide(color: AppColors.darkBorder),
131 - ),
132 - ),
133 - inputDecorationTheme: InputDecorationTheme(
134 - border: OutlineInputBorder(
135 - borderRadius: BorderRadius.circular(8),
136 - borderSide: BorderSide(color: AppColors.darkBorder),
137 - ),
138 - filled: true,
139 - fillColor: AppColors.darkSurface,
140 - ),
141 - dividerTheme: DividerThemeData(
142 - color: AppColors.darkBorder,
143 - thickness: 1,
144 - ),
145 - );
146 -
147 - static ThemeData get lightTheme => ThemeData(
148 - useMaterial3: true,
149 - brightness: Brightness.light,
150 - scaffoldBackgroundColor: AppColors.lightBackground,
151 - colorScheme: ColorScheme.light(
152 - surface: AppColors.lightSurface,
153 - primary: AppColors.lightAccent,
154 - error: AppColors.lightError,
155 - ),
156 - textTheme: AppTypography.textTheme.apply(
157 - bodyColor: AppColors.lightPrimaryText,
158 - displayColor: AppColors.lightPrimaryText,
159 - ),
160 - cardTheme: CardTheme(
161 - color: AppColors.lightSurface,
162 - shape: RoundedRectangleBorder(
163 - borderRadius: BorderRadius.circular(8),
164 - side: BorderSide(color: AppColors.lightBorder),
165 - ),
166 - ),
167 - inputDecorationTheme: InputDecorationTheme(
168 - border: OutlineInputBorder(
169 - borderRadius: BorderRadius.circular(8),
170 - borderSide: BorderSide(color: AppColors.lightBorder),
171 - ),
172 - filled: true,
173 - fillColor: AppColors.lightSurface,
174 - ),
175 - dividerTheme: DividerThemeData(
176 - color: AppColors.lightBorder,
177 - thickness: 1,
178 - ),
179 - );
180 -}
181 -```
182 -
183 ----
184 -
185 -## Components
186 -
187 -Location: `core/components/`
188 -
189 -### `app_button.dart`
190 -
191 -```dart
192 -enum AppButtonVariant { primary, outline, ghost, destructive }
193 -
194 -class AppButton extends StatelessWidget {
195 - const AppButton({
196 - super.key,
197 - required this.onPressed,
198 - required this.child,
199 - this.variant = AppButtonVariant.primary,
200 - this.isLoading = false,
201 - this.icon,
202 - });
203 -
204 - final VoidCallback? onPressed;
205 - final Widget child;
206 - final AppButtonVariant variant;
207 - final bool isLoading;
208 - final Widget? icon;
209 -}
210 -```
211 -
212 -**Variants:**
213 -- **Primary**: Filled background (accent color), white text
214 -- **Outline**: Transparent background, border, text color
215 -- **Ghost**: Transparent, text only, hover state shows surface tone
216 -- **Destructive**: Red accent for dangerous actions
217 -
218 -### `app_card.dart`
219 -
220 -```dart
221 -class AppCard extends StatelessWidget {
222 - const AppCard({
223 - super.key,
224 - required this.child,
225 - this.padding,
226 - this.onTap,
227 - });
228 -
229 - final Widget child;
230 - final EdgeInsetsGeometry? padding;
231 - final VoidCallback? onTap;
232 -}
233 -```
234 -
235 -- Background: `darkSurface`
236 -- Border: `darkBorder`
237 -- Border radius: `8px`
238 -- Optional tap handler with ripple
239 -
240 -### `app_text_field.dart`
241 -
242 -```dart
243 -class AppTextField extends StatelessWidget {
244 - const AppTextField({
245 - super.key,
246 - this.controller,
247 - this.hint,
248 - this.label,
249 - this.error,
250 - this.prefixIcon,
251 - this.suffixIcon,
252 - this.onChanged,
253 - this.onSubmitted,
254 - this.obscureText = false,
255 - this.maxLines = 1,
256 - });
257 -
258 - final TextEditingController? controller;
259 - final String? hint;
260 - final String? label;
261 - final String? error;
262 - final Widget? prefixIcon;
263 - final Widget? suffixIcon;
264 - final ValueChanged<String>? onChanged;
265 - final ValueChanged<String>? onSubmitted;
266 - final bool obscureText;
267 - final int maxLines;
268 -}
269 -```
270 -
271 -- Border: `darkBorder`
272 -- Background: transparent or `darkSurface` (focused)
273 -- Error text below when provided
274 -
275 -### `app_sheet.dart`
276 -
277 -Bottom sheet wrapper with drag handle, padding, and standard close button.
278 -
279 -### `app_dialog.dart`
280 -
281 -Dialog wrapper with title, description, actions, and standard padding/radius.
282 -
283 -### `app_loading_indicator.dart`
284 -
285 -Minimalist circular progress indicator, accent color.
286 -
287 ----
288 -
289 -## Haptic Feedback
290 -
291 -**Triggers:**
292 -- **Light impact**: Toggling switches, expanding sections
293 -- **Medium impact**: Sending message, deleting, saving
294 -- **Success impact**: Successful server test, saving persona
295 -- **Error impact**: Connection failure, invalid input
296 -
297 ----
298 -
299 -## Animations
300 -
301 -Location: `core/utils/animations.dart` (or inline)
302 -
303 -1. **Page Transitions**: `CupertinoPageRoute` or `FadeUpwardsPageTransitionsBuilder`
304 -2. **Loading Spinners**: Minimalist circular, accent color
305 -3. **Typing Indicator**: 3 bouncing dots
306 -4. **Blinking Cursor**: For streaming text
307 -5. **Hover States**: Scale `1.02x` or opacity change
308 -
309 ----
310 -
311 -## Spacing & Radius Constants
312 -
313 -```dart
314 -class AppSizes {
315 - static const double paddingXs = 4;
316 - static const double paddingSm = 8;
317 - static const double paddingMd = 16;
318 - static const double paddingLg = 24;
319 - static const double paddingXl = 32;
320 -
321 - static const double radiusSm = 4;
322 - static const double radiusMd = 8;
323 - static const double radiusLg = 12;
324 - static const double radiusXl = 16;
325 -
326 - static const double iconSm = 16;
327 - static const double iconMd = 24;
328 - static const double iconLg = 32;
329 -}
330 -```
331 -
332 ----
333 -
334 -## Acceptance Criteria
335 -
336 -- [ ] `AppColors` and typography defined and integrated into `ThemeData`.
337 -- [ ] Core components (Buttons, Inputs, Cards) created and used consistently.
338 -- [ ] Haptic feedback map defined and applied to key interactions.
339 -- [ ] Transitions and loading states feel premium and responsive.
340 -- [ ] Dark and Light mode fully supported and verifiable via system toggle.
plans/09_extended_features.md deleted
-187
@@ -1,187 +0,0 @@
1 -# Feature 09 — Extended Features
2 -
3 -> **Phase:** 4 — Extended
4 -> **Status:** Pending
5 -> **Depends on:** `02_chat_interface`
6 -
7 ----
8 -
9 -## Goal
10 -
11 -Add advanced capabilities that elevate LocalMind from a basic chat client to a premium AI assistant tool.
12 -
13 ----
14 -
15 -## Features
16 -
17 -### 1. AI Voice (Text-to-Speech)
18 -
19 -**Package:** `flutter_tts`
20 -
21 -**Implementation:**
22 -- Add "Read Aloud" action to assistant messages (in action bar)
23 -- Highlight text as it is being read (if feasible)
24 -- Global setting to auto-read assistant responses
25 -- TTS stops when navigating away or starting new generation
26 -- Voice selection in settings (OS-dependent)
27 -
28 -### 2. Context Smart Replies
29 -
30 -**Implementation:**
31 -- When assistant message completes, generate 2-3 suggested follow-up prompts
32 -- Show as selectable chips above input bar
33 -- Generation method: lightweight background prompt or rule-based suggestions
34 -- Rule-based fallbacks: "Tell me more", "Explain simply", "Give an example"
35 -
36 -### 3. Multimodal Support (Images/Documents)
37 -
38 -**Packages:** `file_picker`, `image_picker`
39 -
40 -**Implementation:**
41 -- Update `Message` model to support `attachmentPaths`
42 -- Add paperclip `[📎]` icon to `ChatInputBar`
43 -- Allow selecting images (`.png`, `.jpg`)
44 -- If model is multimodal (LLaVA, GPT-4o, Claude 3.5 Sonnet), encode image as Base64
45 -- Display image thumbnails in user chat bubble
46 -
47 -### 4. Tablet & Desktop Optimized Layout
48 -
49 -**Implementation:**
50 -- Detect screen width (`MediaQuery.of(context).size.width`)
51 -- If width > 600px (tablet) or 900px (desktop):
52 - - Pin conversation drawer to left side permanently
53 - - Show `ChatPage` in remaining space
54 -- Limit chat bubble width to ~800px, centered on wide screens
55 -
56 -### 5. Export Conversation
57 -
58 -**Implementation:**
59 -- Add "Export Chat" option to conversation context menu
60 -- Formats:
61 - - **Markdown (.md)**: Plain text export
62 - - **PDF**: Use `pdf` package to render chat history
63 -- Use `share_plus` to share to other apps
64 -
65 -### 6. Quick-Launch Shortcut / Widget
66 -
67 -**Implementation:**
68 -- Use `quick_actions` package for home screen shortcuts
69 -- Add "New Chat" shortcut from OS launcher icon
70 -
71 ----
72 -
73 -## Feature Folder Structure
74 -
75 -Extended features are organized within existing features or as new sub-features:
76 -
77 -```
78 -features/
79 -├── chat/
80 -│ └── views/components/
81 -│ └── smart_reply_chips.dart # Feature 2
82 -│
83 -├── settings/
84 -│ └── views/
85 -│ └── components/
86 -│ └── voice_settings.dart # Feature 1 (settings portion)
87 -│
88 -├── extended/ # New feature for multimodal/export
89 -│ ├── data/
90 -│ ├── providers/
91 -│ └── views/
92 -│ ├── image_attachment_sheet.dart # Feature 3
93 -│ └── export_dialog.dart # Feature 5
94 -```
95 -
96 ----
97 -
98 -## Implementation Notes
99 -
100 -### TTS Integration
101 -
102 -```dart
103 -class TTSService {
104 - final FlutterTts _flutterTts = FlutterTts();
105 -
106 - Future<void> speak(String text);
107 - Future<void> stop();
108 - Future<void> setVoice(String voiceId);
109 -
110 - // Events
111 - Stream<void> get onStart;
112 - Stream<void> get onComplete;
113 -}
114 -```
115 -
116 -### Smart Reply Generation
117 -
118 -```dart
119 -class SmartReplyService {
120 - final ChatService _chatService;
121 -
122 - Future<List<String>> generateReplies({
123 - required Server server,
124 - required String modelId,
125 - required List<Message> conversation,
126 - required Persona? persona,
127 - });
128 -
129 - // Fallback suggestions
130 - static const List<String> fallbackSuggestions = [
131 - "Tell me more",
132 - "Can you explain this more simply?",
133 - "Give me an example",
134 - "What are the pros and cons?",
135 - "How does this work?",
136 - ];
137 -}
138 -```
139 -
140 -### Image Attachment
141 -
142 -```dart
143 -class ImageAttachmentService {
144 - final ImagePicker _picker = ImagePicker();
145 -
146 - Future<String?> pickImage();
147 - Future<String> encodeImageToBase64(String path);
148 - bool isMultimodalModel(String? modelId);
149 -}
150 -```
151 -
152 -### Responsive Layout
153 -
154 -```dart
155 -class ResponsiveLayout extends StatelessWidget {
156 - const ResponsiveLayout({
157 - super.key,
158 - required this.chatPage,
159 - required this.drawer,
160 - });
161 -
162 - final Widget chatPage;
163 - final Widget drawer;
164 -
165 - @override
166 - Widget build(BuildContext context) {
167 - final width = MediaQuery.of(context).size.width;
168 - if (width > 900) {
169 - // Tablet/Desktop: Permanent drawer
170 - return Row(children: [drawer, Expanded(child: chatPage)]);
171 - }
172 - // Mobile: Drawer in scaffold
173 - return chatPage;
174 - }
175 -}
176 -```
177 -
178 ----
179 -
180 -## Acceptance Criteria
181 -
182 -- [ ] TTS button reads message text aloud; can be stopped.
183 -- [ ] Smart reply chips appear after completion and populate input when tapped.
184 -- [ ] User can attach image and send to multimodal model.
185 -- [ ] UI scales responsively on larger screens (persistent sidebar).
186 -- [ ] User can export conversation to Markdown and share it.
187 -- [ ] Quick action shortcuts available from OS launcher.
plans/plan.md deleted
-419
@@ -1,419 +0,0 @@
1 -# LocalMind — Project Plan
2 -
3 -## App Name: **LocalMind**
4 -
5 -**Tagline:** _Your AI. Your Device. Your Rules._
6 -
7 -The name "LocalMind" communicates two things immediately:
8 -
9 -- **Local** — runs on your network, no cloud, no tracking, no subscriptions
10 -- **Mind** — intelligent, conversational, thoughtful
11 -
12 -It's short, memorable, and instantly signals privacy-first, offline-capable AI.
13 -
14 ----
15 -
16 -## Vision
17 -
18 -LocalMind is a premium Flutter mobile app that acts as a beautiful, fast, and privacy-respecting interface for local LLM servers (LM Studio, Ollama) and optional cloud providers (OpenRouter). Inspired by the polish of ChatGPT and Claude's UI, but built for power users who want full control over their AI stack.
19 -
20 ----
21 -
22 -## Tech Stack
23 -
24 -| Layer | Technology |
25 -| ------------------ | -------------------------------------------------------------------- |
26 -| Framework | Flutter (latest stable) |
27 -| UI Components | shadcn-inspired Flutter package (`flutter_shadcn_ui` or custom port) |
28 -| State Management | Riverpod |
29 -| Local Storage | Hive CE Edition |
30 -| Networking | Dio (HTTP client for LM Studio / Ollama REST APIs) |
31 -| Markdown Rendering | `flutter_markdown` |
32 -| Code Highlighting | `flutter_highlight` |
33 -| TTS | `flutter_tts` |
34 -| File Handling | `file_picker` + `path_provider` |
35 -| Platform | Android first, iOS later |
36 -
37 ----
38 -
39 -## Architecture: Feature-Based with Riverpod
40 -
41 -This project follows the **Feature-Based Architecture** where code is organized by feature (vertical slices) rather than by technical layer. Shared, cross-cutting concerns live in a `core/` directory. State management is handled exclusively with **Riverpod**.
42 -
43 -### Core Principles
44 -
45 -- **Feature Isolation:** Each feature owns its data, providers, and views. Features must **never** import from other features. If two features need the same code, move it to `core/`.
46 -- **Riverpod-First State Management:** All state is managed via Riverpod providers. No `ChangeNotifier`, `Bloc`, or raw `setState` for business logic.
47 -- **Core for Shared Code:** Anything used by 2+ features belongs in `core/`. This includes shared models, repositories, providers, components, constants, utilities, and infrastructure.
48 -- **Unidirectional Data Flow:** Data flows from Repository → Provider/StateNotifier → View. User events flow from View → Provider/StateNotifier → Repository.
49 -- **Single Source of Truth (SSOT):** Each piece of data has exactly one authoritative source — the Repository. Providers expose that data reactively to the UI.
50 -
51 ----
52 -
53 -## Project Structure
54 -
55 -```
56 -lib/
57 -├── main.dart # Entry point
58 -├── app.dart # App root (MaterialApp / ShadCN App)
59 -│
60 -├── core/ # Shared, cross-cutting concerns (GLOBAL)
61 -│ ├── constants/ # App-wide constants (colors, URLs, config)
62 -│ │ └── app_constants.dart
63 -│ ├── models/ # Shared domain models
64 -│ │ ├── server.dart # Server model + enums (ServerType, ConnectionStatus)
65 -│ │ ├── message.dart # Message model + enums (MessageRole, MessageStatus)
66 -│ │ ├── conversation.dart # Conversation model
67 -│ │ ├── persona.dart # Persona model
68 -│ │ ├── model_info.dart # LLM model metadata
69 -│ │ ├── chat_parameters.dart # Chat parameter config
70 -│ │ └── app_settings.dart # App settings model
71 -│ ├── providers/ # Shared Riverpod providers
72 -│ │ └── app_providers.dart # Theme, active server, active conversation
73 -│ ├── repository/ # Shared repositories & services
74 -│ │ ├── storage/ # Hive storage (init, adapters, box references)
75 -│ │ │ ├── hive_initializer.dart
76 -│ │ │ ├── hive_keys.dart
77 -│ │ │ └── hive_boxes.dart
78 -│ │ ├── server/ # Server API service
79 -│ │ │ └── server_repository.dart
80 -│ │ └── chat/ # Chat service + implementations
81 -│ │ ├── chat_service.dart
82 -│ │ ├── lm_studio_chat_service.dart
83 -│ │ ├── ollama_chat_service.dart
84 -│ │ └── openrouter_chat_service.dart
85 -│ ├── routes/ # App routing (GoRouter config)
86 -│ │ └── app_router.dart
87 -│ ├── theme/ # App theme data
88 -│ │ ├── app_theme.dart
89 -│ │ ├── colors.dart
90 -│ │ └── typography.dart
91 -│ ├── components/ # Reusable UI widgets (shared across features)
92 -│ │ ├── app_button.dart
93 -│ │ ├── app_card.dart
94 -│ │ ├── app_text_field.dart
95 -│ │ └── ...
96 -│ └── utils/ # General-purpose utilities
97 -│ ├── uuid.dart
98 -│ └── date_utils.dart
99 -│
100 -├── features/ # Feature modules (vertical slices)
101 -│ ├── server_connection/ # Server setup & management
102 -│ │ ├── data/
103 -│ │ │ └── models/
104 -│ │ ├── providers/
105 -│ │ │ └── server_connection_providers.dart
106 -│ │ └── views/
107 -│ │ ├── server_list_page.dart
108 -│ │ ├── add_server_page.dart
109 -│ │ └── components/
110 -│ │ ├── server_card.dart
111 -│ │ ├── connection_status_indicator.dart
112 -│ │ └── server_type_selector.dart
113 -│ │
114 -│ ├── chat/ # Chat interface
115 -│ │ ├── data/
116 -│ │ │ └── models/
117 -│ │ ├── providers/
118 -│ │ │ └── chat_providers.dart
119 -│ │ └── views/
120 -│ │ ├── chat_page.dart
121 -│ │ └── components/
122 -│ │ ├── chat_bubble.dart
123 -│ │ ├── chat_input_bar.dart
124 -│ │ ├── typing_indicator.dart
125 -│ │ ├── message_action_bar.dart
126 -│ │ └── code_block.dart
127 -│ │
128 -│ ├── model_management/ # Model browsing & selection
129 -│ │ ├── data/
130 -│ │ │ └── models/
131 -│ │ ├── providers/
132 -│ │ │ └── model_management_providers.dart
133 -│ │ └── views/
134 -│ │ ├── model_picker_sheet.dart
135 -│ │ └── components/
136 -│ │ ├── model_tile.dart
137 -│ │ └── model_metadata_chip.dart
138 -│ │
139 -│ ├── conversation/ # Conversation history management
140 -│ │ ├── data/
141 -│ │ │ └── models/
142 -│ │ ├── providers/
143 -│ │ │ └── conversation_providers.dart
144 -│ │ └── views/
145 -│ │ ├── conversation_drawer.dart
146 -│ │ └── components/
147 -│ │ ├── conversation_tile.dart
148 -│ │ ├── conversation_search_bar.dart
149 -│ │ └── date_section_header.dart
150 -│ │
151 -│ ├── persona/ # Templates & personas
152 -│ │ ├── data/
153 -│ │ │ └── models/
154 -│ │ ├── providers/
155 -│ │ │ └── persona_providers.dart
156 -│ │ └── views/
157 -│ │ ├── persona_list_page.dart
158 -│ │ ├── create_persona_page.dart
159 -│ │ └── components/
160 -│ │ ├── persona_card.dart
161 -│ │ └── persona_category_chips.dart
162 -│ │
163 -│ └── settings/ # App settings
164 -│ ├── data/
165 -│ │ └── models/
166 -│ ├── providers/
167 -│ │ └── settings_providers.dart
168 -│ └── views/
169 -│ ├── settings_page.dart
170 -│ └── components/
171 -│ ├── settings_section.dart
172 -│ ├── settings_slider.dart
173 -│ ├── settings_toggle.dart
174 -│ ├── theme_toggle.dart
175 -│ └── font_size_preview.dart
176 -│
177 -└── loc/ # Localization (optional, future)
178 -```
179 -
180 ----
181 -
182 -## Core (Global) Layer
183 -
184 -The `core/` directory holds code that is **shared across multiple features**. The rule is simple: _if it's used by 2+ features, it belongs in `core/`_.
185 -
186 -### `core/constants/`
187 -
188 -App-wide constant values.
189 -
190 -| What to put here | Example |
191 -|-----------------|---------|
192 -| Color constants | `AppColors` with static `Color` fields |
193 -| URL constants | `AppUrls` with static URL strings (API base, privacy policy) |
194 -| Size/spacing constants | `AppSizes` with static doubles for padding, radius |
195 -| Config values | API timeouts, pagination limits |
196 -
197 -### `core/models/`
198 -
199 -Domain models shared across features. All Hive models live here:
200 -- `Server` — server connection profiles
201 -- `Message` — chat messages
202 -- `Conversation` — conversation threads
203 -- `Persona` — AI personas
204 -- `ModelInfo` — LLM model metadata
205 -- `ChatParameters` — chat configuration
206 -- `AppSettings` — app preferences
207 -
208 -### `core/providers/`
209 -
210 -Riverpod providers consumed by multiple features:
211 -- `themeProvider` — current theme mode
212 -- `activeServerProvider` — currently selected server
213 -- `activeConversationProvider` — currently active conversation
214 -- `hiveBoxesProvider` — opened Hive boxes
215 -
216 -### `core/repository/`
217 -
218 -Repositories and services for shared data domains:
219 -
220 -```
221 -core/repository/
222 -├── storage/ # Hive initialization, adapters, box access
223 -├── server/ # ServerApiService (connection testing, model fetching)
224 -└── chat/ # ChatService + implementations (LM Studio, Ollama, OpenRouter)
225 -```
226 -
227 -### `core/routes/`
228 -
229 -App-level routing and navigation configuration (GoRouter setup, route constants).
230 -
231 -### `core/theme/`
232 -
233 -App theme data — `ThemeData`, text styles, component themes. Color definitions reference `core/constants/`.
234 -
235 -### `core/components/`
236 -
237 -Reusable UI widgets consumed by multiple features. These are **presentational** widgets that receive data via constructor params — no business logic.
238 -
239 -### `core/utils/`
240 -
241 -General-purpose utility classes and functions (UUID generation, date formatters, etc.).
242 -
243 ----
244 -
245 -## Feature Layer
246 -
247 -Each feature directory follows a **consistent internal structure**:
248 -
249 -```
250 -features/<feature_name>/
251 -├── data/ # Data layer
252 -│ └── models/ # Feature-specific models (if any, beyond core models)
253 -│
254 -├── providers/ # State management
255 -│ └── <feature>_providers.dart # All Riverpod providers for this feature
256 -│
257 -└── views/ # UI layer
258 - ├── <feature>_page.dart # Main page widget(s)
259 - ├── add_<feature>_page.dart # Create/edit page (if applicable)
260 - └── components/ # Feature-specific UI components
261 - └── <component>.dart
262 -```
263 -
264 -### Rules
265 -
266 -1. **Never import from another feature.** If feature A needs something from feature B, extract it to `core/`.
267 -2. **Keep views lean.** Views (`ConsumerWidget` / `ConsumerStatefulWidget`) should only handle UI concerns — layout, animations, navigation. All business logic lives in providers.
268 -3. **One provider file per feature** is the default. Split only when the file exceeds ~400 lines.
269 -4. **Models must have serialization.** Every model needs `fromMap()` / `fromJson()` and `toMap()` / `toJson()` along with `copyWith()`.
270 -
271 ----
272 -
273 -## State Management with Riverpod
274 -
275 -### Provider Types Used
276 -
277 -| Provider Type | When to Use | Example |
278 -|---------------|-------------|---------|
279 -| `Provider` | Expose a stateless repository or service instance | `final repoProvider = Provider<Repo>((ref) => Repo());` |
280 -| `StateProvider` | Simple mutable state (filters, toggles, search query) | `final searchProvider = StateProvider<String>((ref) => '');` |
281 -| `StateNotifierProvider` | Complex state with methods (CRUD controllers, paginated lists) | `final listProvider = StateNotifierProvider<Notifier, State>((ref) { ... });` |
282 -| `FutureProvider` | One-shot async data fetching | `final dataProvider = FutureProvider<List<Item>>((ref) async { ... });` |
283 -| `FutureProvider.family` | Parameterized async data fetching | `final itemProvider = FutureProvider.family<Item?, String>((ref, id) async { ... });` |
284 -| `StreamProvider` | Real-time data streams | `final streamProvider = StreamProvider<List<Item>>((ref) { ... });` |
285 -
286 -### Provider File Organization
287 -
288 -Each feature's `providers/` file is organized in this order:
289 -
290 -1. **Repository Providers** — `Provider<XRepository>`
291 -2. **Filter/UI State Providers** — `StateProvider<String>`, `StateProvider<bool>`
292 -3. **State Classes** — Immutable state objects with `copyWith()`
293 -4. **StateNotifier Classes** — Business logic
294 -5. **StateNotifierProvider declarations** — Wiring notifiers to their dependencies
295 -
296 -### Use `autoDispose` by Default
297 -
298 -Prefer `.autoDispose` on providers scoped to a screen or feature:
299 -
300 -```dart
301 -final featureListProvider = StateNotifierProvider.autoDispose<
302 - FeatureListNotifier, FeatureListState
303 ->((ref) { ... });
304 -```
305 -
306 ----
307 -
308 -## Core Features (MVP)
309 -
310 -### 1. Server Connection (`features/server_connection/`)
311 -
312 -- Add LM Studio server (IP + port)
313 -- Add Ollama server (IP + port)
314 -- Add OpenRouter (API key)
315 -- Connection health indicator (ping status)
316 -- Multiple server profiles
317 -
318 -### 2. Chat Interface (`features/chat/`)
319 -
320 -- ChatGPT/Claude-like chat bubbles
321 -- Markdown rendering with syntax highlighting
322 -- Streaming responses (SSE support)
323 -- Copy, retry, delete individual messages
324 -- Long-press context menu on messages
325 -
326 -### 3. Model Management (`features/model_management/`)
327 -
328 -- Fetch and list available models from connected server
329 -- Swap models mid-chat
330 -- Display model metadata (size, context length, etc.)
331 -
332 -### 4. Conversation Management (`features/conversation/`)
333 -
334 -- Create, rename, delete conversations
335 -- Conversation history with search
336 -- Pin important chats
337 -
338 -### 5. Persona Management (`features/persona/`)
339 -
340 -- Built-in presets: Code Assistant, Math Tutor, Story Writer, General Tutor
341 -- Create custom persona (system prompt + name + avatar emoji)
342 -- Apply persona per conversation
343 -
344 -### 6. Settings (`features/settings/`)
345 -
346 -- Chat parameters: temperature, top_p, max tokens, context length
347 -- UI preferences: theme (dark/light), font size
348 -- Default server selection
349 -
350 ----
351 -
352 -## Extended Features (Post-MVP)
353 -
354 -| Feature | Priority |
355 -| -------------------------------------------- | -------- |
356 -| AI Voice (TTS) — read responses aloud | High |
357 -| Context Smart Replies — suggested follow-ups | High |
358 -| Multimodal — attach images/documents | Medium |
359 -| Tablet-optimized split layout | Medium |
360 -| Export conversation (Markdown, PDF) | Medium |
361 -| Widget / quick-launch shortcut | Low |
362 -| Haptic feedback | Low |
363 -
364 ----
365 -
366 -## Design Philosophy
367 -
368 -- **Shadcn-inspired**: Clean, neutral, component-driven. Cards, popovers, command palettes, sheets.
369 -- **Dark-first**: Primary theme is a deep neutral dark (not pure black). Light mode optional.
370 -- **Density-aware**: Comfortable padding on phone, denser on tablet.
371 -- **No fluff**: Every UI element serves a purpose. No splash screens, no onboarding carousels beyond essentials.
372 -
373 ----
374 -
375 -## Privacy Commitments
376 -
377 -- No analytics by default
378 -- Conversations stored only on-device (Hive encrypted)
379 -- Network requests go only to user-configured servers
380 -- "LocalMind never sees your data" — shown in onboarding and settings
381 -- Optional: show a live indicator of where data is going (local vs. cloud)
382 -
383 ----
384 -
385 -## Milestones
386 -
387 -| Phase | Deliverable | Est. Time |
388 -| --------------- | ---------------------------------------- | --------- |
389 -| 0 — Setup | Flutter project, core folder structure, shadcn theme, routing | 1 week |
390 -| 1 — Core | Server config + basic chat (LM Studio) | 2 weeks |
391 -| 2 — Chat Polish | Streaming, markdown, model swap | 2 weeks |
392 -| 3 — Features | Personas, conversation history, settings | 2 weeks |
393 -| 4 — Extended | TTS, smart replies, multimodal | 3 weeks |
394 -| 5 — Release | Polish, Play Store prep, beta testing | 2 weeks |
395 -
396 ----
397 -
398 -## Monetization (Optional)
399 -
400 -- Free: Core chat, 1 server, basic personas
401 -- One-time unlock (~$3–5): Unlimited servers, TTS, smart replies, multimodal, no ads
402 -- No subscriptions. Ever.
403 -
404 ----
405 -
406 -## Tracking Checklist
407 -
408 -Follow these detailed breakdown documents to implement the project:
409 -
410 -- [ ] [Feature 00 — Project Setup & Scaffolding](00_project_setup.md)
411 -- [ ] [Feature 01 — Server Connection & Management](01_server_connection.md)
412 -- [ ] [Feature 02 — Chat Interface](02_chat_interface.md)
413 -- [ ] [Feature 03 — Model Management](03_model_management.md)
414 -- [ ] [Feature 04 — Conversation Management](04_conversation_management.md)
415 -- [ ] [Feature 05 — Templates & Personas](05_templates_personas.md)
416 -- [ ] [Feature 06 — Settings](06_settings.md)
417 -- [ ] [Feature 07 — Core Architecture](07_core_architecture.md)
418 -- [ ] [Feature 08 — Design System](08_design_system.md)
419 -- [ ] [Feature 09 — Extended Features](09_extended_features.md)
plans/tracking.md deleted
-438
@@ -1,438 +0,0 @@
1 -# LocalMind — Implementation Tracking
2 -
3 -> Last updated: 2026-03-30 (v5 — Steps 1-9 + all remaining complete)
4 -
5 ----
6 -
7 -## Legend
8 -
9 -- ✅ **Done** — Fully implemented and functional
10 -- 🔶 **Partial** — Exists but incomplete (stubs, placeholders, missing pieces)
11 -- ❌ **Not Done** — Does not exist or is just a placeholder
12 -
13 ----
14 -
15 -## Phase 0 — Setup & Core
16 -
17 -### Feature 00: Project Setup
18 -
19 -| Task | Status |
20 -|------|--------|
21 -| `main.dart` — Hive init, ProviderScope, routing | ✅ Done |
22 -| `app.dart` — App root with ShadCN + GoRouter | ✅ Done |
23 -| Folder structure (core + features) | ✅ Done |
24 -| `pubspec.yaml` — all dependencies configured | ✅ Done |
25 -
26 -### Feature 07: Core Architecture
27 -
28 -| Task | Status |
29 -|------|--------|
30 -| `core/constants/app_constants.dart` — app-wide constants | ✅ Done |
31 -| `core/models/enums.dart` — ServerType, ConnectionStatus, MessageRole, MessageStatus | ✅ Done |
32 -| `core/providers/app_providers.dart` — active server, active conversation, settings | ✅ Done |
33 -| `core/providers/service_providers.dart` — Dio, ServerApiService, SettingsProvider | ✅ Done |
34 -| `core/providers/storage_providers.dart` — Hive boxes access, personas, conversations | ✅ Done |
35 -| `core/storage/hive_initializer.dart` — box opening, adapter registration | ✅ Done |
36 -| `core/storage/hive_keys.dart` — box name constants | ✅ Done |
37 -| `core/routes/app_routes.dart` — route definitions | ✅ Done |
38 -| UUID generation utility | ✅ Done (inline in chat/conversation providers) |
39 -| `core/repository/server/server_repository.dart` — abstract server repo | ✅ Done | `core/repository/server/server_repository.dart` |
40 -| `core/repository/chat/chat_service.dart` — abstract chat service | ✅ Done (located in `features/chat/data/`) |
41 -| `core/utils/date_utils.dart` — date formatting | ✅ Done | `core/utils/date_utils.dart` |
42 -| `AppException` / `AppErrorType` error model | ✅ Done | `core/models/app_exception.dart` |
43 -
44 -### Feature 08: Design System
45 -
46 -| Task | Status |
47 -|------|--------|
48 -| `core/theme/colors.dart` — AppColors dark + light | ✅ Done |
49 -| `core/theme/typography.dart` — Inter + Fira Code text styles | ✅ Done |
50 -| `core/theme/app_theme.dart` — ThemeData dark + light | ✅ Done |
51 -| `core/components/app_button.dart` — button variants | ✅ Done | AppButton (4 variants, 3 sizes, loading) |
52 -| `core/components/app_card.dart` — card component | ✅ Done | AppCard in `app_button.dart` |
53 -| `core/components/app_text_field.dart` — text input component | ✅ Done | AppTextField + AppSheet + AppDialog in `app_text_field.dart` |
54 -| `core/components/app_sheet.dart` — bottom sheet wrapper | ✅ Done | AppSheet in `app_text_field.dart` |
55 -| `core/components/app_dialog.dart` — dialog wrapper | ✅ Done | AppDialog (confirm + input) in `app_text_field.dart` |
56 -| `core/components/app_loading_indicator.dart` — spinner | ✅ Done | AppLoadingIndicator in `app_button.dart` |
57 -| Haptic feedback integration | ✅ Done (in chat input bar) |
58 -| Spacing & radius constants (`AppSizes`) | ✅ Done | `core/components/app_sizes.dart` |
59 -
60 ----
61 -
62 -## Phase 1 — Core Features
63 -
64 -### Feature 01: Server Connection
65 -
66 -| Task | Status | Notes |
67 -|------|--------|-------|
68 -| **DATA & MODELS** | | |
69 -| `server.dart` model with Hive annotations | ✅ Done | `features/servers/data/models/` |
70 -| `server.g.dart` — generated Hive adapter | ✅ Done | |
71 -| **LOGIC (PROVIDERS)** | | |
72 -| `ServersNotifier` — add/update/delete server | ✅ Done | `features/servers/providers/server_providers.dart` |
73 -| `ActiveServerNotifier` — active server selection + persistence | ✅ Done | |
74 -| `ConnectionStatusNotifier` — connection check on server change | ✅ Done | |
75 -| `availableModelsProvider` — fetch models per server | ✅ Done | |
76 -| Background connection monitoring (30s ping) | ❌ Not Done | Manual refresh only |
77 -| **SERVICES** | | |
78 -| `ServerApiService` — testConnection (LM Studio) | ✅ Done | `features/servers/data/server_api_service.dart` |
79 -| `ServerApiService` — testConnection (Ollama) | ✅ Done | |
80 -| `ServerApiService` — testConnection (OpenRouter) | ✅ Done | |
81 -| `ServerApiService` — fetchModels (LM Studio) | ✅ Done | |
82 -| `ServerApiService` — fetchModels (Ollama) | ✅ Done | |
83 -| `ServerApiService` — fetchModels (OpenRouter) | ✅ Done | |
84 -| `ServerApiService` — pingServer | ✅ Done | |
85 -| **UI** | | |
86 -| `server_list_screen.dart` — list + empty state + FAB | ✅ Done | |
87 -| `server_card.dart` — server info card | ✅ Done | |
88 -| `add_server_screen.dart` — form with validation + test + save | ✅ Done | |
89 -| `server_type_selector.dart` — LM Studio / Ollama / OpenRouter toggle | ✅ Done | |
90 -| `connection_status_indicator.dart` — animated status dot | ✅ Done | |
91 -| `server_icon_picker.dart` — icon picker sheet | ✅ Done | |
92 -
93 -### Feature 02: Chat Interface
94 -
95 -| Task | Status | Notes |
96 -|------|--------|-------|
97 -| **DATA & MODELS** | | |
98 -| `message.dart` model with Hive annotations | ✅ Done | `features/chat/data/models/message.dart` |
99 -| `message.g.dart` — generated Hive adapter | ✅ Done | |
100 -| `chat_parameters.dart` — ChatParameters model | ✅ Done | |
101 -| **LOGIC (PROVIDERS)** | | |
102 -| `ChatNotifier` — sendMessage with streaming | ✅ Done | `features/chat/providers/chat_providers.dart` |
103 -| `ChatNotifier` — loadConversation | ✅ Done | |
104 -| `ChatNotifier` — retryLastMessage | ✅ Done | |
105 -| `ChatNotifier` — deleteMessage | ✅ Done | |
106 -| `ChatNotifier` — clearConversation | ✅ Done | |
107 -| `ChatNotifier` — cancelStream | ✅ Done | |
108 -| `ChatNotifier` — startNewConversation | ✅ Done | |
109 -| Context window truncation logic | ✅ Done | |
110 -| System prompt injection (persona + default) | ✅ Done | `chat_providers.dart` — `_getPersonaSystemPrompt()` + default |
111 -| **SERVICES** | | |
112 -| `ChatService` abstract class | ✅ Done | `features/chat/data/chat_service.dart` |
113 -| `LMStudioChatService` — SSE streaming | ✅ Done | |
114 -| `OllamaChatService` — NDJSON streaming | ✅ Done | |
115 -| `OpenRouterChatService` — SSE with API key | ✅ Done | |
116 -| `TtsService` — text-to-speech | ✅ Done | `features/chat/data/tts_service.dart` |
117 -| `ExportService` — Markdown/text export | ✅ Done | `features/chat/data/export_service.dart` |
118 -| **UI** | | |
119 -| `chat_screen.dart` — full chat page with messages + input | ✅ Done | |
120 -| `chat_bubble.dart` — user / assistant / system bubbles | ✅ Done | |
121 -| `chat_bubble.dart` — markdown rendering | ✅ Done | |
122 -| `chat_bubble.dart` — streaming indicator (blinking cursor) | ✅ Done | `StreamingIndicator` in `typing_indicator.dart` |
123 -| `chat_input_bar.dart` — multi-line input + send/stop | ✅ Done | |
124 -| `typing_indicator.dart` — 3 bouncing dots | ✅ Done | |
125 -| `message_action_bar.dart` — copy/retry/delete buttons | ✅ Done | |
126 -| `code_block.dart` — syntax highlighting + copy | ✅ Done | |
127 -| Empty state with quick prompts | ✅ Done | |
128 -| "New messages ↓" scroll-to-bottom FAB | ✅ Done | |
129 -| Connection error banner | ✅ Done | |
130 -| Recent conversations on empty state | ✅ Done | |
131 -| "Read Aloud" TTS button in message actions | ✅ Done | `message_action_bar.dart` |
132 -| Smart reply chips after assistant response | ✅ Done | `smartRepliesProvider` + `_SmartReplyChips` |
133 -| Image attachment button + preview | ✅ Done | `chat_input_bar.dart` with `file_picker` |
134 -| Persona emoji in chat app bar | ✅ Done | |
135 -| "Change Persona" / "Remove Persona" menu items | ✅ Done | |
136 -| Persona picker bottom sheet | ✅ Done | |
137 -| Haptic feedback on send | ✅ Done | |
138 -
139 -### Feature 03: Model Management
140 -
141 -| Task | Status | Notes |
142 -|------|--------|-------|
143 -| **DATA & MODELS** | | |
144 -| `model_info.dart` — ModelInfo model | ✅ Done | `features/models/data/models/model_info.dart` |
145 -| **LOGIC** | | |
146 -| `selectedModelProvider` — track selected model | ✅ Done | In `chat_providers.dart` |
147 -| Model cache with 5-min TTL | ✅ Done | `features/models/data/model_cache.dart` |
148 -| `modelSearchQueryProvider` — search/filter | ✅ Done | In `model_picker_sheet.dart` |
149 -| **UI** | | |
150 -| `model_picker_sheet.dart` — full model list + search + selection | ✅ Done | |
151 -| `_ModelTile` — model row with name + metadata | ✅ Done | |
152 -| `_MetadataChip` — pills for params/size/quantization/context | ✅ Done | |
153 -| Model search bar | ✅ Done | |
154 -| Selected model indicator (checkmark) | ✅ Done | |
155 -| Loading skeleton + error state + retry | ✅ Done | |
156 -| Refresh models button | ✅ Done | |
157 -
158 ----
159 -
160 -## Phase 3 — Features
161 -
162 -### Feature 04: Conversation Management
163 -
164 -| Task | Status | Notes |
165 -|------|--------|-------|
166 -| **DATA & MODELS** | | |
167 -| `conversation.dart` model with Hive annotations | ✅ Done | `features/conversations/data/models/conversation.dart` |
168 -| `conversation.g.dart` — generated Hive adapter | ✅ Done | |
169 -| **LOGIC (PROVIDERS)** | | |
170 -| `ConversationsNotifier` — create | ✅ Done | `features/conversations/providers/conversation_providers.dart` |
171 -| `ConversationsNotifier` — rename | ✅ Done | |
172 -| `ConversationsNotifier` — delete | ✅ Done | |
173 -| `ConversationsNotifier` — togglePin | ✅ Done | |
174 -| `ConversationsNotifier` — updatePreview | ✅ Done | |
175 -| `ConversationsNotifier` — deleteAll | ✅ Done | |
176 -| `ActiveConversationNotifier` — active conversation tracking | ✅ Done | |
177 -| `ConversationSearchNotifier` — search query state | ✅ Done | |
178 -| `filteredConversationsProvider` — filter by title/preview | ✅ Done | |
179 -| `groupedConversationsProvider` — date grouping (Pinned/Today/Yesterday/7d/30d/Older) | ✅ Done | |
180 -| **UI** | | |
181 -| `conversation_drawer.dart` — Drawer wrapper | ✅ Done | |
182 -| `conversation_sidebar.dart` — full sidebar with conversations + nav | ✅ Done | |
183 -| `conversation_drawer_header.dart` — "LocalMind" header | ✅ Done | |
184 -| `conversation_list.dart` — grouped list view | ✅ Done | |
185 -| `conversation_tile.dart` — single conversation row | ✅ Done | |
186 -| `conversation_search_bar.dart` — search input | ✅ Done | |
187 -| `conversation_empty_state.dart` — no conversations state | ✅ Done | |
188 -| `date_section_header.dart` — TODAY/YESTERDAY/OLDER headers | ✅ Done | |
189 -| `drawer_nav_item.dart` — Chat/Servers/Personas nav | ✅ Done | |
190 -| `new_chat_button.dart` — "New Chat" CTA | ✅ Done | |
191 -| Long-press context menu (Rename/Pin/Delete) | ✅ Done | In `conversation_tile.dart` |
192 -| Swipe-to-delete with undo snackbar | ✅ Done | `Dismissible` in `conversation_tile.dart` |
193 -| Auto-title generation after first exchange | ✅ Done | `_autoGenerateTitle` in `chat_providers.dart` |
194 -| Inline rename dialog | ✅ Done | In `conversation_list.dart` |
195 -| `updatePersona` method in ConversationsNotifier | ✅ Done | |
196 -
197 -### Feature 05: Personas
198 -
199 -| Task | Status | Notes |
200 -|------|--------|-------|
201 -| **DATA & MODELS** | | |
202 -| `persona.dart` model with Hive annotations | ✅ Done | `features/personas/data/models/persona.dart` |
203 -| `persona.g.dart` — generated Hive adapter | ✅ Done | |
204 -| **LOGIC** | | |
205 -| `PersonasNotifier` — CRUD operations | ✅ Done | `features/personas/providers/personas_providers.dart` |
206 -| `seedBuiltInPersonas()` — 7 presets on first launch | ✅ Done | Auto-seeded on first build |
207 -| `personaCategoryFilterProvider` — category filter | ✅ Done | |
208 -| `filteredPersonasProvider` — filter by category + search | ✅ Done | |
209 -| `personaSearchQueryProvider` — search query | ✅ Done | |
210 -| `builtInPersonasProvider` / `userPersonasProvider` | ✅ Done | |
211 -| `personaByIdProvider` — lookup by ID | ✅ Done | |
212 -| Persona clone functionality | ✅ Done | |
213 -| Persona system prompt injection into chat | ✅ Done | In `chat_providers.dart` |
214 -| **UI** | | |
215 -| `persona_list_screen.dart` — full list with sections | ✅ Done | |
216 -| `_PersonaCard` — persona display card | ✅ Done | |
217 -| Category filter chips (All/General/Coding/Education/Creative) | ✅ Done | |
218 -| Built-in vs My Personas sections | ✅ Done | |
219 -| Long-press actions (Edit/Delete/Clone) | ✅ Done | |
220 -| Persona emoji display | ✅ Done | |
221 -| Empty state | ✅ Done | |
222 -| `create_persona_screen.dart` — full form | ✅ Done | |
223 -| Emoji picker (horizontal scroll) | ✅ Done | |
224 -| Category dropdown | ✅ Done | |
225 -| System prompt field with character count | ✅ Done | |
226 -| Preview mode toggle | ✅ Done | |
227 -| Advanced settings (temperature/topP overrides) | ✅ Done | |
228 -| Edit mode (pre-filled from persona) | ✅ Done | |
229 -
230 -### Feature 06: Settings
231 -
232 -| Task | Status | Notes |
233 -|------|--------|-------|
234 -| **DATA & MODELS** | | |
235 -| `app_settings.dart` model with Hive annotations | ✅ Done | `features/settings/data/models/app_settings.dart` |
236 -| `app_settings.g.dart` — generated Hive adapter | ✅ Done | |
237 -| **LOGIC** | | |
238 -| `SettingsNotifier` — load, update individual fields, persist | ✅ Done | In `app_providers.dart` |
239 -| `setTemperature` / `setTopP` / `setMaxTokens` / `setContextLength` | ✅ Done | |
240 -| `setFontSize` / `setShowSystemMessages` / `setHapticFeedback` | ✅ Done | |
241 -| `setSendOnEnter` / `setDefaultServer` / `setDefaultPersona` | ✅ Done | |
242 -| `setStreamingEnabled` / `setAutoGenerateTitle` / `setShowDataIndicator` | ✅ Done | |
243 -| `resetToDefaults` | ✅ Done | |
244 -| **UI** | | |
245 -| `settings_screen.dart` — full scrollable settings page | ✅ Done | |
246 -| Temperature slider (0.0–2.0) + explanation | ✅ Done | |
247 -| Top P slider (0.0–1.0) + explanation | ✅ Done | |
248 -| Max Tokens input (bounded) + explanation | ✅ Done | |
249 -| Context Length input (bounded) + explanation | ✅ Done | |
250 -| Theme toggle (System/Light/Dark/Claude) | ✅ Done | |
251 -| Font size slider + live preview | ✅ Done | |
252 -| Toggles: streaming, auto-title, send-on-enter, system messages, haptic | ✅ Done | |
253 -| Default server dropdown | ✅ Done | |
254 -| Default persona dropdown | ✅ Done | |
255 -| Privacy section with data indicator toggle | ✅ Done | |
256 -| Delete all conversations button + confirmation | ✅ Done | |
257 -| Reset settings button + confirmation | ✅ Done | |
258 -| About section | ✅ Done | |
259 -| **UI** | | |
260 -| `settings_screen.dart` — main settings page | ❌ Not Done | Placeholder only — "Settings - Placeholder" |
261 -| `settings_section.dart` — section group + header | ❌ Not Done | |
262 -| `settings_slider.dart` — label + slider + value | ❌ Not Done | |
263 -| `settings_toggle.dart` — label + switch | ❌ Not Done | |
264 -| `settings_dropdown.dart` — server/persona picker | ❌ Not Done | |
265 -| `theme_toggle.dart` — System/Light/Dark visual toggle | ❌ Not Done | |
266 -| `font_size_preview.dart` — live text preview | ❌ Not Done | |
267 -| Chat parameter explanations | ❌ Not Done | |
268 -| Dangerous actions with confirmation | ❌ Not Done | |
269 -
270 ----
271 -
272 -## Phase 4 — Extended Features
273 -
274 -### Feature 09: Extended Features
275 -
276 -| Task | Status |
277 -|------|--------|
278 -| AI Voice (TTS) — read aloud | ✅ Done | `TtsService` + "Read Aloud" button |
279 -| Context Smart Replies — follow-up chips | ✅ Done | `smartRepliesProvider` + `_SmartReplyChips` widget |
280 -| Multimodal — image attachment (button exists but disabled) | ✅ Done | `ChatInputBar` with `file_picker` + image preview |
281 -| Tablet/Desktop responsive layout | ✅ Done | `AppShell` in `app.dart` — persistent sidebar on `>= md` breakpoint |
282 -| Export conversation (Markdown/PDF) | ✅ Done | `ExportService` with Markdown + text |
283 -| Quick-launch shortcut | ✅ Done | `QuickLaunchService` with MethodChannel |
284 -
285 ----
286 -
287 -## Phase Extra: Onboarding (Not in original plan)
288 -
289 -| Task | Status | Notes |
290 -|------|--------|-------|
291 -| `onboarding_server_type_screen.dart` — pick LM Studio/Ollama/OpenRouter | ✅ Done | |
292 -| `onboarding_server_setup_screen.dart` — configure first server | ✅ Done | |
293 -| `onboarding_theme_screen.dart` — pick dark/light | ✅ Done | |
294 -
295 ----
296 -
297 -## Summary
298 -
299 -| Category | Done | Partial | Not Done | Total |
300 -|----------|------|---------|----------|-------|
301 -| **Phase 0** — Setup & Core | 15 | 0 | 0 | 15 |
302 -| **Phase 1** — Server Connection | 14 | 0 | 1 | 15 |
303 -| **Phase 1** — Chat Interface | 25 | 0 | 0 | 25 |
304 -| **Phase 2** — Model Management | 11 | 0 | 0 | 11 |
305 -| **Phase 3** — Conversations | 17 | 0 | 0 | 17 |
306 -| **Phase 3** — Personas | 21 | 0 | 0 | 21 |
307 -| **Phase 3** — Settings | 17 | 0 | 0 | 17 |
308 -| **Phase 4** — Extended | 6 | 0 | 0 | 6 |
309 -| **Extra** — Onboarding | 3 | 0 | 0 | 3 |
310 -| **TOTAL** | **129** | **0** | **1** | **130** |
311 -
312 ----
313 -
314 -## What to Build Next (Ordered Build Plan)
315 -
316 -> Follow this order. Each step depends on the previous one.
317 -
318 -### Step 1: Settings Screen (UI + Logic) ✅ DONE
319 -**Why first:** Everything else (chat params, theme, defaults) flows through settings.
320 -
321 -| Sub-task | Type |
322 -|----------|------|
323 -| `SettingsNotifier` — load, update individual fields, persist | Logic |
324 -| Temperature slider (0.0–2.0) + explanation | UI |
325 -| Top P slider (0.0–1.0) + explanation | UI |
326 -| Max Tokens input (bounded) + explanation | UI |
327 -| Context Length input (bounded) + explanation | UI |
328 -| Theme toggle (System/Light/Dark/Claude) — wire to existing `themeModeProvider` | UI |
329 -| Font size slider + live preview | UI |
330 -| Toggles: streaming, auto-title, send-on-enter, system messages, haptic | UI |
331 -| Default server dropdown — wire to `serversProvider` | UI |
332 -| Default persona dropdown — wire to `personasProvider` | UI |
333 -| Delete all conversations button + confirmation | UI |
334 -| Reset settings button + confirmation | UI |
335 -| Settings sections + scrollable layout | UI |
336 -
337 -### Step 2: Model Picker Sheet (UI) ✅ DONE
338 -**Why second:** Chat screen already passes selected model to API — just needs the picker UI.
339 -
340 -| Sub-task | Type |
341 -|----------|------|
342 -| Wire `availableModelsProvider` to model picker | Logic |
343 -| Model list with metadata chips (params, size, quantization, context) | UI |
344 -| Search/filter bar | UI |
345 -| Selected model checkmark | UI |
346 -| Loading skeleton + error state + retry | UI |
347 -| Refresh models button | UI |
348 -| Tap to select + dismiss | UI |
349 -
350 -### Step 3: Persona Providers (Logic) ✅ DONE
351 -**Why third:** Persona UI needs providers to read from.
352 -
353 -| Sub-task | Type |
354 -|----------|------|
355 -| `PersonasNotifier` — load, create, update, delete, clone | Logic |
356 -| Seed 7 built-in personas on first launch | Logic |
357 -| `selectedPersonaProvider` — current persona for new chat | Logic |
358 -| `personaCategoryFilterProvider` + `filteredPersonasProvider` | Logic |
359 -| Persona CRUD with Hive persistence | Logic |
360 -
361 -### Step 4: Persona List Screen (UI) ✅ DONE
362 -**Why fourth:** Depends on providers from step 3.
363 -
364 -| Sub-task | Type |
365 -|----------|------|
366 -| Persona cards (emoji, name, description, category chip) | UI |
367 -| Built-in vs My Personas sections | UI |
368 -| Category filter chips (All/Coding/Education/Creative/General) | UI |
369 -| Tap persona → apply to current conversation | UI |
370 -| Long-press → Edit/Delete/Clone | UI |
371 -| "Create Custom Persona" FAB | UI |
372 -
373 -### Step 5: Create Persona Screen (UI) ✅ DONE
374 -**Why fifth:** Create/edit flow for personas.
375 -
376 -| Sub-task | Type |
377 -|----------|------|
378 -| Form: name, emoji picker, category dropdown, description, system prompt | UI |
379 -| Character count on system prompt | UI |
380 -| Preview mode toggle | UI |
381 -| Advanced settings: temperature/topP overrides | UI |
382 -| Save + validation | UI |
383 -| Edit mode (pre-filled) | Logic |
384 -
385 -### Step6: Persona → Chat Integration (Logic + UI) ✅ DONE
386 -**Why sixth:** Connects personas to actual chat behavior.
387 -
388 -| Sub-task | Type |
389 -|----------|------|
390 -| Persona system prompt injection in `_buildMessagesForApi` (already done ✅) | Logic |
391 -| Persona parameter overrides applied to `chatParamsProvider` | Logic |
392 -| Persona emoji shown in chat app bar | UI |
393 -| "Change Persona" in chat overflow menu | UI |
394 -| "Remove Persona" option | UI |
395 -| Conversation `personaId` set when persona selected | Logic |
396 -
397 -### Step 7: Conversation Enhancements (UI + Logic) ✅ DONE
398 -**Why seventh:** Polish existing working features.
399 -
400 -| Sub-task | Type |
401 -|----------|------|
402 -| Long-press context menu (Rename, Pin/Unpin, Delete) | UI |
403 -| Swipe-to-delete with undo snackbar | UI |
404 -| Auto-title generation (LLM-based after first exchange) | Logic |
405 -| Inline rename dialog | UI |
406 -
407 -### Step 8: Design System Components (UI) ✅ DONE
408 -**Why eighth:** Reusable building blocks for polish.
409 -
410 -| Sub-task | Type |
411 -|----------|------|
412 -| `AppButton` — primary, outline, ghost, destructive, loading, icon | Component |
413 -| `AppCard` — standard card with border, tap, padding | Component |
414 -| `AppTextField` — styled text input | Component |
415 -| `AppSheet` — bottom sheet wrapper with drag handle | Component |
416 -| `AppDialog` — dialog wrapper | Component |
417 -| `AppLoadingIndicator` — accent-colored spinner | Component |
418 -| `AppSizes` — spacing and radius constants | Constants |
419 -
420 -### Step 9: Error Handling (Logic) ✅ DONE
421 -**Why ninth:** Centralizes error messages.
422 -
423 -| Sub-task | Type |
424 -|----------|------|
425 -| `AppException` model with type, message, detail | Model |
426 -| `AppErrorType` enum mapping to user-friendly strings | Model |
427 -| Wire into chat, server, and model services | Logic |
428 -
429 -### Step 10: Extended Features (UI + Logic) ✅ DONE
430 -**Why last:** Polished app first, then extra features.
431 -
432 -| Sub-task | Type | Status |
433 -|----------|------|--------|
434 -| TTS — read aloud button + voice settings | Feature | ✅ Done |
435 -| Export — Markdown + text + share | Feature | ✅ Done |
436 -| Smart Replies — follow-up chips after assistant response | Feature | ✅ Done |
437 -| Multimodal — image attachment (wire existing button) | Feature | ✅ Done |
438 -| Quick-launch shortcuts | Feature | ✅ Done |