# 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 (Gitea) | # | 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 | ❌ REMOVED | | 8 | (duplicate of #7) | ✅ DONE | | 9 | Hierarchical Locations (Room/Bin) | ✅ DONE | | 10 | Database Import/Export | ✅ DONE | | 11 | Slicer Integration / Print Jobs | ✅ DONE | | 12 | Redesign Home Page Dashboard | ✅ DONE | | 13 | Batch Operations (Multi-Select Edit) | ✅ DONE | | 14 | Extra Weight Field (DryPods, etc.) | ✅ DONE | | 15 | Spool Count per Filament in List | ✅ DONE | | 16 | Filter Spools by Color | ✅ DONE | | 17 | Image Attachments for Filaments | **Open** | | 18 | QR Code Labels for Filaments | **Open** | | 19 | Integration with filamentcolors.xyz | ✅ DONE | | 20 | Gallery View for Spools (Color Grid) | ✅ DONE | | 21 | QR Code Version/Complexity Config | **Open** | | 23 | Reporting & Data Export | **Open** | | 24 | NFC/RFID Tag Support | **Open** | | 25 | Sort Spools by Custom Fields | ✅ DONE | | 26 | Label Template Enhancements | **Open** | | 27 | QR Scanner Camera Device Selector | **Open** | | 28 | Mobile-Friendly UI / Responsive | **Open** | | 29 | Table Column Customization | **Open** | | 30 | Printer Management & Parts Inventory | **Open** | | 31 | Special Filament Properties | **Open** | | 32 | SpoolType Model (Cardboard/Plastic/Metal) | **Open** | | 34 | Color Swatch Border for Dark Mode | ✅ DONE | | 35 | Global Search Box | ✅ DONE | | 36 | Spool-Level Overrides (Color, Price) | ✅ DONE | | 37 | Plugin System for Extensions | **Open** | | 39 | Usage Statistics & Cost Tracking | ✅ DONE | | 40 | Storage vs Active Spool Status | **Open** | | 41 | Load Cell / Scale Integration | **Open** | | 42 | Allow Spools Heavier Than Max | ✅ DONE | | 43 | Show Filament Fields on Spool View | ✅ DONE | | 44 | HomeAssistant Add-On Packaging | **Open** | | 45 | Filament Swatch Card Generator | **Open** | | 46 | Project-Based Spool Grouping | **Open** | | 47 | G-code Filament Usage Estimation | **Open** | ### Other Features (not tracked in Gitea) - ✅ Theme Colors (5 options) - ✅ Clickable Dashboard Cards - ✅ QR Code Size Setting - ✅ Parse Temps from Comments - ✅ Prev/Next Navigation on Edit Pages - ✅ Price/Cost Tracking (remaining value, inventory value) - ✅ Print History on Spool Detail - ✅ Usage Analytics Dashboard - ✅ Color Column in Spool List (with filter) ### 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 REMOVED - 3dfilamentprofiles Integration **Removed** - 3dfilamentprofiles.com has Vercel bot protection blocking automated requests. Their GitHub repo only has sample data (~3 filaments). SpoolmanDB has 6,957+ filaments, so this was unnecessary. ### 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) ### Issue #15 Complete - Print Job Tracking / Slicer Integration Track pending print jobs from slicer and manage filament deduction after prints complete. **Workflow:** 1. Slicer post-processing script parses G-code for filament usage 2. Script creates pending print job in Spoolman via API 3. After print completes, user visits Print Queue UI 4. Complete: Deducts filament from spool, records adjustment history 5. Cancel: Flags spool for manual weigh-in (no automatic deduction) **Backend:** - `spoolman/database/models.py` - `PrintJob` model, `needs_weighing` field on Spool - `spoolman/database/print_job.py` - CRUD operations for print jobs - `spoolman/api/v1/print_job.py` - REST API endpoints - `POST /print-job` - Create pending job - `POST /print-job/{id}/complete` - Complete job, deduct filament - `POST /print-job/{id}/cancel` - Cancel job, flag spool for weighing - `GET /print-job` - List jobs with status filter - `migrations/versions/2025_01_15_0400-c3d4e5f6g7h8_add_print_jobs.py` **Frontend:** - `client/src/pages/print-queue/index.tsx` - Print Queue UI page - `client/src/pages/spools/model.tsx` - `IPrintJob` interface, `needs_weighing` on ISpool **Slicer Script:** - `scripts/slicer_post_process.py` - Elegoo/Orca Slicer post-processing script - Parses G-code comments for filament usage (weight in grams) - Creates pending print job via API - Configure via env vars: `SPOOLMAN_URL`, `SPOOLMAN_SPOOL_ID` - Or config file: `~/.config/spoolman/slicer.conf` ### Color Column in Spool List Added optional color column to the spool list with filtering capability. **Backend:** - `spoolman/database/filament.py` - `find_colors()` function to get distinct color hex codes - `spoolman/api/v1/other.py` - `GET /api/v1/color` endpoint to list all colors - `spoolman/api/v1/spool.py` - Added `filament.color_hex` query parameter for filtering - `spoolman/database/spool.py` - Filter implementation (supports comma-separated values) **Frontend:** - `client/src/components/column.tsx` - `ColorFilterColumn` component renders color swatches - `client/src/components/otherModels.tsx` - `useSpoolmanColors()` hook to fetch colors - `client/src/pages/spools/list.tsx` - Added color column (hidden by default) - `client/public/locales/en/common.json` - Added "Color" translation key **Features:** - Color displayed as small swatch with hex tooltip - Filter dropdown shows swatches with hex codes - Multi-select filtering (can filter by multiple colors) - Column hidden by default (enable via column selector) ### Issue #9 Complete - Hierarchical Locations Storage locations can now be organized in a tree structure (Room > Shelf > Bin). **Backend:** - `spoolman/database/models.py` - Added `Location` model with self-referential parent - `spoolman/database/location.py` - CRUD operations, tree building, full path resolution - `spoolman/api/v1/location.py` - REST API for locations - `migrations/versions/2025_01_21_0100-*.py` - Migration for location table **Frontend:** - `client/src/components/LocationTreeSelect.tsx` - Tree-based location picker - `client/src/components/LocationCreateModal.tsx` - Inline location creation **Features:** - Hierarchical structure with parent/child relationships - Location types: room, shelf, bin, drawer, box, other - Legacy flat location string preserved for backward compatibility - API endpoints: GET /location, GET /location/tree, POST/PATCH/DELETE ### Issue #12 Complete - Dashboard Redesign Redesigned home page with better visualizations and quick stats. **Backend:** No changes needed - uses existing endpoints. **Frontend:** - `client/src/pages/home/index.tsx` - Refactored layout - `client/src/pages/home/components/AlertCards.tsx` - Low stock, pending jobs, needs weighing alerts - `client/src/pages/home/components/MaterialDistributionChart.tsx` - Pie chart by material (recharts) - `client/src/pages/home/components/QuickStats.tsx` - Total spools, filaments, vendors, weight **Dependencies:** - Added `recharts` package for charts ### Issue #13 Complete - Batch Operations Multi-select spools with bulk archive/delete/move actions. **Backend:** - `spoolman/api/v1/spool.py` - Added bulk endpoints: - POST /spool/bulk/archive - POST /spool/bulk/delete - POST /spool/bulk/update **Frontend:** - `client/src/pages/spools/list.tsx` - Added row selection - `client/src/pages/spools/components/BatchActionBar.tsx` - Floating action bar - `client/src/pages/spools/functions.tsx` - Bulk helper functions **Features:** - Checkbox selection with select-all - Floating action bar shows when items selected - Archive, unarchive, delete, move to location - Confirmation modals for all destructive actions ### Issue #14 Complete - Extra Weight Field Track extra weight from DryPods, custom spool holders, or other accessories. **Backend:** - `spoolman/database/models.py` - Added `extra_weight` column to Spool model - `spoolman/database/spool.py` - Updated `measure()` to account for extra weight - `spoolman/api/v1/spool.py` - Added `extra_weight` to create/update parameters - `migrations/versions/2025_01_21_0200-*.py` - Migration for extra_weight column **Frontend:** - `client/src/pages/spools/create.tsx` - Added extra_weight form field - `client/src/pages/spools/edit.tsx` - Added extra_weight form field - Weight calculations updated to include extra_weight in gross weight **Features:** - Optional field for tracking additional weight on spool - Automatically subtracted when measuring spool weight - Useful for DryPods, custom spool holders, reel adapters ### Issue #15 Complete - Spool Count per Filament Already implemented - filament list shows `spool_count` and `total_remaining_weight` columns. ### Price & Cost Tracking Track spool purchase price and calculate remaining value. **Backend:** - `spoolman/api/v1/models.py` - Added `remaining_value` computed field - Formula: `remaining_value = (remaining_weight / initial_weight) * price` - Price falls back to filament price if not set on spool **Frontend:** - `client/src/pages/spools/list.tsx` - Added remaining_value column (hidden by default) - `client/src/pages/home/components/QuickStats.tsx` - Added "Inventory Value" stat ### Print History on Spool Detail Show print job history on the spool detail page. **Frontend:** - `client/src/pages/spools/show.tsx` - Added collapsible print history table - Fetches print jobs filtered by spool_id - Shows: Created date, Filename, Filament Used, Status, Finished date ### Usage Analytics Dashboard Time-series charts showing filament consumption trends. **Backend:** - `spoolman/api/v1/other.py` - New analytics endpoints: - `GET /analytics/usage?days=N` - Daily usage from SpoolAdjustments - `GET /analytics/by-material?days=N` - Usage breakdown by material **Frontend:** - `client/src/pages/home/components/UsageAnalytics.tsx` - New component - Area chart showing daily consumption (recharts) - Period selector: 7, 30, or 90 days - Material breakdown cards showing usage by filament type --- ## 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" ``` --- ### Issue #42 Complete - Allow Spools Heavier Than Max Removed weight validation cap that prevented spools from exceeding their theoretical initial weight. **Backend:** - `spoolman/database/spool.py` - Removed `max(..., 0)` clamps from create/update; when remaining > initial, adjusts initial upward - `spoolman/api/v1/models.py` - Removed `max(..., 0)` from remaining_weight computation ### Issue #43 Complete - Show Filament Fields on Spool View Display filament custom fields on the spool detail page. **Frontend:** - `client/src/pages/spools/show.tsx` - Added `filamentExtraFields` section after spool extra fields ### Issue #25 Complete - Sort by Custom Fields & Remaining Weight **Backend:** - `spoolman/database/spool.py` - Added `extra.{key}` sort support via outerjoin on SpoolField table **Frontend:** - `client/src/pages/spools/list.tsx` - Added `sorter: true` to weight/length NumberColumns - `client/src/components/column.tsx` - Changed CustomFieldColumn to sortable with `dataId: extra.{key}` ### Issue #35 Complete - Global Search Box **Backend:** - `spoolman/api/v1/spool.py` - Added `q` parameter to find endpoint - `spoolman/database/spool.py` - OR-based search across filament name, material, vendor name, location, lot_nr, comment **Frontend:** - `client/src/pages/spools/list.tsx` - Added `Input.Search` component, passes `q` via meta.queryParams ### Issue #20 Complete - Gallery View for Spools Color grid view for spools with progress bars and material labels. **Frontend:** - `client/src/pages/spools/components/SpoolGallery.tsx` - Grid of color cards with progress bars - `client/src/pages/spools/list.tsx` - View mode toggle (table/gallery) ### Issue #36 Complete - Spool-Level Overrides Per-spool color override with ColorPicker (price and initial_weight already existed). **Backend:** - `spoolman/database/models.py` - Added `color_hex` field to Spool model - `spoolman/api/v1/spool.py` - Added `color_hex` to create/update params - `migrations/versions/2025_01_22_0100-g7h8i9j0k1l2_add_spool_color_hex.py` **Frontend:** - `client/src/pages/spools/create.tsx`, `edit.tsx` - ColorPicker form field - All display locations updated to prefer `spool.color_hex` over `filament.color_hex` ### Issue #39 Complete - Usage Statistics & Cost Tracking Added cost analytics endpoint and total spent / avg cost per kg stats. **Backend:** - `spoolman/api/v1/other.py` - Added `GET /analytics/cost` endpoint returning total_spent, avg_cost_per_kg, cost_by_material **Frontend:** - `client/src/pages/home/components/QuickStats.tsx` - Added "Total Spent" and "Avg Cost/kg" stat cards - `client/src/pages/home/components/UsageAnalytics.tsx` - Added cost per material in breakdown cards ### Issue #19 Complete - filamentcolors.xyz Integration Color lookup from filamentcolors.xyz database (2134 swatches, 317 manufacturers). **Backend:** - `spoolman/api/v1/other.py` - Proxy endpoints: - `GET /filamentcolors/manufacturers` - List all manufacturers - `GET /filamentcolors/search` - Search swatches by manufacturer, color, filament type **Frontend:** - `client/src/components/filamentColorLookup.tsx` - Modal with manufacturer filter + color grid - Added search button next to ColorPicker on filament create and edit forms - Click a swatch to apply its measured hex color ### Bug Fixes - **Filament Select Refresh**: Fixed race condition where form value was set before options re-rendered. Uses `pendingFilamentId` state + useEffect. - **Parse Temps from Comments**: Added regex-based temperature parser button on filament edit page. ## Known Issues / TODO ### Issue #7 - 3dfilamentprofiles Integration REMOVED The 3dfilamentprofiles.com website has Vercel bot protection that blocks automated requests. Their GitHub repo (jklewa/filament-profiles-data) only provides sample data (~3 filaments). Since SpoolmanDB already has 6,957+ filaments from major brands, the 3dfp integration was removed rather than maintaining dead code.