Files
spoolman2/CLAUDE.md
tonym 3aa633df0a
Some checks failed
CI / style (push) Has been cancelled
CI / build-client (push) Has been cancelled
CI / build-amd64 (push) Has been cancelled
CI / build-tester (push) Has been cancelled
CI / test (cockroachdb) (push) Has been cancelled
CI / test (mariadb) (push) Has been cancelled
CI / test (postgres) (push) Has been cancelled
CI / test (sqlite) (push) Has been cancelled
CI / build-arm64 (push) Has been cancelled
CI / build-armv7 (push) Has been cancelled
CI / publish-images (push) Has been cancelled
CI / publish-release (push) Has been cancelled
docs: Update CLAUDE.md with completed features #14, #15, and new features
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 22:41:17 -06:00

19 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

  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

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

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:

# 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:

Update script: /mnt/user/appdata/spoolman2-build/update.sh

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.