# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview Spoolman is a self-hosted web service for managing 3D printer filament spools. It tracks inventory, monitors usage, and integrates with OctoPrint and Klipper/Moonraker. **Tech Stack:** - Backend: Python 3.9-3.12, FastAPI, SQLAlchemy 2.0 (async), Alembic - Frontend: React 19, TypeScript, Vite, Ant Design, Refine framework - Databases: SQLite (default), PostgreSQL, MySQL, CockroachDB ## Common Commands ### Python (PDM) ```bash pdm install # Install all dependencies including dev pdm sync --prod --no-editable # Install production dependencies only pdm run app # Run backend server (uvicorn) pdm run itest # Run integration tests (all databases) pdm run docs # Generate API documentation ``` ### Frontend ```bash cd client npm ci # Install dependencies npm run dev # Development server with hot reload npm run build # Production build npm run tsc # Type check only ``` ### Linting/Formatting ```bash black . # Format Python code ruff check . --fix # Lint and auto-fix Python pre-commit run --all-files # Run all pre-commit hooks ``` ### Integration Tests Tests run in Docker containers against multiple database types: ```bash python tests_integration/run.py sqlite # Single database python tests_integration/run.py sqlite postgres # Multiple databases pdm run itest # All databases ``` Run a single test: ```bash pytest tests_integration/tests/spool/test_add.py::test_add_spool_remaining_weight -v ``` ### Database Migrations 1. Edit models in `spoolman/database/models.py` 2. Run the server to update local SQLite 3. `pdm run alembic revision -m "description" --autogenerate` 4. Review generated migration, format with Black/Ruff ## Architecture ### Backend (`/spoolman/`) - `main.py` - FastAPI app entry, startup/shutdown, SPA mounting - `env.py` - Environment variable parsing and configuration - `ws.py` - WebSocket manager for real-time updates - `api/v1/` - REST endpoints and WebSocket handlers - `router.py` - Main router with WebSocket root endpoint - `spool.py`, `filament.py`, `vendor.py` - CRUD endpoints per resource - `models.py` - Pydantic response models - `database/` - SQLAlchemy models and async database operations - `models.py` - ORM definitions (Vendor, Filament, Spool, Setting, *Field) - `database.py` - Connection setup, backup logic, migrations ### Frontend (`/client/src/`) - `App.tsx` - Routing and Refine framework setup - `pages/` - Page components (spools/, filaments/, vendors/, settings/) - `components/dataProvider.ts` - REST API integration - `components/liveProvider.ts` - WebSocket live updates ### Data Model ``` Vendor (1) ──→ (many) Filament (1) ──→ (many) Spool ``` All entities support custom fields via *Field tables (VendorField, FilamentField, SpoolField). ### API Patterns - REST: Standard CRUD at `/api/v1/{resource}` - WebSocket: Real-time events at `WS /api/v1/` or `WS /api/v1/{resource}` - Query params: `?skip=0&limit=50&sort_by=name&sort_order=asc` ### Key Configuration Environment variables (see `.env.example`): - `SPOOLMAN_DB_TYPE` - Database type (sqlite/mysql/postgres/cockroachdb) - `SPOOLMAN_PORT` - Server port (default: 7912) - `SPOOLMAN_DEBUG_MODE` - Enables permissive CORS - `VITE_APIURL` - Frontend API base URL (e.g., `/api/v1`) ## Code Quality - Line length: 120 characters - Python target: 3.9 - Pre-commit hooks enforce Black formatting and Ruff linting - Backend is fully async using SQLAlchemy AsyncSession ## Gitea Repository - **URL:** http://192.168.0.5:3022/tonym/spoolman2 - **API Token:** 8a04b3cb5dbb54e2d895b707305523c3ad83a945 - **Upstream:** https://github.com/Donkie/Spoolman (remote: `upstream`) This is a fork with UX improvements. Issues are tracked on the Gitea instance above. ## UX Improvement Issues | # | Title | Status | |---|-------|--------| | 1 | Inline Creation Modals | ✅ DONE | | 2 | Smart Density Defaults | ✅ DONE | | 3 | QR Code Sizing Fixes | ✅ DONE | | 4 | Layout Max-Width | ✅ DONE | | 5 | Temperature Ranges | ✅ DONE | | 6 | Spool Adjustment History | ✅ DONE | | 7 | 3dfilamentprofiles Integration | ✅ DONE | | 8 | (see Gitea #8) | ✅ DONE | | 9 | Hierarchical Locations (Room/Bin) | Open | | 10 | Database Import/Export | ✅ DONE | | 11 | Orca Slicer / Elegoo Slicer Integration | Open | | - | Theme Colors | ✅ DONE | | - | Clickable Dashboard Cards | ✅ DONE | | - | QR Code Size Setting | ✅ DONE | | - | Parse Temps from Comments | ✅ DONE | | - | Prev/Next Navigation on Edit Pages | ✅ DONE | ### Issue #1 Complete - Inline Creation Modals Added ability to create filament/vendor inline without leaving the page: - `client/src/components/vendorCreateModal.tsx` - Quick vendor creation - `client/src/components/filamentCreateModal.tsx` - Quick filament creation with nested vendor modal - Modified spool create to add "Create Filament" in dropdown - Modified filament create to add "Create Manufacturer" in dropdown - Uses `dropdownRender` pattern (same as location select) - Uses `useInvalidate` to refresh lists after creation ### Issue #2 Complete - Smart Density Defaults Auto-fill density based on material type: - `client/src/utils/materialDefaults.ts` - Density lookup table with fuzzy matching - Material field converted to AutoComplete (dropdown suggestions + free text) - Fuzzy matching: "PLA Silk" → PLA (1.24 g/cm³) - New filaments default to 1.75mm diameter and 1.24 g/cm³ (PLA) - Applied to both full create page and FilamentCreateModal - Edit page not affected (preserves user customizations) Material densities in lookup: ``` PLA: 1.24, ABS: 1.04, PETG: 1.27, NYLON: 1.52, TPU: 1.21 PC: 1.3, WOOD: 1.28, CF: 1.3, PC/ABS: 1.19, HIPS: 1.03 PVA: 1.23, ASA: 1.05, PP: 0.9, POM: 1.4, PMMA: 1.18, FPE: 2.16 ``` ### Issue #3 Complete - QR Code Sizing Fixes See upstream issue: https://github.com/Donkie/Spoolman/issues/671 Changes: - Removed fixed 50% max-width constraint (now uses fit-content by default) - Added `qrCodePadding` setting (0-10mm, default 2mm) - Added `qrCodeMaxWidth` setting (0-100%, 0 = auto) - Added UI controls with slider + input number for both settings Files modified: - `client/src/pages/printing/printing.tsx` - Added settings to interface - `client/src/pages/printing/qrCodePrintingDialog.tsx` - CSS and UI controls ### Issue #4 Complete - Layout Max-Width Changes: - Added 1400px max-width container in `SpoolmanLayout` - Added 800px max-width for forms via CSS override in `overrides.css` - Content is centered on wide screens ### Issue #5 Complete - Temperature Ranges Store extruder and bed temperatures as min/max ranges: - Added 4 new DB columns: `settings_extruder_temp_min/max`, `settings_bed_temp_min/max` - Old single-value columns kept for backward compatibility (deprecated) - Alembic migration copies existing values to both min and max - Frontend uses Space.Compact with min/max input pairs - Label template shows ranges: "ET: 190-220 °C" Files: - `spoolman/database/models.py` - New columns - `migrations/versions/2025_01_15_0200-*.py` - Migration - `spoolman/api/v1/models.py`, `filament.py` - API changes - `client/src/pages/filaments/create.tsx` - Form updates - `client/src/pages/printing/spoolQrCodePrintingDialog.tsx` - Template tags ### Issue #6 Complete - Spool Adjustment History Track history of all spool weight/length adjustments with timestamps and comments. Backend: - `spoolman/database/models.py` - Added `SpoolAdjustment` model with relationship to Spool - `migrations/versions/2025_01_15_0300-*.py` - Migration for spool_adjustment table - `spoolman/api/v1/models.py` - Added `SpoolAdjustment` Pydantic model - `spoolman/api/v1/spool.py` - Added comment field to use/measure endpoints, GET /spool/{id}/adjustments endpoint - `spoolman/database/spool.py` - Updated use_weight, use_length, measure functions to record adjustments Frontend: - `client/src/pages/spools/model.tsx` - Added `ISpoolAdjustment` interface - `client/src/pages/spools/show.tsx` - Collapsible adjustment history table - `client/public/locales/en/common.json` - Translation keys for history table ### Issue #7 Complete - 3dfilamentprofiles Integration Added 3dfilamentprofiles.com as a second external database source alongside SpoolmanDB. Backend (`spoolman/externaldb.py`): - Added `ExternalSource` enum (SPOOLMANDB, FILAMENT_PROFILES_3D) - Added `Filament3DFP` model for raw 3dfp data - Added `_transform_3dfp_to_external()` to convert 3dfp format to ExternalFilament - Updated `_sync()` to fetch from both sources and merge results - Environment variables: `EXTERNAL_DB_3DFP_URL`, `EXTERNAL_DB_3DFP_ENABLED` Frontend: - `client/src/utils/queryExternalDB.ts` - Added `ExternalSource` enum, `source` field, temp range fields - `client/src/components/filamentImportModal.tsx` - Added tabs to filter by source (All/SpoolmanDB/3DFP) - `client/src/pages/filaments/create.tsx` - Updated import handler for temp range fields Data mapping (3dfp → Spoolman): - `brand_name` → `manufacturer` - `color` + `material_type` → `name` - `rgb` → `color_hex` (strip # prefix) - `properties.temp_min/max` → `extruder_temp/extruder_temp_max` - `properties.bed_temp_min/max` → `bed_temp/bed_temp_max` - Diameter defaults to 1.75mm (3dfp doesn't provide) - Density from material lookup table ### Issue #10 Complete - Database Import/Export Full backup/restore functionality for migrating between databases (PostgreSQL ↔ SQLite). Backend (`spoolman/api/v1/backup.py`): - `GET /api/v1/backup/export` - Downloads all data as single JSON file - `POST /api/v1/backup/import` - Imports JSON with `replace` option - Exports: vendors, filaments, spools, spool_adjustments, settings - Handles ID remapping to maintain relationships - Requires `python-multipart` package for file uploads Frontend (`client/src/pages/settings/generalSettings.tsx`): - "Backup & Restore" section in Settings page - "Download Backup" button for export - File upload with "Import (Merge)" or "Import (Replace All)" options - Confirmation dialog for destructive replace operation Migration workflow: 1. Export from PostgreSQL instance via Settings → Download Backup 2. Reconfigure container for SQLite 3. Import via Settings → Import (Replace All) --- ## Local Development Setup To run locally against the Unraid PostgreSQL database: ```bash # Create virtualenv and install deps python3 -m venv .venv source .venv/bin/activate pip install -e ".[postgres]" # Start backend (reads .env file) source .venv/bin/activate export $(grep -v '^#' .env | xargs) uvicorn spoolman.main:app --host 0.0.0.0 --port 7912 # Start frontend (separate terminal) cd client VITE_APIURL=http://localhost:7912/api/v1 npm run dev ``` Access at http://localhost:5173 ### Issue #11 Complete - Theme Colors Added theme color selector with 5 color options: - `client/src/contexts/color-mode/index.tsx` - Theme color system with THEME_COLORS constant - Colors: orange, teal, red, green, blue - Persists in localStorage - Settings page has color selector buttons ### Issue #12 Complete - Clickable Dashboard Cards Dashboard cards now link to their respective list pages: - `client/src/pages/home/index.tsx` - Card body is clickable, links to list - Only the + button stays independent (for create) ### Issue #13 Complete - QR Code Size Setting Added explicit QR code size control: - `client/src/pages/printing/printing.tsx` - Added `qrCodeSize` to interface - `client/src/pages/printing/qrCodePrintingDialog.tsx` - Slider for size in mm (0=auto) --- ## Deployment ### Production (Unraid) Deployed on Unraid with br0 network: - **URL:** http://192.168.0.11:8000 or https://spoolman.nastynas.xyz - **Database:** SQLite - **Network:** br0 (own IP: 192.168.0.11) Update script: `/mnt/user/appdata/spoolman2-build/update.sh` ```bash ssh root@192.168.0.5 "/mnt/user/appdata/spoolman2-build/update.sh" ``` --- ## Known Issues / TODO ### Parse Temperature from Comments Some filaments have temp info in comment field (e.g., "extrude 190-230 / bed 50-70") but not in the min/max fields. Could add a migration or button to parse and populate. ### Filament Select Not Refreshing After Inline Creation When creating a filament via the inline modal on spool create page, the select dropdown doesn't immediately show the new filament with its name (shows ID instead). ### 3dfilamentprofiles Sample Data The 3dfp GitHub repo only contains sample data (3 filaments). The full dataset may require using their website API directly. ### Future: Issue #9 - Hierarchical Locations Add room/bin/shelf support for locations. See Gitea issue #9 for details.