Layers

KB/server.ts (Bun.serve, port 3001)
   ├── /api/chat       → Anthropic SDK agentic loop (search_web tool)
   ├── /api/search-web → Serper.dev passthrough
   └── static (default) → KB/public/index.html
                              └── /src/frontend.tsx (bundled by Bun on demand)
                                     └── React App (KB/src/frontend.tsx:16)
                                            ├── <Sidebar>          (article list + search)
                                            ├── <LegalDisclaimer>  (banner)
                                            ├── <ArticleViewer>    (selected article)
                                            ├── <ChatPanel>        (Ask Claude)
                                            └── <SettingsModal>    (API keys)

Server

Single file, no framework. KB/server.ts:306 calls Bun.serve({ port, fetch }). The fetch handler dispatches by (method, pathname).

CORS is wide-open: Access-Control-Allow-Origin: * (KB/server.ts:7–11). Suitable for local dev only.

The chat route returns a text/event-stream Response whose body is a ReadableStream. The stream coroutine runs the Anthropic agentic loop until stop_reason !== "tool_use", then chunks the final text into 500-char SSE data: slices to give a streaming feel (KB/server.ts:201–214). This is not native Anthropic streaming — stream: false is set on the SDK call (KB/server.ts:147).

Frontend

Entry: KB/src/frontend.tsx. Mounts <App> into #root under React.StrictMode. Single screen, three columns:

  1. Sidebar (fixed 256px) — article tree grouped by category, fuzzy search input, settings button, theme toggle.
  2. Main column — <LegalDisclaimer> banner above <ArticleViewer>.
  3. Floating chat panel — slides in from the right when “Ask Claude” is clicked.

State lives in App (useState for selectedArticle, isChatOpen, isSettingsOpen). Settings come from useSettings (KB/src/hooks/useSettings.ts).

Article registry

KB/src/kb/index.ts exports allArticles: KBArticle[] — 10 inline articles. The frontend imports this once via import { allArticles } from "./kb/index" (KB/src/frontend.tsx:7) and passes it to <Sidebar>.

Warning

Per-topic files under KB/src/kb/gdpr/*.ts, KB/src/kb/swedish-law/*.ts, KB/src/kb/b2b-enrichment/*.ts, and KB/src/kb/enforcement/*.ts exist but are not imported anywhere. They also have a broken import path (from "../../types" instead of from "../../lib/types" — only the gdpr/ and swedish-law/ files use the correct path; the rest are broken). They appear to be a planned-but-unwired expansion. See KB Content Index for the gap.

Hooks

  • useSettings — keys + model, persisted to localStorage (kb_anthropic_key, kb_serper_key, kb_model).
  • useTheme — dark/light, persisted to localStorage (kb_theme), respects prefers-color-scheme on first load.
  • useSearch — Fuse.js index over title/summary/tags, threshold 0.35.
  • useChat — POSTs to /api/chat, streams SSE response, accumulates assistant message.

See also

KB Overview, KB Chat Flow, KB Search, KB Settings, KB UI Components.

See also