# Frontend Architecture This document describes the frontend architecture of the Visual Notebook, covering the React + TypeScript + Vite setup, Tailwind CSS configuration, Zustand state management, the API client layer, component hierarchy, the cell system, chart rendering, and map visualization. ## React + TypeScript + Vite Setup The frontend is built as a single-page application using React 18 with TypeScript and Vite 5. The project lives under the `frontend/` directory. ### Vite Configuration The Vite configuration in `vite.config.ts` defines two key features: **Development proxy**: During local development, API requests to `/api` and `/health` are proxied to the backend at `http://localhost:8000`, avoiding CORS issues: ```typescript server: { proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true }, '/health': { target: 'http://localhost:8000', changeOrigin: true }, } } ``` **Production build optimization**: The Rollup output is manually chunked to separate large dependencies: ```typescript manualChunks: { echarts: ['echarts', 'echarts-for-react'], react: ['react', 'react-dom'], vendor: ['axios', 'zustand'], } ``` This separation ensures that ECharts (the largest dependency) and React are loaded as separate cacheable chunks, improving initial page load performance and enabling better browser caching on subsequent visits. ### TypeScript Configuration TypeScript is configured with strict mode. The `api/types.ts` file mirrors the Pydantic response schemas from the backend, ensuring compile-time type safety for API responses. This contract between frontend and backend types catches breaking changes at build time. ## Tailwind CSS Configuration The project uses Tailwind CSS 3 with a custom brand color theme defined in `tailwind.config.js`: ```javascript colors: { brand: { 50: '#eef2ff', 100: '#e0e7ff', 200: '#c7d2fe', 300: '#a5b4fc', 400: '#818cf8', 500: '#6366f1', 600: '#4f46e5', 700: '#4338ca', 800: '#3730a3', 900: '#312e81', } } ``` This indigo-based palette is used throughout the application for primary buttons, active states, focus rings, and brand accents. The 10-shade scale provides sufficient contrast for accessibility while maintaining visual consistency. PostCSS with Autoprefixer processes the CSS for cross-browser compatibility. The global stylesheet in `src/styles/index.css` imports the Tailwind base, components, and utilities layers. ## Zustand Stores The frontend uses four independent Zustand stores, each managing a distinct domain of application state. ### useSessionStore Manages session and dataset metadata: | Field | Type | Description | |-------|------|-------------| | `sessionId` | `string \| null` | Backend session ID | | `datasetId` | `string \| null` | Uploaded dataset ID | | `tableName` | `string \| null` | DuckDB table name | | `rowCount` | `number` | Total row count | | `columns` | `ColumnInfo[]` | Column schema with statistics | | `isConnected` | `boolean` | Backend connectivity flag | | `isLoaded` | `boolean` | Whether a dataset is loaded | Actions: `setSession`, `setSchema`, `clearSession`, `setConnected`. The `isLoaded` flag is the primary gate for conditional rendering -- components that require a dataset check this flag before rendering. ### useNotebookStore Manages the notebook cell layout: | Field | Type | Description | |-------|------|-------------| | `cells` | `Cell[]` | Ordered list of notebook cells | | `activeCellId` | `string \| null` | Currently selected cell | Actions: `addCell(type, title?, config?)`, `removeCell(id)`, `updateCell(id, updates)`, `setActiveCell(id)`, `moveCell(fromIndex, toIndex)`, `toggleCollapse(id)`. Each cell has a unique ID generated from a counter and timestamp, ensuring stability across re-renders. The `addCell` method returns the new cell ID for programmatic selection. ### useUIStore Manages UI panel toggles and error state: | Field | Type | Description | |-------|------|-------------| | `selectedSlot` | `'table' \| 'chart' \| 'schema' \| 'map' \| null` | Active cell type slot | | `showUploadDialog` | `boolean` | Upload dialog visibility | | `showManipulationPanel` | `boolean` | Transform panel visibility | | `showVisualizationPanel` | `boolean` | Visualization panel visibility | | `errors` | `string[]` | Queue of error messages | Actions: `setSelectedSlot`, `toggleUploadDialog`, `toggleManipulationPanel`, `toggleVisualizationPanel`, `addError`, `clearErrors`, `removeError`. The error queue allows multiple errors to be displayed simultaneously, with individual dismissal. ### useHistoryStore Manages the undo/redo state synchronized with the backend: | Field | Type | Description | |-------|------|-------------| | `nodes` | `HistoryNode[]` | Full history tree nodes | | `currentId` | `string \| null` | Current state ID | | `order` | `string[]` | Linear state order | | `canUndo` | `boolean` | Whether undo is available | | `canRedo` | `boolean` | Whether redo is available | Actions: `setHistory`, `updateCurrent`, `setUndoRedo`, `reset`. The `canUndo` and `canRedo` flags are computed from the position of `currentId` in the `order` array. ## API Client Layer ### Axios Instance The API client in `api/client.ts` creates a configured Axios instance: ```typescript const api = axios.create({ baseURL: import.meta.env.VITE_API_URL || '', withCredentials: true, headers: { 'Content-Type': 'application/json' }, }); ``` Key configuration: - **baseURL**: Reads from `VITE_API_URL` environment variable, defaulting to the same origin (for production where the backend serves the frontend). In development, the Vite proxy handles routing. - **withCredentials**: Enables sending cookies (specifically the `session_id` cookie) with every request. - **Response interceptor**: Extracts the `detail` field from error responses, providing a consistent error message format. ### API Helper Functions The client module exports typed async functions for each API endpoint: | Function | Method | Endpoint | |----------|--------|----------| | `uploadCSV(file)` | POST | `/api/upload/csv` | | `getSchema()` | GET | `/api/schema` | | `getRows(offset, limit)` | GET | `/api/rows` | | `listOperations()` | GET | `/api/ops` | | `applyOperation(opKey, params)` | POST | `/api/ops/{opKey}` | | `undo()` | POST | `/api/history/undo` | | `redo()` | POST | `/api/history/redo` | | `getHistory()` | GET | `/api/history` | | `createChart(req)` | POST | `/api/viz/chart` | | `exportCSV()` | GET | `/api/export/csv` | | `createCheckpoint()` | POST | `/api/checkpoint` | | `checkHealth()` | GET | `/health` | The CSV upload uses `multipart/form-data` content type. The CSV export uses `responseType: 'blob'` for binary file download. ### React Hooks The `api/hooks.ts` module wraps the API functions in React hooks with loading and error state management. Each hook follows the same pattern: ```typescript export function useUpload() { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const upload = useCallback(async (file: File): Promise => { setLoading(true); setError(null); try { return await uploadCSV(file); } catch (err: any) { setError(err.message); return null; } finally { setLoading(false); } }, []); return { upload, loading, error }; } ``` Available hooks: `useUpload`, `useSchema`, `useRows`, `useApplyOp`, `useHistory`, `useChart`, `useExport`. The `useExport` hook handles file download by creating a temporary anchor element and triggering a click event. ## Component Hierarchy The component tree is structured as follows: ``` App (ErrorBoundary) +-- Header +-- NotebookPage | +-- UploadPanel (modal, shown when no dataset or "New File" clicked) | +-- TableCellView (for each cell of type "table") | +-- ChartCellView (for each cell of type "chart") | +-- SchemaView (for each cell of type "schema") | +-- MapCanvas (for each cell of type "map") | +-- ManipulationPanel (side panel, toggled from header) | +-- VisualizationPanel (side panel, toggled from header) +-- ErrorToast ``` ### Header The header component displays the application title, dataset metadata (table name and row count), undo/redo buttons, cell creation buttons, transform/visualize panel toggles, and the CSV export button. It reads from `useSessionStore`, `useHistoryStore`, and `useUIStore`. ### NotebookPage The main content area that conditionally renders the landing screen (when no dataset is loaded) or the notebook cell list. The landing screen displays a centered call-to-action with the upload button. The notebook area renders each cell with a collapsible header showing the cell index, title, type badge, and collapse/remove controls. ### UploadPanel A modal dialog that wraps an HTML file input for CSV upload. Uses the `useUpload` hook to manage the upload lifecycle and update the session store on success. ### TableCellView Renders paginated table data using the `useRows` hook. Displays column headers and row data in a scrollable grid. ### ChartCellView Renders ECharts visualizations using the `echarts-for-react` library. When a chart configuration is provided (chartType and params), it calls the `useChart` hook to compute the chart spec and renders it with `ReactEChartsCore`. The component supports dynamic chart type changes and displays a loading spinner during computation. ### SchemaView Displays the column schema as a table with column name, type, statistics (null count, unique count, min, max, mean, std, quartiles). Uses the `useSchema` hook. ### ManipulationPanel A side panel that provides forms for applying data operations (filter, sort, select, rename, groupby, fill_nulls, clip). Uses the `useApplyOp` hook and reads available operations from `listOperations()`. ### VisualizationPanel A side panel for configuring chart visualizations. Allows the user to select a chart type, choose columns, and set parameters. Creates chart cells in the notebook when a visualization is configured. ### MapCanvas Renders geographic point data. Currently uses an SVG-based renderer with coordinate projection. Each point is rendered as a circle with latitude mapped to the Y axis and longitude to the X axis. The component displays point count and center coordinates. ### ErrorToast A notification component that displays errors from the `useUIStore` error queue. Each error can be individually dismissed. Errors are rendered at the bottom of the viewport. ## Cell System The notebook is organized as a collection of cells, each with a type, title, and configuration: ```typescript interface Cell { id: string; type: CellType; // 'table' | 'chart' | 'schema' | 'map' title: string; config: Record; state_id?: string; collapsed?: boolean; } ``` Cell operations: - **Add**: Header buttons create new cells with default configuration. - **Remove**: Each cell has a close button that removes it from the notebook. - **Collapse/Expand**: Each cell can be collapsed to its header, hiding the content. - **Active selection**: Clicking a cell sets it as active (highlighted with a brand ring). ## Drag and Drop The project includes `@dnd-kit/core` and `@dnd-kit/sortable` as dependencies for future drag-and-drop cell reordering. The `moveCell` action in the notebook store is prepared for this functionality, accepting source and destination indices to reorder the cell array. ## ECharts Rendering Chart rendering uses `echarts-for-react`, a React wrapper for the Apache ECharts library. The `ChartCellView` component receives the chart specification from the backend and passes it directly to `ReactEChartsCore`: ```typescript ``` - **notMerge**: Ensures each update replaces the entire chart rather than merging with previous options. - **lazyUpdate**: Defers rendering to the next animation frame for better performance. - The chart height is fixed at 400px, with full-width responsive behavior. ## Map Rendering The `MapCanvas` component currently uses an SVG-based renderer rather than Leaflet. This decision was made to reduce the initial bundle size and avoid Leaflet's CSS dependency. The SVG renderer: 1. Computes the projection by mapping latitude/longitude bounds to a 400x400 viewport. 2. Renders grid lines at regular intervals. 3. Plots each point as an SVG circle with brand-colored fill and stroke. 4. Displays coordinate bounds as text labels. A future migration to `react-leaflet` is planned for interactive map features (zoom, pan, marker popups). The `leaflet` and `react-leaflet` packages are already included as dependencies.