Imported from joelvinaykumar/raga-fe (
AGENTS.md). Install upstream withnpx skills add joelvinaykumar/raga-fe. Copyright stays with the author.
AGENTS.md
RAGA — a Vite + React 18 + TypeScript RAG chat UI ("Rag As A Service"). Authenticated via Supabase, talks to a FastAPI backend, streams LLM answers over SSE.
Commands
- Dev server:
bun dev - Build (typecheck + bundle):
bun run build— runstsc && vite build; type errors block build - Lint:
bun run lint(Biome). No test runner / test script exists. - Quick typecheck:
bun x tsc --noEmit - Package manager is bun (
bun.lock). The husky pre-commit hook runsbun x lint-stagedusing bun.
Architecture
- Entry:
src/main.tsx→RouterProvider;AuthProviderwraps the app. Router instance + types registered there. - File-based routing (TanStack Router): routes live in
src/routes/**._layout.tsxis a pathless authenticated layout — itsbeforeLoadchecks the Supabase session andthrow redirectto/login; the_layoutsegment never appears in URLs. - Colocated components: route-local components go in a
-components/sibling dir (e.g.src/routes/_layout/chat/-components/). Types get re-exported from the route file and imported by siblings via..(e.g.FileAttachmentfromchat/index.tsx). - Generated code:
src/routeTree.gen.tsis auto-generated by the@tanstack/router-vite-pluginduringdev/build. Never hand-edit; after adding/renaming route files restart dev or build to regenerate. Biome ignores it. - Env:
src/lib/env.tszod-parsesimport.meta.envat module load and throws ifVITE_BASE_URL,VITE_SUPABASE_URL, orVITE_SUPABASE_ANON_KEYare missing — the app won't boot without a.env(gitignored; mirror.env). - Backend: FastAPI at
VITE_BASE_URL. Axios (src/lib/axios.ts) injectsAuthorization: Bearer <supabase access_token>in a request interceptor and toasts API errors in a response interceptor. Endpoints used:list-sessions,chat-history/{session_id},list-docs/{session_id},upload-doc/{session_id},delete-doc,delete-session/{session_id},POST /chat. - SSE streaming:
src/lib/stream.tsuses rawfetch(not axios, so no auth header/interceptor applies), POSTs to/chat, parsesdata: {content, done}lines. A newstream()call aborts the previous in-flight stream via a shared AbortController. - State: TanStack Query for server state, Zustand (
src/store/index.ts) for persisted auth flags, Supabase for auth. - Deploy: Vercel.
vercel.jsonrewrites all paths toindex.html(SPA fallback required for router history URLs — direct loads/refreshes on/chat/...404 without it).
Coding standards
- Strict TS:
tsconfig.jsonhasstrict,noUnusedLocals,noUnusedParameters— unused imports/vars failbun run build. - Formatting: Prettier (
.prettierrc, 2 spaces) runs on commit via lint-staged.biome.json's formatter settings (tabs/4) are not applied to the codebase — match the existing 2-space style; runprettier --writebefore committing. - Lint: Biome (
bun run lint).noUnusedVariablesis an error;noExplicitAnyis off (looseanyis accepted). Auto-fix withbun run lint --write. - Buttons: the shadcn
Buttondoes not settype; native default istype="submit". Non-form buttons need explicittype="button", and pass no args to handlers (onClick={() => fn()}) — routeonSubmit(message?)treats its first arg as the chat message. - UI: shadcn/ui primitives in
src/components/ui/*,cn()from@/lib/utils, Tailwind, sonner toasts, framer-motion, lucide icons,@/alias →src. - Empty states: every component that renders a collection must handle the empty case explicitly — never render a bare/interactive-but-useless control. e.g. a select/dropdown with no options must be
disabledand show a placeholder like "No options available"; lists, tables, and menus show an empty-state message instead of a blank area. Also handle loading and error states so the component never renders nothing. - Markdown:
@/components/custom/markdownis a thinReact.lazywrapper (Suspense) aroundmarkdown-impl.tsx, which holds the heavyreact-markdown+ remark/rehype stack — keep it code-split so it stays out of the initial bundle. The renderer is intentionally lean: GFM only (remark-gfm,rehype-raw,rehype-slug). No KaTeX/math, syntax-highlighter, or emoji deps — don't re-add them without cause. Onlyframer-motionis used for animation (themotionpackage was removed).
Known issues
- Initial chat jitter (new chat): the first message is typed on
/chatand passed to/chat/{sessionId}vialocation.state.query, whichsrc/routes/_layout/chat/$sessionId/index.tsxreplays in a mount effect. Becausemain.tsxwraps in<StrictMode>, that effect double-fires in dev. The file guards this with aninitialQuerySubmittedRefref plus acancelledflag in the effect cleanup, andonSubmitearly-returnsif (isStreaming). Do not remove these guards: without themonSubmitruns twice, producing duplicate prompt/reply pairs and one reply stuckloading: trueforever (the secondstream()aborts the first via the shared AbortController). The Enter key in the input still firesonSubmiteven though the send button is disabled while loading. - Chat streaming animation bug:
message-bubble.tsxre-mounts the markdown node on every chunk viakey={msg.loading ? \s-${msg.content.length}` : "static"}, re-triggering the.markdown-streamingfade-inanimation (defined insrc/index.css). Combined with the per-chunkflushSyncin$sessionId/index.tsx`, every token causes a full synchronous re-render plus a CSS animation restart → visible flicker/jitter while streaming. Fix direction: animate without remounting (CSS transition on opacity instead of a keyed remount) and/or batch chunk updates. - Account page renders nothing:
/account(src/routes/_layout/account/index.tsx) mounts but shows an empty page. Investigate the data source it reads (user/profile query) — the layout renders but its content columns come up blank, likely an unresolved/empty query or a guard that returns null before the details render. - MCP connection — authentication still needs work: the MCP connection flow is buggy and not yet production-ready. Authentication for MCP clients (API-key provisioning/validation and scoped access) is incomplete and needs further work before it can be relied on. Fix direction: harden the MCP auth path — robust API-key issuance/rotation, consistent header validation on the server, and clear error handling on the client.
Resolved issues
top_klimit inconsistency (fixed): previously three different caps were enforced for the same value (create form.max(10), config slidermax="20", backend_resolve_top_kclamp tomin(x, 30)). Now aligned to a single source of truth across the create form (src/routes/_layout/knowledge-base/new.tsx), the workspace config slider (src/routes/_layout/knowledge-base/$kbId/-components/config-sidebar.tsx), and the backend_resolve_top_k(raga-be/main.py).