docs(local-llm): improve dashboard review — add checkboxes, commit log, new findings

Rewrite DASHBOARD_REVIEW.md with progress-tracking improvements:
- Add GitHub-style checkboxes to all 41 actionable items
- Add file inventory table with line counts and purposes
- Add commit log section for tracking implementation progress
- Add sprint tracker tables with effort estimates and commit columns
- New finding B11: prompt text not cleared on Escape close
- New finding CQ6: no Zod validation on API responses
- Consolidate priority matrix into sprint tables (less redundancy)
- Add deferred items section with dependency notes
- Improve item descriptions with more precise file:line references
- Add stack summary and total effort estimate (14–17 hrs)
This commit is contained in:
saravanakumardb1 2026-02-19 15:11:19 -08:00
parent 093682eace
commit 554a5137ec

View File

@ -1,323 +1,253 @@
# Mission Control Dashboard — Bug & Improvement Review # Mission Control Dashboard — Bug & Improvement Review
> Systematic review of `__LOCAL_LLMs/dashboard/` — Feb 19, 2026 > Systematic code review of `__LOCAL_LLMs/dashboard/` (6 source files, 1,395 lines)
> Last updated: Feb 19, 2026
---
## File Inventory
| File | Lines | Purpose |
| ------------------------------------ | ----- | -------------------------------------------------------------------- |
| `src/app/page.tsx` | 1,079 | Main dashboard UI (single component) |
| `src/app/globals.css` | 91 | Design tokens, animations, base styles |
| `src/app/layout.tsx` | 20 | Root layout (metadata, dark mode) |
| `src/app/api/ollama/route.ts` | 117 | Ollama REST proxy (list, load, unload, pull, delete, show, generate) |
| `src/app/api/ollama/stream/route.ts` | 38 | Ollama streaming generate proxy (NDJSON) |
| `src/app/api/whisper/route.ts` | 66 | Whisper binary + GGML model discovery |
| `src/app/api/system/route.ts` | 162 | System info (chip, memory via vm_stat, disk, brew) |
**Stack:** Next.js 16, React 19, TailwindCSS v4, Lucide icons, TypeScript
--- ---
## 1. Bugs ## 1. Bugs
### B1. Hardcoded "Apple M4 Pro · 48 GB" in header - [ ] **B1. Hardcoded machine specs in header**`page.tsx:317`
Subtitle reads `Apple M4 Pro · 48 GB · {system?.platform}` — should use `system?.chip` and `formatBytes(system?.memory.total)` dynamically so it works on any machine.
**File:** `page.tsx:317` - [ ] **B2. Pull model blocks UI — no progress feedback**`api/ollama/route.ts:84-92`
**Severity:** Medium `handlePull` calls Ollama with `stream: false`. Large models (20+ GB) block for 30+ minutes. The Next.js API route will likely timeout. Must use `stream: true` and pipe progress events to the client. _(Combined with F1.)_
**Issue:** Header subtitle is hardcoded: `Apple M4 Pro · 48 GB · {system?.platform}`. Should use `system?.chip` and `system?.memory.total` dynamically so it works on any machine.
### B2. Pull model blocks entire UI with `stream: false` - [ ] **B3. Dead code: non-streaming `generate` action**`api/ollama/route.ts:69-82`
The `action === 'generate'` handler is unused — UI only uses `/api/ollama/stream`. Remove or keep as fallback with a comment.
**File:** `api/ollama/route.ts:88` - [ ] **B4. Escape key closes modal during active streaming**`page.tsx:188-197`
**Severity:** High Global `keydown` handler calls `setPromptModel(null)` unconditionally. Backdrop click correctly checks `!promptLoading`. Escape should also respect `promptLoading` to prevent discarding an in-flight response.
**Issue:** `handlePull` calls the Ollama pull API with `stream: false`, which means the HTTP request blocks until the entire download completes. For large models (e.g., 20 GB), this can take 30+ minutes. The Next.js API route will likely timeout, and the user has no progress feedback.
**Fix:** Use `stream: true` for pull and pipe progress events to the client (like the generate stream endpoint). Show download percentage in the UI.
### B3. Generate (non-streaming) endpoint still exists but is unused - [ ] **B5. Auto-refresh (15s) fires during streaming/pull**`page.tsx:182-185`
`setInterval(fetchAll, 15000)` runs unconditionally. During streaming this causes background churn and potential UI flicker. Should pause while `promptLoading` or `pullLoading` is true.
**File:** `api/ollama/route.ts:69-82` - [ ] **B6. Toast ID collision on HMR remount**`page.tsx:156-159`
**Severity:** Low `toastId.current` resets to 0 on component remount during dev. Use `Date.now()` or `crypto.randomUUID()` for robust uniqueness.
**Issue:** The `action === 'generate'` block in the POST handler is dead code — the UI exclusively uses the `/api/ollama/stream` endpoint for prompts. Dead code adds maintenance burden.
### B4. Escape key closes prompt modal even while streaming - [ ] **B7. vm_stat page size hardcoded**`api/system/route.ts:103`
Hardcoded `16384`. Should parse from vm_stat's first line: `"(page size of NNNNN bytes)"` for portability.
**File:** `page.tsx:188-197` - [ ] **B8. Whisper models dir not configurable**`api/whisper/route.ts:24`
**Severity:** Medium Hardcoded to `~/whisper-models`. Should scan multiple known paths (`/opt/homebrew/share/whisper-cpp/models/`, `~/whisper-models`, `~/.cache/whisper/`) or accept `WHISPER_MODELS_DIR` env var.
**Issue:** The global Escape keydown handler calls `setPromptModel(null)` unconditionally, but the backdrop click handler correctly checks `!promptLoading` before closing. Pressing Escape during an active stream will close the modal and discard the response. The Escape handler should respect `promptLoading` state too.
### B5. Auto-refresh (15s) during active streaming - [ ] **B9. No AbortController for streaming fetch**`page.tsx:250-289`
Closing the prompt modal doesn't cancel the underlying fetch. The `reader.read()` loop continues in the background wasting CPU/bandwidth until the model finishes generating.
**File:** `page.tsx:182-185` - [ ] **B10. Brew shows "Loading..." when array is empty**`page.tsx:936-940`
**Severity:** Medium When `system.brewPackages` is `[]` (all uninstalled), displays "Loading..." instead of "No packages found". Needs to distinguish "still fetching" vs "fetched but empty".
**Issue:** The `setInterval(fetchAll, 15000)` runs unconditionally, including while a prompt is streaming. This causes background network churn and state updates (flicker) while the user is reading a streaming response. Should pause auto-refresh while `promptLoading` or `pullLoading` is true.
### B6. Toast ID uses incrementing ref (not globally unique) - [ ] **B11. Prompt text not cleared on close without send**`page.tsx:951-957`
Backdrop click clears `promptText`, but Escape handler (B4 fix) should also clear it. Otherwise stale text persists when re-opening.
**File:** `page.tsx:156-159`
**Severity:** Low
**Issue:** `toastId.current` is a simple incrementing number. If the component remounts (e.g., HMR during dev), it resets to 0, potentially creating duplicate IDs. Not a production risk but can cause React key warnings during development.
### B7. `vm_stat` page size is hardcoded to 16384
**File:** `api/system/route.ts:103`
**Severity:** Low
**Issue:** Page size is hardcoded as `16384` (Apple Silicon default). The `vm_stat` output actually prints the page size on its first line: `"Mach Virtual Memory Statistics: (page size of 16384 bytes)"`. Should parse it from the output for correctness on non-standard configurations.
### B8. Whisper models directory hardcoded to `~/whisper-models`
**File:** `api/whisper/route.ts:24`
**Severity:** Medium
**Issue:** The models directory is hardcoded to `~/whisper-models`. Different users or Homebrew versions may store GGML models elsewhere (e.g., `/opt/homebrew/share/whisper-cpp/models/`). Should check multiple known paths or make it configurable via env var.
### B9. No abort controller for streaming fetch
**File:** `page.tsx:250-289`
**Severity:** Medium
**Issue:** When the user closes the prompt modal during streaming, the `reader.read()` loop continues running in the background. There's no `AbortController` to cancel the fetch. This wastes bandwidth and CPU until the model finishes generating.
### B10. Brew packages shows "Loading..." when none are installed
**File:** `page.tsx:936-940`
**Severity:** Low
**Issue:** When `system.brewPackages` is an empty array (all 3 packages not installed), the UI shows "Loading..." instead of "No packages found". The condition checks `length === 0` but doesn't distinguish between "still loading" and "loaded but empty".
--- ---
## 2. Code Quality Issues ## 2. Code Quality
### CQ1. 1079-line single component (page.tsx) - [ ] **CQ1. Monolithic 1,079-line single component**`page.tsx`
All interfaces, utilities, sub-components, and 900+ lines of JSX in one file. Extract to:
- `components/` — StatusDot, ProgressBar, ToastContainer, PromptModal, OllamaModelsPanel, SystemPanel, WhisperPanel, BrewPanel
- `lib/types.ts` — interfaces (OllamaModel, SystemData, etc.)
- `lib/format.ts` — formatBytes, formatUptime
- `lib/hooks.ts` — useAutoRefresh, useToasts, useOllamaActions
**Severity:** Medium - [ ] **CQ2. Pervasive inline styles instead of CSS/Tailwind classes**`page.tsx` (100+ occurrences)
**Issue:** The entire dashboard is a single `Dashboard` component in one file. It contains interfaces, utility functions, sub-components (`StatusDot`, `ProgressBar`), and 900+ lines of JSX. This makes it hard to read, test, or extend. Every `style={{ color: 'var(--text-tertiary)' }}` should be a utility class. Options: custom Tailwind theme mapping, or CSS utility classes in `globals.css` (e.g., `.text-muted`).
**Fix:** Extract into:
- `components/StatusDot.tsx` - [ ] **CQ3. OLLAMA_URL duplicated**`api/ollama/route.ts:3` + `api/ollama/stream/route.ts:3`
- `components/ProgressBar.tsx` Same `process.env.OLLAMA_URL || 'http://localhost:11434'` in two files. Extract to `lib/ollama-config.ts`.
- `components/ToastContainer.tsx`
- `components/PromptModal.tsx`
- `components/OllamaModelsPanel.tsx`
- `components/SystemPanel.tsx`
- `components/WhisperPanel.tsx`
- `components/BrewPanel.tsx`
- `lib/types.ts` (interfaces)
- `lib/format.ts` (formatBytes, formatUptime)
### CQ2. Inline styles everywhere instead of CSS classes - [ ] **CQ4. No React Error Boundary**`page.tsx`
Unexpected API response shape crashes the entire dashboard. Add an `error.tsx` (Next.js App Router convention) for graceful recovery.
**Severity:** Low - [ ] **CQ5. No loading skeleton / shimmer UI**
**Issue:** Most color/background styling is done via inline `style={{ color: 'var(--text-tertiary)' }}` rather than Tailwind utilities or CSS classes. This adds JSX noise and makes theming harder. Initial load shows "..." placeholders. Skeleton cards would be more polished.
**Fix:** Define utility classes in `globals.css` (e.g., `.text-muted { color: var(--text-tertiary); }`) or use Tailwind's `text-[var(--text-tertiary)]` syntax.
### CQ3. OLLAMA_URL duplicated across two files - [ ] **CQ6. No TypeScript strict null checks in API responses**
API route handlers catch errors but return loosely typed JSON. Add Zod validation on the Ollama/system responses to prevent runtime surprises.
**File:** `api/ollama/route.ts:3`, `api/ollama/stream/route.ts:3`
**Severity:** Low
**Issue:** The Ollama URL default is defined independently in both files. Should be a shared constant or env utility.
### CQ4. No error boundaries
**Severity:** Medium
**Issue:** If any component throws during render (e.g., unexpected API response shape), the entire dashboard crashes with an unhandled error. Should add a React Error Boundary for graceful degradation.
### CQ5. No loading skeleton
**Severity:** Low
**Issue:** On initial load, the dashboard shows empty cards with "..." values. A skeleton/shimmer UI would look more polished.
--- ---
## 3. Feature Improvements ## 3. Features
### F1. Pull progress streaming - [ ] **F1. Streaming pull with progress bar** _(fixes B2)_
Use Ollama `stream: true` for `/api/pull`. Create `/api/ollama/pull/route.ts` that pipes NDJSON progress. UI shows progress bar with `completed/total` bytes, speed, and ETA.
**Priority:** High - [ ] **F2. Model search/filter**
**Description:** Stream model pull progress from Ollama (`stream: true` on `/api/pull`) and show a real-time progress bar with download percentage, speed, and ETA. Current pull just shows a toast and blocks. Search input above models list. Filter by name, family, quantization. Useful when 10+ models are installed.
### F2. Model search/filter - [ ] **F3. Prompt history (localStorage)**
Store last 20 prompts with model name + timestamp. Dropdown in prompt modal to re-run previous prompts.
**Priority:** Medium - [ ] **F4. Chat mode (multi-turn conversation)**
**Description:** When many models are installed (10+), add a search/filter input above the models list. Filter by name, family, or quantization level. Use Ollama `/api/chat` instead of `/api/generate`. Chat bubble layout with message history. System prompt input field.
### F3. Prompt history - [ ] **F5. Model comparison (side-by-side)**
Send same prompt to 2 models simultaneously. Display responses side-by-side with latency/quality comparison.
**Priority:** Medium - [ ] **F6. Token/s metrics after generation**
**Description:** Store recent prompts in `localStorage` and show a dropdown/list in the prompt modal. Allow re-running previous prompts with one click. Parse `eval_count` and `eval_duration` from the final NDJSON chunk. Display tokens/second, total tokens, and latency in the response footer.
### F4. Chat mode (multi-turn) - [ ] **F7. System resource sparklines (time-series)**
Ring buffer of memory/CPU snapshots (localStorage). Render mini sparkline charts in the System panel. Spot trends over time.
**Priority:** Medium - [ ] **F8. Ollama server logs viewer**
**Description:** Current prompt modal is single-shot. Add chat mode with conversation history, using Ollama's `/api/chat` endpoint instead of `/api/generate`. Show messages in a chat bubble layout. Read `~/.ollama/logs/` and display in a collapsible terminal-style panel. Filter by level. Auto-scroll.
### F5. Model comparison - [ ] **F9. Modelfile / template viewer**
The `show` action already fetches Modelfile, template, and system prompt. Display in a collapsible code block in expanded model details.
**Priority:** Low - [ ] **F10. Dark/light theme toggle**
**Description:** Side-by-side prompt comparison: send the same prompt to 2 models simultaneously and show both responses for latency/quality comparison. Add `:root.light` CSS variable overrides. Theme toggle with localStorage persistence. Current architecture supports this natively.
### F6. Token/s metrics display - [ ] **F11. Keyboard shortcuts panel (`?` key)**
Show all shortcuts in a modal: ⌘+Enter (send), Esc (close), R (refresh), / (search models), ? (help).
**Priority:** Medium - [ ] **F12. Whisper transcription test**
**Description:** The streaming endpoint already receives `eval_count` and `eval_duration` in the final NDJSON chunk. Parse and display tokens/second after generation completes. Already available in the non-streaming generate response but not surfaced in UI. Upload/record a short audio clip, transcribe locally via whisper-cli, display result with latency. Tests the full local STT pipeline.
### F7. System resource charts (time series) - [ ] **F13. Responsive mobile layout**
Better breakpoints for the 4-column stats row and 3-column main grid. Collapsible sidebar on mobile.
**Priority:** Medium - [ ] **F14. Model tags/labels (localStorage)**
**Description:** Store memory/CPU snapshots over time (in-memory ring buffer or localStorage) and render a mini sparkline chart showing resource usage trends. Helpful for spotting memory leaks from loaded models. User-defined tags (coding, fast, vision) with colored badges. Persisted in localStorage.
### F8. Ollama server logs viewer - [ ] **F15. Extraction service integration panel**
Show extraction-service (port 4005) health status. Run test extractions against loaded Ollama models. Bridges dashboard to LysnrAI pipeline.
**Priority:** Low - [ ] **F16. Auto-load preferred model**
**Description:** Read Ollama server logs (stdout/stderr or `~/.ollama/logs/`) and display in a collapsible panel. Useful for debugging model loading issues. Mark a model as "auto-load" (stored in localStorage). When Ollama is online but no models loaded, auto-load the preferred model.
### F9. Model Modelfile viewer
**Priority:** Low
**Description:** The `show` action already fetches model details including the Modelfile. Add a collapsible code block in the expanded model details showing the full Modelfile, template, and system prompt.
### F10. Dark/light theme toggle
**Priority:** Low
**Description:** Current dashboard is dark-only. Add a theme toggle storing preference in localStorage. The CSS variables architecture already supports it — just need a `:root.light` override.
### F11. Keyboard shortcuts panel
**Priority:** Low
**Description:** Add a `?` keyboard shortcut to show all available shortcuts. Currently only Cmd+Enter (send) and Escape (close) exist but aren't discoverable.
### F12. Whisper transcription test
**Priority:** Medium
**Description:** Add a "Test" button in the Whisper panel that lets users upload/record a short audio clip and transcribe it locally. Shows real latency and accuracy.
### F13. Responsive / mobile layout
**Priority:** Low
**Description:** The grid layout works but isn't optimized for mobile. The 4-column stats row and 3-column main grid could use better breakpoints and a hamburger menu for mobile.
### F14. Model tags/labels
**Priority:** Low
**Description:** Let users tag models (e.g., "coding", "fast", "vision") with colored labels for quick visual identification. Store in localStorage.
### F15. Extraction service integration
**Priority:** Medium
**Description:** Add a panel showing extraction-service status (port 4005), active tasks, and a button to run test extractions against loaded Ollama models. This connects the dashboard to the actual LysnrAI pipeline.
### F16. Auto-load preferred model on Ollama start
**Priority:** Low
**Description:** Let users mark a model as "auto-load". When the dashboard detects Ollama is online but no models are loaded, automatically load the preferred model.
--- ---
## 4. Performance & Reliability ## 4. Performance & Reliability
### P1. No request deduplication - [ ] **P1. No request deduplication on Refresh**`page.tsx:164-176`
Rapid clicks on Refresh fire duplicate `fetchAll()` calls. Add a `fetchingRef` guard or disable the button during fetch (partially done for `actionLoading` but not for `fetchAll`).
**Severity:** Medium - [ ] **P2. Static cache never expires**`api/system/route.ts:81-90`
**Issue:** Rapid clicks on Refresh or model actions can fire duplicate requests. Add request deduplication or disable buttons during pending requests (partially done for `actionLoading` but not for `fetchAll`). `staticCache` (chip, GPU, brew) lives forever in the server process. Brew package upgrades won't reflect. Add 5-minute TTL.
### P2. Static cache never invalidates - [ ] **P3. `du -sk ~/.ollama/models` on every refresh**`api/system/route.ts:41`
Traverses entire models directory every 15 seconds. Cache with 60-second TTL.
**File:** `api/system/route.ts:81-90` - [ ] **P4. No fetch timeout on Ollama calls**`api/ollama/route.ts:5-12`
**Severity:** Low `fetchOllama` has no `AbortSignal` or timeout. If Ollama hangs, the dashboard hangs. Add 5-second timeout.
**Issue:** `staticCache` (chip, GPU, brew packages) is cached indefinitely per server process. If the user installs/upgrades a brew package while the dashboard is running, it won't reflect. Add a TTL (e.g., 5 minutes) or a manual "Refresh system info" button.
### P3. `du -sk ~/.ollama/models` can be slow - [ ] **P5. `system_profiler` slow on first load**`api/system/route.ts:52-53`
Takes ~2-3 seconds. Cached after first call, but first dashboard load waits. Consider eager background fetch on server start or return placeholder.
**File:** `api/system/route.ts:41`
**Severity:** Low
**Issue:** `du` traverses the entire models directory on every system API call (every 15s refresh). For large model collections, this can take seconds. Should cache with TTL (e.g., 60s).
### P4. No connection timeout on Ollama fetch
**File:** `api/ollama/route.ts:5-12`
**Severity:** Medium
**Issue:** `fetchOllama` has no `signal` (AbortController) or timeout. If Ollama hangs or is unreachable, the request hangs indefinitely. Add a 5-second timeout.
### P5. `system_profiler SPDisplaysDataType` is slow (~2-3s)
**File:** `api/system/route.ts:52-53`
**Severity:** Low (cached)
**Issue:** This command is slow but cached on first call. However, the first dashboard load still waits for it. Consider running it lazily or returning a placeholder while it loads.
--- ---
## 5. Security & Hardening ## 5. Security & Hardening
### S1. No input validation on model names - [ ] **S1. No input validation on model names**`api/ollama/route.ts:50-51`
`model` from request body passed directly to Ollama. Add regex validation: `^[a-zA-Z0-9._:/-]{1,256}$`.
**File:** `api/ollama/route.ts:50-51` - [ ] **S2. Shell command interpolation pattern**`api/system/route.ts:67`
**Severity:** Medium `execAsync(\`brew list --versions ${pkg}\`)`— safe today (hardcoded targets) but fragile. Use`execFile('brew', ['list', '--versions', pkg])` for safety.
**Issue:** The `model` field from the request body is passed directly to Ollama API calls without validation. While Ollama itself validates, the dashboard should sanitize to prevent unexpected payloads (e.g., very long strings, special characters).
### S2. Shell injection via brew package names - [ ] **S3. No CORS or auth** _(acceptable for local-only)_
Any local process can call API routes. Fine for dev tool; document the assumption.
**File:** `api/system/route.ts:67`
**Severity:** Low (hardcoded targets)
**Issue:** The `targets` array is hardcoded so this isn't exploitable now, but the `execAsync` pattern of interpolating into shell commands is risky if the targets list ever becomes dynamic. Use `execFile` instead of `exec` for safety.
### S3. No CORS or auth
**Severity:** Low (local-only)
**Issue:** The dashboard has no authentication or CORS restrictions. Any local process or browser tab can call the API routes. Acceptable for a local dev tool but worth noting.
--- ---
## 6. Priority Matrix ## 6. Implementation Tracker
| ID | Type | Priority | Effort | Impact | ### Sprint 1 — Critical Bug Fixes _(est. 12 hrs)_
| ---------- | ------- | -------- | ------- | ------ |
| B2 | Bug | P0 | Medium | High | | # | ID | Task | Effort | Commit |
| B4 | Bug | P0 | Low | Medium | | --- | --------- | ----------------------------------------- | ------ | ------ |
| B5 | Bug | P1 | Low | Medium | | 1 | - [ ] B4 | Guard Escape key during streaming | 5 min | |
| B9 | Bug | P1 | Low | Medium | | 2 | - [ ] B5 | Pause auto-refresh during prompt/pull | 10 min | |
| B1 | Bug | P1 | Low | Low | | 3 | - [ ] B9 | Add AbortController to streaming fetch | 15 min | |
| P4 | Perf | P1 | Low | Medium | | 4 | - [ ] B1 | Dynamic chip/RAM in header | 5 min | |
| CQ1 | Quality | P1 | High | High | | 5 | - [ ] B11 | Clear prompt text on Escape close | 5 min | |
| F1 | Feature | P1 | Medium | High | | 6 | - [ ] P4 | Add timeout to Ollama fetch calls | 10 min | |
| F6 | Feature | P1 | Low | Medium | | 7 | - [ ] B3 | Remove dead generate action (or document) | 5 min | |
| CQ4 | Quality | P2 | Low | Medium | | 8 | - [ ] B6 | Use Date.now() for toast IDs | 2 min | |
| F3 | Feature | P2 | Medium | Medium | | 9 | - [ ] B10 | Fix brew "Loading..." vs "empty" state | 5 min | |
| F4 | Feature | P2 | High | High |
| F12 | Feature | P2 | Medium | Medium | ### Sprint 2 — Pull Progress + Metrics _(est. 23 hrs)_
| F15 | Feature | P2 | Medium | Medium |
| B8 | Bug | P2 | Low | Low | | # | ID | Task | Effort | Commit |
| B10 | Bug | P2 | Low | Low | | --- | ----------- | ----------------------------------- | ------ | ------ |
| P1 | Perf | P2 | Low | Low | | 10 | - [ ] B2+F1 | Streaming pull with progress bar | 60 min | |
| P2 | Perf | P2 | Low | Low | | 11 | - [ ] F6 | Display tokens/s after generation | 30 min | |
| P3 | Perf | P2 | Low | Low | | 12 | - [ ] B7 | Parse vm_stat page size dynamically | 10 min | |
| F2 | Feature | P3 | Low | Medium | | 13 | - [ ] B8 | Multi-path whisper model discovery | 15 min | |
| F7 | Feature | P3 | Medium | Medium |
| F9 | Feature | P3 | Low | Low | ### Sprint 3 — Component Refactor _(est. 23 hrs)_
| F11 | Feature | P3 | Low | Low |
| All others | Various | P3+ | Various | Low | | # | ID | Task | Effort | Commit |
| --- | --------- | --------------------------------------- | ------ | ------ |
| 14 | - [ ] CQ1 | Extract components into separate files | 90 min | |
| 15 | - [ ] CQ4 | Add error.tsx Error Boundary | 15 min | |
| 16 | - [ ] CQ3 | Shared ollama-config.ts | 10 min | |
| 17 | - [ ] CQ2 | Consolidate inline styles → CSS classes | 45 min | |
| 18 | - [ ] S1 | Add model name input validation | 10 min | |
| 19 | - [ ] S2 | Replace exec → execFile for brew | 10 min | |
### Sprint 4 — UX Enhancements _(est. 34 hrs)_
| # | ID | Task | Effort | Commit |
| --- | ----------- | ------------------------------------ | ------ | ------ |
| 20 | - [ ] F3 | Prompt history (localStorage) | 45 min | |
| 21 | - [ ] F6+F9 | Token metrics + Modelfile viewer | 30 min | |
| 22 | - [ ] F4 | Chat mode (multi-turn via /api/chat) | 90 min | |
| 23 | - [ ] F2 | Model search/filter | 30 min | |
| 24 | - [ ] F11 | Keyboard shortcuts panel | 20 min | |
### Sprint 5 — Integration & Polish _(est. 23 hrs)_
| # | ID | Task | Effort | Commit |
| --- | ----------- | -------------------------- | ------ | ------ |
| 25 | - [ ] F15 | Extraction service panel | 60 min | |
| 26 | - [ ] F12 | Whisper transcription test | 45 min | |
| 27 | - [ ] F7 | System resource sparklines | 45 min | |
| 28 | - [ ] CQ5 | Loading skeleton UI | 20 min | |
| 29 | - [ ] P1-P3 | Request dedup + cache TTLs | 30 min | |
| 30 | - [ ] F16 | Auto-load preferred model | 20 min | |
### Deferred (nice-to-have)
| ID | Task | Notes |
| --------- | ------------------------------- | -------------------------- |
| - [ ] F5 | Model comparison (side-by-side) | Needs F4 (chat) first |
| - [ ] F10 | Dark/light theme toggle | Low priority, dark is fine |
| - [ ] F13 | Responsive mobile layout | Desktop-first tool |
| - [ ] F14 | Model tags/labels | After F2 (search) |
| - [ ] CQ6 | Zod validation on API responses | After CQ1 (refactor) |
| - [ ] F8 | Ollama server logs viewer | Nice but low ROI |
| - [ ] S3 | CORS / auth | Local-only, acceptable |
--- ---
## 7. Recommended Implementation Order ## 7. Commit Log
### Sprint 1 — Critical fixes (est. 12 hrs) _Commits will be added here as work progresses._
1. **B4** — Guard Escape key during streaming | # | Date | Commit | Sprint | Items Completed |
2. **B5** — Pause auto-refresh during prompt/pull | --- | ---- | ------ | ------ | --------------- |
3. **B9** — Add AbortController to streaming fetch | 1 | | | | |
4. **B1** — Dynamic chip/RAM in header
5. **P4** — Add timeout to Ollama fetch calls
### Sprint 2 — Pull progress + metrics (est. 23 hrs)
6. **B2/F1** — Streaming pull with progress bar
7. **F6** — Display tokens/second after generation
8. **B10** — Fix brew "Loading..." vs "empty" state
### Sprint 3 — Component refactor (est. 23 hrs)
9. **CQ1** — Extract components into separate files
10. **CQ4** — Add Error Boundary
11. **CQ2** — Consolidate inline styles
12. **CQ3** — Shared Ollama URL constant
### Sprint 4 — UX enhancements (est. 34 hrs)
13. **F3** — Prompt history (localStorage)
14. **F4** — Chat mode (multi-turn)
15. **F12** — Whisper transcription test
16. **F2** — Model search/filter
### Sprint 5 — Integration & polish (est. 23 hrs)
17. **F15** — Extraction service panel
18. **F9** — Modelfile viewer
19. **F7** — Resource trend sparklines
20. **F11** — Keyboard shortcuts panel
--- ---
_Generated by systematic code review of all 6 source files (1,395 lines total)._ > **39 items total:** 11 bugs, 6 code quality, 16 features, 5 performance, 3 security
> **Estimated total effort:** ~1417 hours across 5 sprints