The SQL workspace minilake serves at /ui. Next.js 16 (App Router, static export),
React 19, Tailwind v4, shadcn/ui on the base-nova style, Monaco for the editor.
User-facing documentation, including the Athena mapping and the dev-server setup,
lives in ../docs/ui.md. This file covers the code.
pnpm install
pnpm dev # dev server on :3000 (needs the API — see ../docs/ui.md)
pnpm build # static export into ./out
pnpm lint
pnpm typecheck
pnpm test # vitest, over the pure helpers in src/libpnpm build and pnpm dev both run scripts/copy-monaco.mjs first.
src/
├── app/ one route per workspace view; every page is a client component
├── components/
│ ├── layout/ sidebar, header, warehouse picker, theme toggle
│ ├── editor/ tabs, Monaco wrapper, toolbar, SQL completion provider
│ ├── results/ grid, chart, stats, the tabbed results panel
│ ├── catalog/ Unity Catalog tree and the DDL dialog
│ └── ui/ shadcn components (generated — regenerate, don't hand-edit)
├── hooks/ react-query wrappers and the execution hook
├── lib/
│ ├── api/ one module per API group, over a shared fetch client
│ ├── sql-identifier.ts quoting, table refs, auto-limit
│ ├── csv.ts CSV/JSON export
│ └── ddl.ts CREATE TABLE generation
└── stores/ zustand: editor tabs and workspace preferences
- Server state is server state. Anything the API owns goes through
@tanstack/react-query; only per-browser preferences and open editor tabs live in zustand'spersist. Query history and saved queries are deliberately not local — they are real minilake APIs. - All API calls go through
lib/api/client.ts, which resolves the base URL and turns{error_code, message}bodies intoMinilakeApiError. Never callfetchfrom a component. - No CDN dependencies. The container image is offline-capable; anything the
page loads must be served from this origin. That is why Monaco is copied into
public/at build time. - Themes are three-state.
next-themeswithattribute="class". ReadresolvedTheme, nevertheme, when the value drives rendering —themeis the literal string"system"until the user picks one. - Base UI, not Radix. The
base-novashadcn style composes withrender={...}where Radix would useasChild. pnpm dlx shadcn@latest add <component>for new primitives, so they stay regenerable.
next.config.tssetsoutput: "export",basePath: "/ui"andtrailingSlash: true. The last one matters: it emitsout/<route>/index.html, which is what FastAPI'sStaticFiles(html=True)serves for a directory.react-resizable-panelsv4 takesorientation(notdirection), and a bare number size means pixels — sizes here are percentage strings.AGENTS.mdin this directory is generated bynext dev; commit it with your changes rather than deleting it.