# 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.) | **Open** | | 15 | Spool Count per Filament in List | **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 - ✅ 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 --- ## 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). ### 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.