Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
11 KiB
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)
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
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
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:
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:
pytest tests_integration/tests/spool/test_add.py::test_add_spool_remaining_weight -v
Database Migrations
- Edit models in
spoolman/database/models.py - Run the server to update local SQLite
pdm run alembic revision -m "description" --autogenerate- Review generated migration, format with Black/Ruff
Architecture
Backend (/spoolman/)
main.py- FastAPI app entry, startup/shutdown, SPA mountingenv.py- Environment variable parsing and configurationws.py- WebSocket manager for real-time updatesapi/v1/- REST endpoints and WebSocket handlersrouter.py- Main router with WebSocket root endpointspool.py,filament.py,vendor.py- CRUD endpoints per resourcemodels.py- Pydantic response models
database/- SQLAlchemy models and async database operationsmodels.py- ORM definitions (Vendor, Filament, Spool, Setting, *Field)database.py- Connection setup, backup logic, migrations
Frontend (/client/src/)
App.tsx- Routing and Refine framework setuppages/- Page components (spools/, filaments/, vendors/, settings/)components/dataProvider.ts- REST API integrationcomponents/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/orWS /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 CORSVITE_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 |
Issue #1 Complete - Inline Creation Modals
Added ability to create filament/vendor inline without leaving the page:
client/src/components/vendorCreateModal.tsx- Quick vendor creationclient/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
dropdownRenderpattern (same as location select) - Uses
useInvalidateto 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
qrCodePaddingsetting (0-10mm, default 2mm) - Added
qrCodeMaxWidthsetting (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 interfaceclient/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 columnsmigrations/versions/2025_01_15_0200-*.py- Migrationspoolman/api/v1/models.py,filament.py- API changesclient/src/pages/filaments/create.tsx- Form updatesclient/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- AddedSpoolAdjustmentmodel with relationship to Spoolmigrations/versions/2025_01_15_0300-*.py- Migration for spool_adjustment tablespoolman/api/v1/models.py- AddedSpoolAdjustmentPydantic modelspoolman/api/v1/spool.py- Added comment field to use/measure endpoints, GET /spool/{id}/adjustments endpointspoolman/database/spool.py- Updated use_weight, use_length, measure functions to record adjustments
Frontend:
client/src/pages/spools/model.tsx- AddedISpoolAdjustmentinterfaceclient/src/pages/spools/show.tsx- Collapsible adjustment history tableclient/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
ExternalSourceenum (SPOOLMANDB, FILAMENT_PROFILES_3D) - Added
Filament3DFPmodel 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- AddedExternalSourceenum,sourcefield, temp range fieldsclient/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→manufacturercolor+material_type→namergb→color_hex(strip # prefix)properties.temp_min/max→extruder_temp/extruder_temp_maxproperties.bed_temp_min/max→bed_temp/bed_temp_max- Diameter defaults to 1.75mm (3dfp doesn't provide)
- Density from material lookup table
Local Development Setup
To run locally against the Unraid PostgreSQL database:
# 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
Dev Mode Styling
Files modified for dev version visual distinction:
client/src/contexts/color-mode/index.tsx- Teal primary color (#0891b2)client/src/components/layout.tsx- "DEV BUILD" banner and badge
Set IS_DEV = false in layout.tsx to disable dev styling.
Known Issues / TODO
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).
Attempted fixes:
- Added
refetchfromuseGetFilamentSelectOptionshook - Called
await refetchFilaments()before setting form value
Still not working reliably. May need to investigate react-query cache invalidation timing or add the new option manually to the select options array.
3dfilamentprofiles Sample Data
The 3dfp GitHub repo only contains sample data (3 filaments). The full dataset may require:
- Using their website API directly (may need auth)
- Waiting for them to publish full JSON exports
- Current implementation uses
sample-filaments.jsonfrom the repo
Next Up: Issue #9 - Hierarchical Locations
Add room/bin/shelf support for locations. See Gitea issue #9 for details.