The UI is a SvelteKit chat interface for the OneBusAway MCP server. This guide covers the dev loop, tests, and provider setup. Architecture and module layout live in ARCHITECTURE.md.
- Node.js 20+
mcprunning in HTTP mode. From../mcp/:OBA_API_KEY=test OBA_HTTP_AUTH_TOKEN=local-dev make serve-http
- An LLM provider — an Anthropic or OpenAI API key entered in the UI Settings page, or a local Ollama / llama-server instance.
npm install
MCP_URL=http://localhost:8080 MCP_AUTH_TOKEN=local-dev make devThe UI listens on http://localhost:5173. Provider settings and API key persist in localStorage; the MCP bearer token and OBA API key stay server-side and never reach the browser.
| Script | Purpose |
|---|---|
npm run dev |
Vite dev server with HMR |
npm run build |
Production build |
npm run preview |
Preview the production build |
npm run check |
svelte-check + tsc --noEmit over JSDoc-typed sources |
npm run lint |
ESLint (flat config) |
npm run format |
Prettier write |
npm run test |
Vitest unit and component tests |
npm run test:e2e |
Playwright end-to-end tests |
check, lint, test, and build all run in CI on every pull request. test:e2e runs on the same job once Playwright fixtures are in place.
The full ruleset lives in ARCHITECTURE.md. The load-bearing ones:
- Structured MCP content is the only contract. Never parse or string-match tool text summaries.
- Zod-validate every MCP response at the parse site (
src/lib/mcp/schemas.js). Unknown optional fields must be ignored, not rejected. - No file above ~250 lines. Pure functions for parsing, geometry, and state transitions.
- No narrator comments. A comment should explain a non-obvious why, never a what.
- Delete rather than deprecate. No
// removedmarkers, no legacy fallback branches.
- Unit (
src/**/*.test.js) — Vitest with jsdom. Pure functions: schemas, geometry, arrival-window logic, LLM stream adapters. - Component (
src/**/*.test.svelte.js) — Vitest +@testing-library/svelte. Render, mocked MCP fixture, assert what the user sees. - E2E (
tests/e2e/*.spec.js) — Playwright. Recorded MCP fixtures, real browser, golden-path flows. - Contract fixtures (
tests/fixtures/mcp/<tool>.json) — one per MCP tool, including a "future field" case and a "legacy" case.
The UI supports Anthropic, OpenAI, OpenRouter, Ollama, and llama-server. Configure in the Settings page. Each provider has its own adapter under src/lib/llm/providers/ with a common streamChat({messages, tools}): AsyncIterable<Event> surface. Adding a new provider means one adapter file plus a switch-case in the registry — no other file changes.
Map engines are intended to be pluggable: MapLibre, Mapbox, Leaflet, or Google Maps sit behind a shared MapEngine interface in src/lib/map/. Add a provider by creating src/lib/map/providers/<name>/index.js and registering it in registry.js; MapCard.svelte must never import a provider directly.
Before opening a PR:
npm run check && npm run lint && npm run test && npm run build— all pass.- No file introduced or grown past ~250 lines.
- New MCP fields have a fixture case and a schema entry.
- Diff contains no narrator comments and no dead code.
For behavior changes, include a screenshot or a short screen capture.
- Architecture and module map: ARCHITECTURE.md.
- MCP contract: oba-mcp/docs/production/phase-0-baseline.md and phase-6-operations.md.
- MCP tool reference:
../mcp/CLAUDE.md. - Open a GitHub issue with the
uilabel for questions not covered above.