Check off 5 items (CQ1, CQ3, CQ4, S1, S2) in code quality, security,
and sprint tracker. CQ2 (inline styles) deferred. Add commit 75a3cd0
to commit log.
15 KiB
Mission Control Dashboard — Bug & Improvement Review
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 machine specs in header —
page.tsx:317Subtitle readsApple M4 Pro · 48 GB · {system?.platform}— should usesystem?.chipandformatBytes(system?.memory.total)dynamically so it works on any machine. -
B2. Pull model blocks UI — no progress feedback —
api/ollama/route.ts:84-92handlePullcalls Ollama withstream: false. Large models (20+ GB) block for 30+ minutes. The Next.js API route will likely timeout. Must usestream: trueand pipe progress events to the client. (Combined with F1.) -
B3. Dead code: non-streaming
generateaction —api/ollama/route.ts:69-82Theaction === 'generate'handler is unused — UI only uses/api/ollama/stream. Remove or keep as fallback with a comment. -
B4. Escape key closes modal during active streaming —
page.tsx:188-197Globalkeydownhandler callssetPromptModel(null)unconditionally. Backdrop click correctly checks!promptLoading. Escape should also respectpromptLoadingto prevent discarding an in-flight response. -
B5. Auto-refresh (15s) fires during streaming/pull —
page.tsx:182-185setInterval(fetchAll, 15000)runs unconditionally. During streaming this causes background churn and potential UI flicker. Should pause whilepromptLoadingorpullLoadingis true. -
B6. Toast ID collision on HMR remount —
page.tsx:156-159toastId.currentresets to 0 on component remount during dev. UseDate.now()orcrypto.randomUUID()for robust uniqueness. -
B7. vm_stat page size hardcoded —
api/system/route.ts:103Hardcoded16384. Should parse from vm_stat's first line:"(page size of NNNNN bytes)"for portability. -
B8. Whisper models dir not configurable —
api/whisper/route.ts:24Hardcoded to~/whisper-models. Should scan multiple known paths (/opt/homebrew/share/whisper-cpp/models/,~/whisper-models,~/.cache/whisper/) or acceptWHISPER_MODELS_DIRenv var. -
B9. No AbortController for streaming fetch —
page.tsx:250-289Closing the prompt modal doesn't cancel the underlying fetch. Thereader.read()loop continues in the background wasting CPU/bandwidth until the model finishes generating. -
B10. Brew shows "Loading..." when array is empty —
page.tsx:936-940Whensystem.brewPackagesis[](all uninstalled), displays "Loading..." instead of "No packages found". Needs to distinguish "still fetching" vs "fetched but empty". -
B11. Prompt text not cleared on close without send —
page.tsx:951-957Backdrop click clearspromptText, but Escape handler (B4 fix) should also clear it. Otherwise stale text persists when re-opening.
2. Code Quality
-
CQ1. Monolithic 1,079-line single component —
page.tsxAll interfaces, utilities, sub-components, and 900+ lines of JSX in one file. Extract to:components/— StatusDot, ProgressBar, ToastContainer, PromptModal, OllamaModelsPanel, SystemPanel, WhisperPanel, BrewPanellib/types.ts— interfaces (OllamaModel, SystemData, etc.)lib/format.ts— formatBytes, formatUptimelib/hooks.ts— useAutoRefresh, useToasts, useOllamaActions
-
CQ2. Pervasive inline styles instead of CSS/Tailwind classes —
page.tsx(100+ occurrences) Everystyle={{ color: 'var(--text-tertiary)' }}should be a utility class. Options: custom Tailwind theme mapping, or CSS utility classes inglobals.css(e.g.,.text-muted). -
CQ3. OLLAMA_URL duplicated —
api/ollama/route.ts:3+api/ollama/stream/route.ts:3Sameprocess.env.OLLAMA_URL || 'http://localhost:11434'in two files. Extract tolib/ollama-config.ts. -
CQ4. No React Error Boundary —
page.tsxUnexpected API response shape crashes the entire dashboard. Add anerror.tsx(Next.js App Router convention) for graceful recovery. -
CQ5. No loading skeleton / shimmer UI Initial load shows "..." placeholders. Skeleton cards would be 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. Features
-
F1. Streaming pull with progress bar (fixes B2) Use Ollama
stream: truefor/api/pull. Create/api/ollama/pull/route.tsthat pipes NDJSON progress. UI shows progress bar withcompleted/totalbytes, speed, and ETA. -
F2. Model search/filter Search input above models list. Filter by name, family, quantization. Useful when 10+ models are installed.
-
F3. Prompt history (localStorage) Store last 20 prompts with model name + timestamp. Dropdown in prompt modal to re-run previous prompts.
-
F4. Chat mode (multi-turn conversation) Use Ollama
/api/chatinstead of/api/generate. Chat bubble layout with message history. System prompt input field. -
F5. Model comparison (side-by-side) Send same prompt to 2 models simultaneously. Display responses side-by-side with latency/quality comparison.
-
F6. Token/s metrics after generation Parse
eval_countandeval_durationfrom the final NDJSON chunk. Display tokens/second, total tokens, and latency in the response footer. -
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.
-
F8. Ollama server logs viewer Read
~/.ollama/logs/and display in a collapsible terminal-style panel. Filter by level. Auto-scroll. -
F9. Modelfile / template viewer The
showaction already fetches Modelfile, template, and system prompt. Display in a collapsible code block in expanded model details. -
F10. Dark/light theme toggle Add
:root.lightCSS variable overrides. Theme toggle with localStorage persistence. Current architecture supports this natively. -
F11. Keyboard shortcuts panel (
?key) Show all shortcuts in a modal: ⌘+Enter (send), Esc (close), R (refresh), / (search models), ? (help). -
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.
-
F13. Responsive mobile layout Better breakpoints for the 4-column stats row and 3-column main grid. Collapsible sidebar on mobile.
-
F14. Model tags/labels (localStorage) User-defined tags (coding, fast, vision) with colored badges. Persisted in localStorage.
-
F15. Extraction service integration panel Show extraction-service (port 4005) health status. Run test extractions against loaded Ollama models. Bridges dashboard to LysnrAI pipeline.
-
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 on Refresh —
page.tsx:164-176Rapid clicks on Refresh fire duplicatefetchAll()calls. Add afetchingRefguard or disable the button during fetch (partially done foractionLoadingbut not forfetchAll). -
P2. Static cache never expires —
api/system/route.ts:81-90staticCache(chip, GPU, brew) lives forever in the server process. Brew package upgrades won't reflect. Add 5-minute TTL. -
P3.
du -sk ~/.ollama/modelson every refresh —api/system/route.ts:41Traverses entire models directory every 15 seconds. Cache with 60-second TTL. -
P4. No fetch timeout on Ollama calls —
api/ollama/route.ts:5-12fetchOllamahas noAbortSignalor timeout. If Ollama hangs, the dashboard hangs. Add 5-second timeout. -
P5.
system_profilerslow on first load —api/system/route.ts:52-53Takes ~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 —
api/ollama/route.ts:50-51modelfrom request body passed directly to Ollama. Add regex validation:^[a-zA-Z0-9._:/-]{1,256}$. -
S2. Shell command interpolation pattern —
api/system/route.ts:67execAsync(\brew list --versions ${pkg}`)— safe today (hardcoded targets) but fragile. UseexecFile('brew', ['list', '--versions', pkg])` for safety. -
S3. No CORS or auth (acceptable for local-only) Any local process can call API routes. Fine for dev tool; document the assumption.
6. Implementation Tracker
Sprint 1 — Critical Bug Fixes (est. 1–2 hrs)
| # | ID | Task | Effort | Commit |
|---|---|---|---|---|
| 1 | - [x] B4 | Guard Escape key during streaming | 5 min | 2da67c2 |
| 2 | - [x] B5 | Pause auto-refresh during prompt/pull | 10 min | 2da67c2 |
| 3 | - [x] B9 | Add AbortController to streaming fetch | 15 min | 2da67c2 |
| 4 | - [x] B1 | Dynamic chip/RAM in header | 5 min | 2da67c2 |
| 5 | - [x] B11 | Clear prompt text on Escape close | 5 min | 2da67c2 |
| 6 | - [x] P4 | Add timeout to Ollama fetch calls | 10 min | 2da67c2 |
| 7 | - [x] B3 | Remove dead generate action (or document) | 5 min | 2da67c2 |
| 8 | - [x] B6 | Use Date.now() for toast IDs | 2 min | 2da67c2 |
| 9 | - [x] B10 | Fix brew "Loading..." vs "empty" state | 5 min | 2da67c2 |
Sprint 2 — Pull Progress + Metrics (est. 2–3 hrs)
| # | ID | Task | Effort | Commit |
|---|---|---|---|---|
| 10 | - [x] B2+F1 | Streaming pull with progress bar | 60 min | 2d9475b |
| 11 | - [x] F6 | Display tokens/s after generation | 30 min | 2d9475b |
| 12 | - [x] B7 | Parse vm_stat page size dynamically | 10 min | 2d9475b |
| 13 | - [x] B8 | Multi-path whisper model discovery | 15 min | 2d9475b |
Sprint 3 — Component Refactor (est. 2–3 hrs)
| # | ID | Task | Effort | Commit |
|---|---|---|---|---|
| 14 | - [x] CQ1 | Extract components into separate files | 90 min | 75a3cd0 |
| 15 | - [x] CQ4 | Add error.tsx Error Boundary | 15 min | 75a3cd0 |
| 16 | - [x] CQ3 | Shared ollama-config.ts | 10 min | 75a3cd0 |
| 17 | - [ ] CQ2 | Consolidate inline styles → CSS classes | 45 min | deferred |
| 18 | - [x] S1 | Add model name input validation | 10 min | 75a3cd0 |
| 19 | - [x] S2 | Replace exec → execFile for brew | 10 min | 75a3cd0 |
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. Commit Log
Commits will be added here as work progresses.
| # | Date | Commit | Sprint | Items Completed | |
|---|---|---|---|---|---|
| 1 | Feb 19 | 2da67c2 |
Sprint 1 | B1, B3, B4, B5, B6, B9, B10, B11, P4 | |
| 2 | Feb 19 | 2d9475b |
Sprint 2 | B2, B7, B8, F1, F6 | |
| 3 | Feb 19 | 75a3cd0 |
Sprint 3 | CQ1, CQ3, CQ4, S1, S2 |
39 items total: 11 bugs, 6 code quality, 16 features, 5 performance, 3 security Estimated total effort: ~14–17 hours across 5 sprints