# Development Guide This document provides a comprehensive guide for setting up a local development environment, understanding the project structure, and extending the Visual Notebook with new operations, chart types, and frontend components. ## Prerequisites Before setting up the development environment, ensure you have the following tools installed: | Tool | Version | Purpose | |------|---------|---------| | Python | 3.11+ | Backend runtime | | Node.js | 20+ | Frontend build and development | | npm | 10+ | Frontend package management | | uv | Latest | Python package management (replaces pip/venv) | | Git | 2.40+ | Version control | ### Installing uv ```bash # macOS / Linux curl -LsSf https://astral.sh/uv/install.sh | sh # Windows powershell -c "irm https://astral.sh/uv/install.ps1 | iex" ``` ## Local Development Setup ### Backend Setup 1. **Navigate to the project root**: ```bash cd /path/to/va_project ``` 2. **Create a virtual environment and install dependencies**: ```bash uv sync ``` This command reads `pyproject.toml`, resolves dependencies, creates a virtual environment in `.venv/`, and installs all packages including development dependencies (pytest, httpx). 3. **Run the backend server**: ```bash uv run uvicorn visual_notebook.main:app --reload --host 0.0.0.0 --port 8000 ``` The `--reload` flag enables automatic server restart when source files change. 4. **Verify the server is running**: ```bash curl http://localhost:8000/health # Expected: {"status":"ok","version":"0.1.0"} ``` 5. **Access the API documentation**: Open `http://localhost:8000/docs` in a browser to view the Swagger UI generated by FastAPI. ### Frontend Setup 1. **Navigate to the frontend directory**: ```bash cd frontend ``` 2. **Install dependencies**: ```bash npm install ``` 3. **Start the development server**: ```bash npm run dev ``` The Vite dev server starts at `http://localhost:5173` with hot module replacement (HMR). API requests to `/api` and `/health` are automatically proxied to the backend at `http://localhost:8000` as configured in `vite.config.ts`. 4. **Build for production**: ```bash npm run build ``` The production build outputs to `frontend/dist/` with code-split chunks for ECharts, React, and vendor libraries. ### Running Both Together For the best development experience, run the backend and frontend in separate terminal sessions: - Terminal 1: `uv run uvicorn visual_notebook.main:app --reload --port 8000` - Terminal 2: `cd frontend && npm run dev` Visit `http://localhost:5173` for the full application with live reloading on both frontend and backend changes. ## Project Structure Overview ``` va_project/ pyproject.toml # Root workspace config Dockerfile # Combined build (frontend + backend) Dockerfile.backend # Backend-only build docker-compose.yml # Development Docker Compose datasets/ # Uploaded CSV files checkpoints/ # Session checkpoint files static/ # Built frontend (production) backend/ pyproject.toml # Backend dependencies visual_notebook/ main.py # FastAPI application entry config.py # Settings from environment session.py # Session and SessionStore api/ routes.py # API endpoint definitions schemas.py # Pydantic request/response models deps.py # Dependency injection domain/ dataframe.py # DataFrameRef wrapper history.py # History tree (Memento) operations.py # Operation registry checkpoints.py # Checkpoint persistence validators.py # Validation helpers data/ csv_loader.py # CSV loading and export schema.py # Schema inference types.py # Column type classification viz/ _common.py # Shared chart utilities histogram.py # Histogram computation scatter.py # Scatter plot computation bar_topn.py # Top-N bar chart timeline.py # Timeline/line chart heatmap.py # Heatmap computation map.py # Map computation tests/ test_history.py # History tests test_operations.py # Operations tests frontend/ package.json # Frontend dependencies vite.config.ts # Vite configuration tailwind.config.js # Tailwind CSS theme tsconfig.json # TypeScript configuration index.html # HTML entry point src/ main.tsx # React entry point App.tsx # Root component with ErrorBoundary api/ client.ts # Axios instance and API functions hooks.ts # React hooks for data fetching types.ts # TypeScript type definitions store/ session.ts # Session state store notebook.ts # Notebook cell store ui.ts # UI toggle store history.ts # Undo/redo state store components/ Header.tsx # Top navigation bar NotebookPage.tsx # Main notebook layout UploadPanel.tsx # CSV upload dialog TableCellView.tsx # Table data display ChartCellView.tsx # ECharts rendering SchemaView.tsx # Column schema display ManipulationPanel.tsx # Operation controls VisualizationPanel.tsx # Chart configuration TablePreview.tsx # Table row preview MapCanvas.tsx # Map point visualization ErrorToast.tsx # Error notification lib/ chartTypes.ts # Chart type definitions csv.ts # CSV utility functions format.ts # Number/formatting helpers styles/ index.css # Global styles and Tailwind docs/ # Documentation (this directory) ``` ## Adding a New Operation Follow these steps to add a new data operation (e.g., `deduplicate`) to the system: ### Step 1: Define the OperationDef Add a new entry to the `OPERATIONS` dictionary in `backend/visual_notebook/domain/operations.py`: ```python "deduplicate": OperationDef( key="deduplicate", label="Remove Duplicates", description="Remove duplicate rows based on specified columns", param_schema={ "type": "object", "required": ["columns"], "properties": { "columns": {"type": "array", "items": {"type": "string"}}, "keep": {"type": "string", "enum": ["first", "last", "none"], "default": "first"}, }, }, validate=_validate_deduplicate, apply=_apply_deduplicate, ), ``` ### Step 2: Implement the Validate Function ```python def _validate_deduplicate(params: dict, schema: list[dict]) -> dict: columns = params.get("columns", []) if not columns: raise ValueError("'columns' list is required") params.setdefault("keep", "first") return params ``` The validate function should check that all required parameters are present and have valid values. It receives the raw user params and the current table schema. ### Step 3: Implement the Apply Function Provide both DuckDB and Polars implementations: ```python def _apply_deduplicate(ref: DataFrameRef, params: dict) -> DataFrameRef: columns = params["columns"] keep = params.get("keep", "first") if ref.backend == "duckdb" and ref.name and ref.con: new_name = f"_dedup_{uuid.uuid4().hex[:6]}" cols = ", ".join(f'"{c}"' for c in columns) if keep == "first": sql = f''' SELECT DISTINCT ON ({cols}) * FROM "{ref.name}" ORDER BY {cols} ''' elif keep == "last": sql = f''' SELECT DISTINCT ON ({cols}) * FROM "{ref.name}" ORDER BY {cols} DESC ''' else: sql = f''' SELECT * FROM "{ref.name}" GROUP BY {cols} HAVING COUNT(*) = 1 ''' ref.con.execute(f'CREATE OR REPLACE TABLE "{new_name}" AS {sql}') return DataFrameRef(backend="duckdb", name=new_name, con=ref.con, source_path=ref.source_path) elif ref.backend == "polars" and ref.lf is not None: lf = ref.lf.unique(subset=columns, keep=keep) return DataFrameRef(backend="polars", lf=lf, source_path=ref.source_path) raise ValueError("Invalid DataFrameRef") ``` ### Step 4: Add Tests Add test cases to `backend/visual_notebook/tests/test_operations.py`: ```python def test_deduplicate_validation(): op = OPERATIONS["deduplicate"] with pytest.raises(ValueError): op.validate({}, []) result = op.validate({"columns": ["a", "b"]}, []) assert result["keep"] == "first" def test_deduplicate_in_registry(): assert "deduplicate" in OPERATIONS ``` ### Step 5: Verify 1. Run the tests: `uv run pytest backend/visual_notebook/tests/ -v` 2. Start the server and verify the operation appears in `GET /api/ops`. 3. Test the operation via the Swagger UI at `http://localhost:8000/docs`. ## Adding a New Chart Type Follow these steps to add a new chart type (e.g., `pie`) to the system: ### Step 1: Create the Computation Module Create a new file `backend/visual_notebook/viz/pie.py`: ```python """Pie chart generation.""" from __future__ import annotations from typing import Any import duckdb from visual_notebook.viz._common import PALETTE, base_chart, title, tooltip_style, palette_colors def compute_pie( con: duckdb.DuckDBPyConnection, table_name: str, category_col: str, value_col: str | None = None, n: int = 10, ) -> dict[str, Any]: """Compute pie chart data and return ECharts spec.""" if value_col: result = con.execute(f''' SELECT "{category_col}" as cat, SUM("{value_col}") as val FROM "{table_name}" WHERE "{category_col}" IS NOT NULL GROUP BY cat ORDER BY val DESC LIMIT {n} ''').fetchall() else: result = con.execute(f''' SELECT "{category_col}" as cat, COUNT(*) as val FROM "{table_name}" WHERE "{category_col}" IS NOT NULL GROUP BY cat ORDER BY val DESC LIMIT {n} ''').fetchall() data = [{"name": str(r[0]), "value": float(r[1])} for r in result] colors = palette_colors(len(data)) chart = base_chart() chart["title"] = title(f"Distribution of {category_col}") chart["tooltip"] = {**tooltip_style(), "trigger": "item", "formatter": "{b}: {c} ({d}%)"} chart["legend"] = {"orient": "vertical", "left": "left", "top": "middle"} chart["series"] = [{ "type": "pie", "radius": ["40%", "70%"], "data": data, "emphasis": {"itemStyle": {"shadowBlur": 10, "shadowOffsetX": 0, "shadowColor": "rgba(0,0,0,0.5)"}}, }] return chart ``` ### Step 2: Register in the Route Handler Add the import and handler in `backend/visual_notebook/api/routes.py`: ```python from visual_notebook.viz.pie import compute_pie # In the create_chart endpoint: elif req.chart_type == "pie": spec = compute_pie(con, table, params["category_col"], params.get("value_col"), params.get("n", 10)) ``` ### Step 3: Add Frontend Support Update `frontend/src/lib/chartTypes.ts` to include the new chart type, and add a configuration form in the `VisualizationPanel` component. ## Adding a New Frontend Component ### Step 1: Create the Component File Create a new file in `frontend/src/components/`: ```typescript // StatsPanel.tsx import React from 'react'; import { useSessionStore } from '../store/session'; export function StatsPanel() { const { columns, rowCount } = useSessionStore(); return (