visual-notebook / docs /frontend.md
Sid-WC121
bug fix1
018ab56
|
Raw History Blame Contribute Delete
13 kB

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_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:

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:

  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.