diff --git a/__LOCAL_LLMs/docs/DASHBOARD_REVIEW.md b/__LOCAL_LLMs/docs/DASHBOARD_REVIEW.md index 36691cb4..ec631430 100644 --- a/__LOCAL_LLMs/docs/DASHBOARD_REVIEW.md +++ b/__LOCAL_LLMs/docs/DASHBOARD_REVIEW.md @@ -1,323 +1,253 @@ # 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 -### 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` -**Severity:** Medium -**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 UI — no progress feedback** — `api/ollama/route.ts:84-92` + `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.)_ -### 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` -**Severity:** High -**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. +- [ ] **B4. Escape key closes modal during active streaming** — `page.tsx:188-197` + Global `keydown` handler calls `setPromptModel(null)` unconditionally. Backdrop click correctly checks `!promptLoading`. Escape should also respect `promptLoading` to prevent discarding an in-flight response. -### 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` -**Severity:** Low -**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. +- [ ] **B6. Toast ID collision on HMR remount** — `page.tsx:156-159` + `toastId.current` resets to 0 on component remount during dev. Use `Date.now()` or `crypto.randomUUID()` for robust uniqueness. -### 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` -**Severity:** Medium -**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. +- [ ] **B8. Whisper models dir not configurable** — `api/whisper/route.ts:24` + 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. -### 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` -**Severity:** Medium -**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. +- [ ] **B10. Brew shows "Loading..." when array is empty** — `page.tsx:936-940` + When `system.brewPackages` is `[]` (all uninstalled), displays "Loading..." instead of "No packages found". Needs to distinguish "still fetching" vs "fetched but empty". -### B6. Toast ID uses incrementing ref (not globally unique) - -**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". +- [ ] **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. --- -## 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 -**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. -**Fix:** Extract into: +- [ ] **CQ2. Pervasive inline styles instead of CSS/Tailwind classes** — `page.tsx` (100+ occurrences) + 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`). -- `components/StatusDot.tsx` -- `components/ProgressBar.tsx` -- `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) +- [ ] **CQ3. OLLAMA_URL duplicated** — `api/ollama/route.ts:3` + `api/ollama/stream/route.ts:3` + Same `process.env.OLLAMA_URL || 'http://localhost:11434'` in two files. Extract to `lib/ollama-config.ts`. -### 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 -**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. -**Fix:** Define utility classes in `globals.css` (e.g., `.text-muted { color: var(--text-tertiary); }`) or use Tailwind's `text-[var(--text-tertiary)]` syntax. +- [ ] **CQ5. No loading skeleton / shimmer UI** + Initial load shows "..." placeholders. Skeleton cards would be more polished. -### CQ3. OLLAMA_URL duplicated across two files - -**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. +- [ ] **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. --- -## 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 -**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. +- [ ] **F2. Model search/filter** + 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 -**Description:** When many models are installed (10+), add a search/filter input above the models list. Filter by name, family, or quantization level. +- [ ] **F4. Chat mode (multi-turn conversation)** + 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 -**Description:** Store recent prompts in `localStorage` and show a dropdown/list in the prompt modal. Allow re-running previous prompts with one click. +- [ ] **F6. Token/s metrics after generation** + 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 -**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. +- [ ] **F8. Ollama server logs viewer** + 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 -**Description:** Side-by-side prompt comparison: send the same prompt to 2 models simultaneously and show both responses for latency/quality comparison. +- [ ] **F10. Dark/light theme toggle** + 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 -**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. +- [ ] **F12. Whisper transcription test** + 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 -**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. +- [ ] **F14. Model tags/labels (localStorage)** + 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 -**Description:** Read Ollama server logs (stdout/stderr or `~/.ollama/logs/`) and display in a collapsible panel. Useful for debugging model loading issues. - -### 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. +- [ ] **F16. Auto-load preferred model** + Mark a model as "auto-load" (stored in localStorage). When Ollama is online but no models loaded, auto-load the preferred model. --- ## 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 -**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`). +- [ ] **P2. Static cache never expires** — `api/system/route.ts:81-90` + `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` -**Severity:** Low -**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. +- [ ] **P4. No fetch timeout on Ollama calls** — `api/ollama/route.ts:5-12` + `fetchOllama` has no `AbortSignal` or timeout. If Ollama hangs, the dashboard hangs. Add 5-second timeout. -### P3. `du -sk ~/.ollama/models` can be slow - -**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. +- [ ] **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. --- ## 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` -**Severity:** Medium -**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 command interpolation pattern** — `api/system/route.ts:67` + `execAsync(\`brew list --versions ${pkg}\`)`— safe today (hardcoded targets) but fragile. Use`execFile('brew', ['list', '--versions', pkg])` for safety. -### S2. Shell injection via brew package names - -**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. +- [ ] **S3. No CORS or auth** _(acceptable for local-only)_ + Any local process can call API routes. Fine for dev tool; document the assumption. --- -## 6. Priority Matrix +## 6. Implementation Tracker -| ID | Type | Priority | Effort | Impact | -| ---------- | ------- | -------- | ------- | ------ | -| B2 | Bug | P0 | Medium | High | -| B4 | Bug | P0 | Low | Medium | -| B5 | Bug | P1 | Low | Medium | -| B9 | Bug | P1 | Low | Medium | -| B1 | Bug | P1 | Low | Low | -| P4 | Perf | P1 | Low | Medium | -| CQ1 | Quality | P1 | High | High | -| F1 | Feature | P1 | Medium | High | -| F6 | Feature | P1 | Low | Medium | -| CQ4 | Quality | P2 | Low | Medium | -| F3 | Feature | P2 | Medium | Medium | -| F4 | Feature | P2 | High | High | -| F12 | Feature | P2 | Medium | Medium | -| F15 | Feature | P2 | Medium | Medium | -| B8 | Bug | P2 | Low | Low | -| B10 | Bug | P2 | Low | Low | -| P1 | Perf | P2 | Low | Low | -| P2 | Perf | P2 | Low | Low | -| P3 | Perf | P2 | Low | Low | -| F2 | Feature | P3 | Low | Medium | -| F7 | Feature | P3 | Medium | Medium | -| F9 | Feature | P3 | Low | Low | -| F11 | Feature | P3 | Low | Low | -| All others | Various | P3+ | Various | Low | +### Sprint 1 — Critical Bug Fixes _(est. 1–2 hrs)_ + +| # | ID | Task | Effort | Commit | +| --- | --------- | ----------------------------------------- | ------ | ------ | +| 1 | - [ ] B4 | Guard Escape key during streaming | 5 min | | +| 2 | - [ ] B5 | Pause auto-refresh during prompt/pull | 10 min | | +| 3 | - [ ] B9 | Add AbortController to streaming fetch | 15 min | | +| 4 | - [ ] B1 | Dynamic chip/RAM in header | 5 min | | +| 5 | - [ ] B11 | Clear prompt text on Escape close | 5 min | | +| 6 | - [ ] P4 | Add timeout to Ollama fetch calls | 10 min | | +| 7 | - [ ] B3 | Remove dead generate action (or document) | 5 min | | +| 8 | - [ ] B6 | Use Date.now() for toast IDs | 2 min | | +| 9 | - [ ] B10 | Fix brew "Loading..." vs "empty" state | 5 min | | + +### Sprint 2 — Pull Progress + Metrics _(est. 2–3 hrs)_ + +| # | ID | Task | Effort | Commit | +| --- | ----------- | ----------------------------------- | ------ | ------ | +| 10 | - [ ] B2+F1 | Streaming pull with progress bar | 60 min | | +| 11 | - [ ] F6 | Display tokens/s after generation | 30 min | | +| 12 | - [ ] B7 | Parse vm_stat page size dynamically | 10 min | | +| 13 | - [ ] B8 | Multi-path whisper model discovery | 15 min | | + +### Sprint 3 — Component Refactor _(est. 2–3 hrs)_ + +| # | 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. 3–4 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. 2–3 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. 1–2 hrs) +_Commits will be added here as work progresses._ -1. **B4** — Guard Escape key during streaming -2. **B5** — Pause auto-refresh during prompt/pull -3. **B9** — Add AbortController to streaming fetch -4. **B1** — Dynamic chip/RAM in header -5. **P4** — Add timeout to Ollama fetch calls - -### Sprint 2 — Pull progress + metrics (est. 2–3 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. 2–3 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. 3–4 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. 2–3 hrs) - -17. **F15** — Extraction service panel -18. **F9** — Modelfile viewer -19. **F7** — Resource trend sparklines -20. **F11** — Keyboard shortcuts panel +| # | Date | Commit | Sprint | Items Completed | +| --- | ---- | ------ | ------ | --------------- | +| 1 | | | | | --- -_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:** ~14–17 hours across 5 sprints