Spaces:
Sleeping
Download docs/frontend.md from Sid-WC121/visual-notebook: direct link, hf CLI and curl.
- Browser
- Download file 13 kB
-
https://huggingface.co/spaces/Sid-WC121/visual-notebook/resolve/main/docs/frontend.md
- Command line
-
hf download hf://spaces/Sid-WC121/visual-notebook/docs/frontend.md
-
curl -L -o frontend.md https://huggingface.co/spaces/Sid-WC121/visual-notebook/resolve/main/docs/frontend.md
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:
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:
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:
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:
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_URLenvironment 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_idcookie) with every request. - Response interceptor: Extracts the
detailfield 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:
export function useUpload() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const upload = useCallback(async (file: File): Promise<UploadResponse | null> => {
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:
interface Cell {
id: string;
type: CellType; // 'table' | 'chart' | 'schema' | 'map'
title: string;
config: Record<string, any>;
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:
<ReactEChartsCore
echarts={echarts}
option={chartSpec}
style={{ height: 400, width: '100%' }}
notMerge={true}
lazyUpdate={true}
/>
- 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:
- Computes the projection by mapping latitude/longitude bounds to a 400x400 viewport.
- Renders grid lines at regular intervals.
- Plots each point as an SVG circle with brand-colored fill and stroke.
- 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.