The dashboard package (packages/dashboard) is a Next.js 16 + NextAuth v5 app that serves as the management UI for the Raven proxy. It currently has zero automated tests — no test runner, no test dependencies, no test scripts in package.json.
Browser ──SSE/fetch──► Dashboard (Next.js :7032) ──fetch/WS──► Proxy (Hono :7033)
│
├── proxy.ts Auth enforcement (replaces middleware.ts)
├── auth.ts NextAuth v5 config (Google OAuth)
├── lib/proxy.ts proxyFetch / safeFetch helpers
├── app/api/* BFF route handlers (9 routes)
├── hooks/ useLogStream (SSE client)
└── components/ RequestTable, ConnectContent, etc.
| File | Lines | Category | Testability |
|---|---|---|---|
src/proxy.ts |
42 | Auth middleware | Needs NextAuth mock |
src/auth.ts |
101 | Auth config | Module-level side effects — mock next-auth + vi.stubEnv |
src/lib/proxy.ts |
80 | Fetch helpers | Pure functions, easy |
src/lib/types.ts |
169 | Types only | No runtime logic |
src/app/api/connection-info/route.ts |
17 | BFF route | Easy — mock proxyFetch |
src/app/api/copilot/[...path]/route.ts |
27 | BFF route | Easy |
src/app/api/keys/route.ts |
32 | BFF route | Easy |
src/app/api/keys/[id]/route.ts |
22 | BFF route | Easy |
src/app/api/keys/[id]/revoke/route.ts |
22 | BFF route | Easy |
src/app/api/logs/stream/route.ts |
112 | SSE bridge | Needs WS mock |
src/app/api/requests/route.ts |
21 | BFF route | Easy |
src/app/api/stats/[...path]/route.ts |
27 | BFF route | Easy |
src/hooks/use-log-stream.ts |
190 | React hook | Needs EventSource mock |
src/components/requests/request-table.tsx |
221 | React component | Needs router mock |
src/app/connect/connect-content.tsx |
383 | React component | Needs fetch mock |
src/app/copilot/account/account-content.tsx |
387 | React component | Needs fetch mock |
src/app/copilot/models/models-content.tsx |
191 | React component | Needs fetch mock |
connect-content.tsxL199-206:handleAction(revoke/delete key) ignores fetch errors — no user feedback on failureaccount-content.tsxL205-213:handleRefreshignores fetch errors — silent failuremodels-content.tsxL90-98:handleRefreshignores fetch errors — silent failuremodels-content.tsxL26:navigator.clipboard.writeTexthas no catch — unhandled rejection in HTTP context
90%+ line coverage on all non-UI-presentational files (lib/proxy.ts, all BFF routes, use-log-stream.ts). Component tests focus on interaction logic (error handling, pagination, API calls), not visual rendering.
Phase 1 — Infrastructure: Set up Vitest + React Testing Library. No tests yet, just validate the toolchain.
Phase 2 — Pure logic: lib/proxy.ts (ProxyError, proxyFetch, safeFetch). Zero external dependencies.
Phase 3 — BFF routes: All 8 API route handlers. Mock proxyFetch via vi.mock.
Phase 4 — SSE bridge: api/logs/stream/route.ts. Mock WebSocket.
Phase 5 — React hooks: use-log-stream.ts. Mock EventSource.
Phase 6 — Component interactions: Error handling bugs in connect-content.tsx, account-content.tsx, models-content.tsx. Pagination logic in request-table.tsx.
Phase 7 — Auth: proxy.ts auth enforcement + auth.ts NextAuth config. Mock NextAuth internals.
bun add -d vitest @testing-library/react @testing-library/jest-dom @vitejs/plugin-react jsdomNew file: vitest.config.ts
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import { resolve } from "path";
export default defineConfig({
plugins: [react()],
test: {
environment: "node", // server-side by default (BFF routes, SSE bridge, lib)
globals: true,
setupFiles: ["./test/setup.ts"],
include: ["test/**/*.test.{ts,tsx}"],
},
resolve: {
alias: {
"@": resolve(__dirname, "src"),
},
},
});Client-side test files (hooks, components) opt-in to jsdom via the magic comment at the top of each file:
// @vitest-environment jsdomThis keeps BFF route and SSE bridge tests running in a Node-like environment with correct Response, ReadableStream, and WebSocket semantics, while component/hook tests get the DOM APIs they need.
New file: test/setup.ts
import "@testing-library/jest-dom/vitest";Note:
setup.tsis only loaded for jsdom files (RTL matchers require DOM). For node-environment tests it's harmless —@testing-library/jest-domadds matchers globally but they simply go unused.
Modify: package.json — add "test": "vitest run", "test:watch": "vitest"
New file: test/smoke.test.ts
import { describe, it, expect } from "vitest";
describe("smoke", () => {
it("vitest runs", () => { expect(1 + 1).toBe(2); });
});Run bun run test → confirm green.
New file: test/lib/proxy.test.ts
Source: src/lib/proxy.ts (80 lines)
- constructor sets name to "ProxyError"
- stores statusCode
- statusCode is undefined when not provided
Requires mocking globalThis.fetch. Module reads PROXY_URL and API_KEY from env at import time — tests that need different env values must call vi.resetModules() before vi.stubEnv() + dynamic import() to force re-evaluation. Without resetModules, Vitest serves the cached module and the new env values are ignored.
// Pattern for env-dependent tests
beforeEach(() => {
vi.resetModules();
});
afterEach(() => {
vi.unstubAllEnvs();
});
it("includes Authorization header when API_KEY is set", async () => {
vi.stubEnv("RAVEN_API_KEY", "test-key");
const { proxyFetch } = await import("@/lib/proxy");
// ...
});
it("omits Authorization header when API_KEY is empty", async () => {
vi.stubEnv("RAVEN_API_KEY", "");
const { proxyFetch } = await import("@/lib/proxy");
// ...
});describe("proxyFetch")
- builds correct URL from PROXY_URL + path
- includes Content-Type: application/json header
- includes Authorization header when API_KEY is set
- omits Authorization header when API_KEY is empty
- merges caller-provided headers
- sets cache: "no-store"
- returns parsed JSON on 200 response
- throws ProxyError with status code on non-ok response
- throws ProxyError with statusText in message
- forwards RequestInit options (method, body)
describe("safeFetch")
- returns { ok: true, data } on success
- returns { ok: false, error: message } on ProxyError
- returns { ok: false, error: message } on generic Error
- returns { ok: false, error: "Unknown error..." } on non-Error throw
All 8 BFF routes share the same pattern:
try {
const data = await proxyFetch<T>(path, init);
return NextResponse.json(data, { status });
} catch (err) {
const status = err instanceof ProxyError ? (err.statusCode ?? 502) : 502;
const message = err instanceof Error ? err.message : "Failed to reach proxy";
return NextResponse.json({ error: message }, { status });
}Mock strategy: vi.mock("@/lib/proxy") to control proxyFetch return values.
New file: test/api/simple-routes.test.ts
Cover 4 routes with identical structure (success + ProxyError + generic Error):
| Route | Handler | Proxy path |
|---|---|---|
api/connection-info/route.ts |
GET |
/api/connection-info |
api/requests/route.ts |
GET |
/api/requests + query params |
api/copilot/[...path]/route.ts |
GET |
/api/copilot/{path} + query params |
api/stats/[...path]/route.ts |
GET |
/api/stats/{path} + query params |
Tests per route:
- success → returns JSON with 200
- ProxyError with statusCode → returns that status
- ProxyError without statusCode → returns 502
- generic Error → returns 502 with error message
Catch-all routes (copilot, stats) additionally test:
- joins path segments correctly (e.g., ["user"] → "user", ["models", "list"] → "models/list")
- forwards query parameters
Requests route additionally tests:
- forwards query params to proxy (cursor, limit, sort, order)
- empty query params → path without "?"
New file: test/api/keys-routes.test.ts
Cover 3 route files:
api/keys/route.ts — GET + POST:
describe("GET /api/keys")
- success → returns key list as JSON
- ProxyError → returns error status
describe("POST /api/keys")
- success → returns 201 with created key data
- forwards request body to proxyFetch
- ProxyError → returns error status
api/keys/[id]/route.ts — DELETE:
describe("DELETE /api/keys/:id")
- success → returns JSON
- extracts id from params promise
- ProxyError → returns error status
api/keys/[id]/revoke/route.ts — POST:
describe("POST /api/keys/:id/revoke")
- success → returns JSON
- extracts id from params promise
- ProxyError → returns error status
New file: test/api/logs-stream.test.ts
Source: src/app/api/logs/stream/route.ts (112 lines)
This is the most complex route — it creates a ReadableStream that bridges upstream WebSocket messages to SSE events. Requires a mock WebSocket.
Create a controllable MockWebSocket class:
class MockWebSocket {
onopen: (() => void) | null = null;
onmessage: ((e: { data: string }) => void) | null = null;
onerror: (() => void) | null = null;
onclose: (() => void) | null = null;
readyState = 0; // CONNECTING
close() { this.readyState = 3; }
}Stub globalThis.WebSocket with a factory that returns the mock instance.
describe("GET /api/logs/stream")
describe("connection setup")
- builds WebSocket URL with ws:// protocol from http:// PROXY_URL
- builds WebSocket URL with wss:// protocol from https:// PROXY_URL
- includes API_KEY as token query param
- includes level query param (default: info)
- includes requestId query param when provided
- returns response with Content-Type: text/event-stream
describe("SSE events")
- onopen → emits "connected" SSE event
- onmessage → emits "log" SSE event with message data
- onerror → emits "error" SSE event
- onclose → emits "disconnected" SSE event + closes stream
describe("error handling")
- WebSocket constructor throws → emits error event + closes stream
- controller.enqueue throws in onerror → silently caught (L71-73)
- controller.enqueue throws in onclose → silently caught (L84-86)
describe("cleanup")
- stream cancel → closes upstream WebSocket
- stream cancel when WS already closed → no error
New file: test/hooks/use-log-stream.test.ts
Source: src/hooks/use-log-stream.ts (190 lines)
Create a controllable MockEventSource:
class MockEventSource {
listeners: Record<string, ((e: MessageEvent) => void)[]> = {};
url: string;
readyState = 0;
close = vi.fn();
addEventListener(type: string, handler: (e: MessageEvent) => void) {
(this.listeners[type] ??= []).push(handler);
}
// Test helpers
emit(type: string, data?: string) {
for (const h of this.listeners[type] ?? []) {
h(new MessageEvent(type, { data }));
}
}
}Use renderHook from @testing-library/react.
describe("useLogStream")
describe("connection")
- creates EventSource with correct URL params
- includes level param
- includes requestId param when provided
- enabled=false → no EventSource created
describe("events")
- "connected" event → sets connected=true
- "connected" event → resets reconnect counter
- "log" event → appends to events array
- "log" event with malformed JSON → ignored (no crash)
- events capped at maxEvents (default 500)
- events over maxEvents → keeps most recent
describe("pause/resume")
- setPaused(true) → buffers incoming events
- setPaused(false) → flushes buffer into events
- buffer flush respects maxEvents cap
describe("reconnect")
- "disconnected" event → closes EventSource + schedules reconnect
- "error" event → closes EventSource + schedules reconnect
- reconnect uses exponential backoff (1s, 2s, 4s, ..., 30s max)
- duplicate reconnect guard (both "disconnected" and "error" → only one reconnect)
- successful reconnect resets attempt counter
describe("cleanup")
- unmount → closes EventSource
- unmount → clears reconnect timer
- level change → reconnects with new URL
describe("clear")
- clear() → empties events array
- clear() → empties pause buffer
New file: test/components/connect-content.test.ts
Source: src/app/connect/connect-content.tsx (383 lines)
Focus on ApiKeysSection and CreateKeyDialog interaction logic. Mock fetch and useRouter.
describe("ApiKeysSection")
describe("handleAction — revoke")
- calls POST /api/keys/{id}/revoke
- calls router.refresh() on success
- fetch failure → shows error feedback to user (fix Bug 1, then assert fixed behavior)
- non-2xx response → shows error feedback to user
describe("handleAction — delete")
- calls DELETE /api/keys/{id}
- calls router.refresh() on success
- fetch failure → shows error feedback to user (fix Bug 1, then assert fixed behavior)
- non-2xx response → shows error feedback to user
describe("CreateKeyDialog")
- empty name → create button disabled
- submit → calls POST /api/keys with name
- success → shows created key for copy
- res.ok=false → shows actual error message from response (fix Bug 2, then assert fixed behavior)
- fetch throws → shows "Failed to create key"
Test-first bug fix workflow: Write the test asserting the correct behavior first (it will fail against the current code), then fix the production code in the same commit so the test passes. Never commit a green test that asserts broken behavior.
New file: test/components/copilot-content.test.ts
describe("AccountContent.handleRefresh")
- calls GET /api/copilot/user?refresh=true
- calls router.refresh() on success
- fetch failure → shows error feedback to user (fix Bug 3, then assert fixed behavior)
describe("CopilotModelsContent.handleRefresh")
- calls GET /api/copilot/models?refresh=true
- calls router.refresh() on success
- fetch failure → shows error feedback to user (fix Bug 4, then assert fixed behavior)
describe("CopilotModelsContent.CopyButton")
- clipboard.writeText throws → fails gracefully, no unhandled rejection (fix Bug 5, then assert fixed behavior)
New file: test/components/request-table.test.ts
Source: src/components/requests/request-table.tsx (221 lines)
Mock useRouter and useSearchParams.
describe("formatTimestamp")
- formats epoch ms to readable string
describe("formatLatency")
- ms < 1000 → "123ms"
- ms >= 1000 → "1.2s"
describe("formatTokens")
- formats input/output with locale separators
- null input/output → "0 / 0"
describe("toggleSort")
- click same column → toggles order (desc↔asc)
- click different column → sets new column + desc
- clears cursor, offset, prevCursors
describe("pagination — cursor mode (sort=timestamp)")
- next page → pushes current cursor to prevCursors, sets nextCursor
- prev page → pops from prevCursors stack
- prev page on first page → deletes cursor param
- canGoPrev = true when cursor param exists
describe("pagination — offset mode (sort=latency_ms)")
- next page → offset += limit
- prev page → offset -= limit, min 0
- offset=0 → deletes offset param
- canGoPrev = true when offset > 0
describe("empty state")
- data=[] → shows "No requests found"
New file: test/proxy.test.ts
Source: src/proxy.ts (42 lines)
proxy.ts exports default as auth(handler) — the handler function receives req with an auth property. The easiest approach: extract the handler callback and test it directly by constructing mock request objects with/without req.auth.
Alternatively, mock @/auth module to return a pass-through auth wrapper, then import and call the default export.
describe("proxy.ts auth enforcement")
describe("/api/auth/* routes")
- passes through regardless of auth state
describe("/login")
- unauthenticated → passes through
- authenticated → redirects to /
describe("/api/* routes (non-auth)")
- unauthenticated → returns 401 JSON { error: "Unauthorized" }
describe("page routes")
- unauthenticated → redirects to /login
- authenticated → passes through
New file: test/auth.test.ts
Source: src/auth.ts (101 lines)
This is the most security-sensitive file in the dashboard. It controls who can sign in and how session cookies are configured. The signIn callback (L92-98) is the core allowlist gate.
auth.ts has module-level side effects: it reads ALLOWED_EMAILS, NEXTAUTH_URL, USE_SECURE_COOKIES from process.env at import time, calls console.warn if allowlist is empty, and invokes NextAuth() which returns { handlers, signIn, signOut, auth }.
Approach: Use vi.resetModules() + vi.stubEnv() + dynamic import() inside each test to force re-evaluation of the module with different env values. resetModules is critical — without it, Vitest serves the cached module and env changes are ignored, causing cross-test pollution. Mock next-auth to capture the config object passed to NextAuth() — this gives direct access to the callbacks.signIn function and cookie config without running a real OAuth flow.
vi.mock("next-auth", () => ({
default: (config: unknown) => {
// Capture config for inspection, return stub exports
lastConfig = config;
return { handlers: {}, signIn: vi.fn(), signOut: vi.fn(), auth: vi.fn() };
},
}));
vi.mock("next-auth/providers/google", () => ({
default: (opts: unknown) => ({ id: "google", ...opts }),
}));
// Each test re-imports auth.ts with fresh env
beforeEach(() => {
vi.resetModules();
});
afterEach(() => {
vi.unstubAllEnvs();
});
it("blocks email not in allowlist", async () => {
vi.stubEnv("ALLOWED_EMAILS", "alice@example.com");
await import("@/auth"); // re-evaluates with new ALLOWED_EMAILS
const result = await lastConfig.callbacks.signIn({ user: { email: "eve@evil.com" } });
expect(result).toBe(false);
});describe("auth.ts signIn callback")
describe("ALLOWED_EMAILS set")
- email in allowlist → returns true
- email NOT in allowlist → returns false
- email comparison is case-insensitive ("User@GMAIL.com" matches "user@gmail.com")
- user with no email → returns false
describe("ALLOWED_EMAILS empty or unset")
- any email → returns true (open access)
- logs console.warn about unrestricted access
describe("auth.ts cookie configuration")
describe("useSecureCookies = true")
- NODE_ENV=production → secure cookies with __Secure- / __Host- prefixes
- NEXTAUTH_URL=https://... → secure cookies
- USE_SECURE_COOKIES=true → secure cookies
- all cookie options have httpOnly: true, sameSite: "lax"
describe("useSecureCookies = false")
- NODE_ENV=development + http URL + no USE_SECURE_COOKIES → non-secure cookie names
- cookie secure option is false
describe("auth.ts provider config")
- passes GOOGLE_CLIENT_ID to Google provider
- passes GOOGLE_CLIENT_SECRET to Google provider
- custom pages: signIn → /login, error → /login
- trustHost is true
These bugs should be fixed as part of the test commits that expose them.
Current code:
const handleAction = useCallback(async (id: string, action: "revoke" | "delete") => {
if (action === "revoke") {
await fetch(`/api/keys/${id}/revoke`, { method: "POST" });
} else {
await fetch(`/api/keys/${id}`, { method: "DELETE" });
}
router.refresh();
}, [router]);Problem: If fetch fails (network error) or returns non-2xx, user gets no feedback. The page refreshes and shows unchanged state, making it appear like the action worked when it didn't.
Fix: Add try/catch, check res.ok, show toast or inline error on failure.
Current code:
setError(data.error?.message ?? "Failed to create key");Problem: The BFF route at api/keys/route.ts L29 returns { error: message } (string), not { error: { message: string } } (object). So data.error?.message is always undefined, and the user always sees the generic "Failed to create key" instead of the actual proxy error message.
Fix: setError(typeof data.error === "string" ? data.error : data.error?.message ?? "Failed to create key")
Same pattern as Bug 1. fetch is not checked for errors.
Fix: Add try/catch with error feedback (toast or inline message).
Same as Bug 3.
Current code:
const handleCopy = useCallback(async () => {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
}, [text]);Problem: navigator.clipboard.writeText throws in HTTP (non-HTTPS) contexts or when Clipboard API is unavailable. The promise rejection is unhandled.
Fix: Wrap in try/catch. Consider fallback to document.execCommand("copy") or show error toast.
tsconfig.json includes .next/types/**/*.ts which contains generated route type definitions. These files only exist after next build or next dev and are not committed to git. Running bun run typecheck without a prior build may fail or produce inconsistent results.
Decision: Change bun run typecheck to run next build first, then tsc --noEmit:
"typecheck": "next build && tsc --noEmit"This is the only approach that guarantees .next/types are generated and consistent. The build adds ~10-15s but ensures route type params (params: Promise<{ id: string }>) are correctly validated. Since typecheck runs infrequently (pre-commit or CI, not on every save), the cost is acceptable.
- Next.js ecosystem alignment —
@testing-library/reactandjsdomintegrate cleanly vi.mocksupports ESM module mocking needed for@/lib/proxyand@/authvi.stubEnvfor controlling env vars that are read at module load time- Path alias (
@/*) support via Vite'sresolve.alias
All dashboard API routes are thin BFF wrappers around proxyFetch. Mocking at the module boundary (vi.mock("@/lib/proxy")) is simpler and more precise than intercepting HTTP at the network layer. Component tests that call fetch directly (handleAction, handleRefresh) use vi.spyOn(globalThis, "fetch") — also module-level, no network interception needed. MSW adds weight without value here.
- Default:
node— all BFF route tests, SSE bridge,lib/proxy.ts,auth.ts,proxy.ts - Opt-in
jsdom: hooks and component tests, via// @vitest-environment jsdomfile comment
This avoids forcing browser globals onto server-side code. Node environment provides real Response, ReadableStream, TextEncoder — higher fidelity for route handler tests.
| Target | Pattern | Reason |
|---|---|---|
@/lib/proxy |
vi.mock("@/lib/proxy") |
Control proxyFetch returns per test |
globalThis.fetch |
vi.spyOn(globalThis, "fetch") |
Component-level fetch calls (handleAction, handleRefresh) |
next/navigation |
vi.mock("next/navigation") |
Mock useRouter, useSearchParams |
@/auth |
vi.mock("@/auth") |
Mock auth() wrapper for proxy.ts tests |
next-auth |
vi.mock("next-auth") |
Capture NextAuth config for auth.ts tests |
next-auth/providers/google |
vi.mock(...) |
Stub Google provider for auth.ts tests |
WebSocket |
vi.stubGlobal("WebSocket", MockWebSocket) |
SSE bridge test |
EventSource |
vi.stubGlobal("EventSource", MockEventSource) |
useLogStream test |
navigator.clipboard |
vi.stubGlobal("navigator", ...) |
CopyButton tests |
process.env.* |
vi.resetModules() + vi.stubEnv(key, value) + dynamic import() / vi.unstubAllEnvs() in afterEach |
Force re-evaluation of modules that read env at import time (auth.ts, lib/proxy.ts). resetModules clears module cache; unstubAllEnvs restores process.env so stubs don't leak across tests |
packages/dashboard/
├── test/
│ ├── setup.ts # Testing library matchers (loaded by all envs)
│ ├── smoke.test.ts # Toolchain verification
│ ├── lib/
│ │ └── proxy.test.ts # proxyFetch, safeFetch, ProxyError [node]
│ ├── api/
│ │ ├── simple-routes.test.ts # connection-info, requests, copilot, stats [node]
│ │ ├── keys-routes.test.ts # keys CRUD routes [node]
│ │ └── logs-stream.test.ts # SSE bridge [node]
│ ├── hooks/
│ │ └── use-log-stream.test.ts # SSE hook [jsdom]
│ ├── components/
│ │ ├── connect-content.test.tsx # API key management interactions [jsdom]
│ │ ├── copilot-content.test.tsx # Account + models refresh [jsdom]
│ │ └── request-table.test.tsx # Pagination + sorting [jsdom]
│ ├── auth.test.ts # NextAuth config + signIn callback [node]
│ └── proxy.test.ts # Auth enforcement [node]
└── vitest.config.ts
bun run test # all dashboard tests
bun run test -- --coverage # with coverage report
bun run typecheck # next build + tsc --noEmitCheck:
- All test files pass
lib/proxy.ts≥ 95% line coverage- All BFF routes ≥ 90% line coverage
use-log-stream.ts≥ 85% line coverageauth.tssignIn callback — all branches covered (allowlist match, no match, empty allowlist, no email)auth.tscookie config — both secure and non-secure paths covered- Bug fixes verified by tests that previously demonstrated the broken behavior
bun run typecheckpasses cleanly (requires priornext build— handled by the updated script)
All phases implemented. 145 tests across 11 files, all passing.
| Phase | Commit(s) | Tests | Status |
|---|---|---|---|
| 1a: Install dependencies | vitest, @testing-library/react, @testing-library/jest-dom, @vitejs/plugin-react, jsdom, @testing-library/user-event |
— | ✅ Done |
| 1b: Vitest config | vitest.config.ts, test/setup.ts |
— | ✅ Done |
| 1c: Smoke test | test/smoke.test.ts |
1 | ✅ Done |
2: lib/proxy.ts |
test/lib/proxy.test.ts |
20 | ✅ Done |
| 3a: Simple routes | test/api/simple-routes.test.ts |
16 | ✅ Done |
| 3b: Keys routes | test/api/keys-routes.test.ts |
12 | ✅ Done |
| 4: SSE bridge | test/api/logs-stream.test.ts |
14 | ✅ Done |
| 5: useLogStream hook | test/hooks/use-log-stream.test.ts |
22 | ✅ Done |
| 6a: connect-content + Bug 1,2 | test/components/connect-content.test.tsx |
10 | ✅ Done |
| 6b: copilot-content + Bug 3,4,5 | test/components/copilot-content.test.tsx |
7 | ✅ Done |
| 6c: request-table | test/components/request-table.test.tsx |
17 | ✅ Done |
| 7a: proxy.ts auth | test/proxy.test.ts |
9 | ✅ Done |
| 7b: auth.ts config | test/auth.test.ts |
17 | ✅ Done |
| Bug | File | Fix |
|---|---|---|
| 1: handleAction ignores errors | connect-content.tsx |
Added try/catch + error state display |
| 2: Error message format mismatch | connect-content.tsx |
Handle both { error: "string" } and { error: { message: "string" } } |
| 3: handleRefresh ignores errors | account-content.tsx |
Added catch block + error state display |
| 4: handleRefresh ignores errors | models-content.tsx |
Added catch block + error state display |
| 5: clipboard.writeText unhandled | copy-button.tsx, models-content.tsx |
Wrapped in try/catch |