DASHBOARD_REVIEW.md — comprehensive code review of all 6 dashboard files (1,395 lines). Organized into 7 sections: - 10 bugs (B1–B10): hardcoded header, blocking pull, escape during stream, auto-refresh during streaming, no abort controller, vm_stat page size, etc. - 5 code quality issues (CQ1–CQ5): monolithic component, inline styles, duplicated constants, no error boundary, no loading skeleton - 16 feature ideas (F1–F16): pull progress, chat mode, prompt history, token/s metrics, model search, whisper test, extraction integration, etc. - 5 performance items (P1–P5): request deduplication, cache TTL, du latency - 3 security notes (S1–S3): input validation, shell injection pattern, CORS - Priority matrix and 5-sprint implementation roadmap
14 KiB
Mission Control Dashboard — Bug & Improvement Review
Systematic review of
__LOCAL_LLMs/dashboard/— Feb 19, 2026
1. Bugs
B1. Hardcoded "Apple M4 Pro · 48 GB" in header
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 entire UI with stream: false
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.
B3. Generate (non-streaming) endpoint still exists but is unused
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.
B4. Escape key closes prompt modal even while streaming
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.
B5. Auto-refresh (15s) during active streaming
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.
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".
2. Code Quality Issues
CQ1. 1079-line single component (page.tsx)
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:
components/StatusDot.tsxcomponents/ProgressBar.tsxcomponents/ToastContainer.tsxcomponents/PromptModal.tsxcomponents/OllamaModelsPanel.tsxcomponents/SystemPanel.tsxcomponents/WhisperPanel.tsxcomponents/BrewPanel.tsxlib/types.ts(interfaces)lib/format.ts(formatBytes, formatUptime)
CQ2. Inline styles everywhere instead of CSS classes
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.
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.
3. Feature Improvements
F1. Pull progress streaming
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
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.
F3. Prompt history
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.
F4. Chat mode (multi-turn)
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.
F5. Model comparison
Priority: Low Description: Side-by-side prompt comparison: send the same prompt to 2 models simultaneously and show both responses for latency/quality comparison.
F6. Token/s metrics display
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.
F7. System resource charts (time series)
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.
F8. Ollama server logs viewer
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.
4. Performance & Reliability
P1. No request deduplication
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 invalidates
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.
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.
5. Security & Hardening
S1. No input validation on model names
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 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.
6. Priority Matrix
| 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 |
7. Recommended Implementation Order
Sprint 1 — Critical fixes (est. 1–2 hrs)
- B4 — Guard Escape key during streaming
- B5 — Pause auto-refresh during prompt/pull
- B9 — Add AbortController to streaming fetch
- B1 — Dynamic chip/RAM in header
- P4 — Add timeout to Ollama fetch calls
Sprint 2 — Pull progress + metrics (est. 2–3 hrs)
- B2/F1 — Streaming pull with progress bar
- F6 — Display tokens/second after generation
- B10 — Fix brew "Loading..." vs "empty" state
Sprint 3 — Component refactor (est. 2–3 hrs)
- CQ1 — Extract components into separate files
- CQ4 — Add Error Boundary
- CQ2 — Consolidate inline styles
- CQ3 — Shared Ollama URL constant
Sprint 4 — UX enhancements (est. 3–4 hrs)
- F3 — Prompt history (localStorage)
- F4 — Chat mode (multi-turn)
- F12 — Whisper transcription test
- F2 — Model search/filter
Sprint 5 — Integration & polish (est. 2–3 hrs)
- F15 — Extraction service panel
- F9 — Modelfile viewer
- F7 — Resource trend sparklines
- F11 — Keyboard shortcuts panel
Generated by systematic code review of all 6 source files (1,395 lines total).