Conversation
…t-workflow standards
…seed Add SQLAlchemy models (FilamentMapping, SpoolMapping, Conflict, SyncLog, Snapshot, BridgeConfig), Alembic setup with initial migration covering all six tables, and lifespan wiring that runs migrations and seeds BridgeConfig defaults on startup. Uses synchronous SQLAlchemy (see docs/decisions.md).
Phase 2 (sync engine), Phase 3 (API layer), Phase 3b (wizard execute, carved out from FR-7). Records the build-phase table update and the inline async-job/sync-DB decision in the engine prompt.
…flict/log) Implements FR-8 through FR-14: snapshot/diff cycle, weight sync both directions (usage-log for FDB decrements, direct PUT for increases), field-mapping resolution, new-spool detection, conflict queuing (never auto-resolved), dry-run mode, and scheduler wiring gated by BridgeConfig.auto_sync_enabled. New modules: core/weight.py, core/matcher.py, core/fields.py, core/differ.py, core/engine.py. Write methods added to both upstream clients. 57 tests passing.
Enables Claude Code's sandbox (confine writes to the repo, no network except localhost + pypi/npm) with autoAllowBashIfSandboxed, plus Read/Edit/Write(**) for in-repo file ops. Reduces permission prompts while keeping out-of-repo writes and network behind a prompt. Preserves the existing vexp PreToolUse guard.
Add .vexpignore excluding prompts/done/ (completed handoffs) from vexp's ranked context — they were duplicating/crowding out source code in results. Extend .gitignore with frontend/Python build & cache artifacts (.vite/, *.tsbuildinfo, coverage/, .ruff_cache/, .mypy_cache/, .coverage, htmlcov/) so generated files stay out of git and vexp's untracked-file index.
Add .vexp/workspace.json to .vexp/.gitignore (machine-local VS Code repo config, like parent_workspace.json) so it isn't accidentally committed. Commit the refreshed manifest.json reflecting the current indexed source set.
Adopt vexp-context-engine @ v1.0.0 from homelab-configs: - Stop tracking .vexp/manifest.json — Shape B, each host builds and owns its own index; update .vexp/.gitignore + .gitattributes to match. - Promote the vexp MCP tool allows into committed .claude/settings.json. - Add the Context-search operational-rules snippet to CLAUDE.md. - Record the adoption (pinned v1.0.0) in standards.md.
- docker-compose.yml: GHCR image refs (filament-db/spoolman were nonexistent Docker Hub names), SPOOLMAN_PORT=7912 (binds 8000 by default), add mongo:7 + MONGODB_URI (filament-db 500s without it), drop the dead filamentdb-data volume. - main.py: real SPA fallback for BrowserRouter — /assets mount + catch-all serving index.html, so client routes survive refresh/direct-load while unknown /api paths still 404. Guarded behind _static_dir.is_dir(). - docker-compose.dev.yml: tracked dev stack with data bind-mounted under gitignored ./private_data/ (no real data committed). - docs/decisions.md: record the four fixes + the deep-link-base caveat.
…ng, import guards) - A: update_spool now uses PATCH (PUT returned 405 on Spoolman v0.23.1); fixes 167 wizard execute failures and the FR-10 ongoing weight sync path - B: filament create no longer 400s on null material; falls back to "Unknown" with a logged warning naming the Spoolman filament id - C: configurable weight_precision_decimals (default 2, range 0–4) applied in both weight conversion directions; Settings UI adds a 0–4 dropdown; churn-safe (max rounding delta 0.005 g << 2 g threshold) - D: wizard_completed only flips true when failed == 0; partial runs leave it false so users can fix issues and re-run idempotently - 99 backend tests green; frontend build clean - docs/reconcile-backlog.md captures 4 deferred items (name collisions, empty spools, default-tare spools, variant grouping on fresh import)
…ticolor handoff prompts
Implements the multicolor mapping decision (docs/decisions.md 2026-05-30): - core/color.py: pure project_colorname() helper with 100-color CSS/X11 palette; nearest_color_name() via Euclidean RGB distance; coaxial→coextruded / longitudinal→gradient vocabulary; no I/O, no external deps. - wizard execute (SM→FDB): _fdb_filament_payload_from_sm() includes colorName for multicolor creates; Phase C applies it to linked filaments as a best-effort update so the wizard handles both create and link paths. - engine (ongoing sync): per-cycle colorName re-derivation for every mapped multicolor spool pair; _colorName tracked in SM spool snapshot so a format setting change triggers a rewrite on the next cycle without a Spoolman-side diff; protect_multicolor guard in _apply_field_changes blocks FDB→SM color field sync for multicolor Spoolman filaments. - Config: two new BridgeConfig keys (multicolor_colorname_format default "name", protect_multicolor_color_in_spoolman default true); surfaced in ConfigResponse and ConfigUpdateRequest. - Settings UI: format dropdown (Name / Hex) + protect checkbox with loss-warning banner when disabled; wired through existing typed api client. - Tests: 29 new color tests + 3 new engine integration tests (colorName set on sync, format-change triggers rewrite, protect blocks FDB→SM color write). 131 backend tests pass; frontend build clean.
vexp now runs as a standalone Ansible-provisioned systemd-user daemon (vexp.service -> vexp serve, enabled + linger); the VS Code extension is deprecated and not used. Re-apply the repo push at v2.0.0 and bump the pin. - CLAUDE.md: replace Context-search snippet with v2.0.0 verbatim — daemon is systemd-managed (vexp daemon-cmd / systemctl are the expected control path, no longer forbidden), add host-namespace bullet; source comment -> @ v2.0.0 - .claude/CLAUDE.md: untrack vexp's auto-generated per-host file (machine-local under v2.0.0) and gitignore it; shared .claude/ files stay tracked - .claude/hooks/vexp-guard.sh: refresh to v2.0.0 shipped comments (no behavior change) - standards.md: pin 1.0.0 -> 2.0.0, record verification result Verified on this host: local CLI-core daemon on 127.0.0.1:7821 (index 71 files / 798 nodes / 569 edges, not a VS Code daemon); LLM vexp-devmind-v1 Operational on CPU fallback (CUDA driver present, accelerator plugin not loaded - conformant).
…doption - vexp-context-engine pin 2.0.0 -> 2.1.0 (GPU-offload verify step sharpened; no artifact changes). CPU fallback now noted as drift-to-fix, still conformant. - Record repo-sandbox-permissions @ 1.0.0 (scope: repo-wide) — already implemented in .claude/settings.json but previously untracked. Add standards.md row + CLAUDE.md pointer (no snippet per the standard).
…nflicts/skipped) Standardize all dry-run preview entries to a typed SyncPreviewEntry shape (action/entity_type/direction/label/field/old/new/reason/spoolman_id/fdb_filament_id/fdb_spool_id). Add missing skip entries for archived-spool and first-baseline paths. Frontend renders four collapsible <details> sections (Created/Updated/Conflicts/Skipped) with per-row direction tag, field, old→new values, reason, and DeepLinks pair.
…/skipped) Move _plan_spoolman_to_fdb + dataclasses into core/planner.py so both wizard_execute and plan_dry_run share the same planner code (preview ≡ execute by construction). Add core/dryrun.py::plan_dry_run which composes three layers: 1. Already-linked pair diff via run_sync_cycle(dry_run=True) for steady-state update/conflict/skipped entries. 2. Cross-ref orphans (SM spools with filamentdb_spool_id extra but no SpoolMapping row — the "167") bucketed as "update (re-link from existing cross-ref)" instead of being silently dropped. 3. Matcher auto-decisions for unlinked SM filaments: matched→update, unmatched→create, ambiguous→conflict with candidates list. False conflicts generated by run_sync_cycle for unlinked SM spools are filtered before the planner entries are appended. Wire POST /api/sync/dry-run to plan_dry_run; live POST /sync/trigger is untouched. Add SyncPreviewEntry.candidates field for ambiguous FDB IDs. Add wizard note to Dashboard dry-run result. 10 new tests green, 164 total green.
…empties, tare, variants
…onflict Add In generic_container mode, conflict Add doom-looped: every retry 409'd on the already-existing container + variant, recorded 2 failures, and left the conflict open. On a 409 the execute path now looks up the existing FDB filament by the intended name (case-insensitive, tie-break by parentId) and attaches/links to it — zero failures, conflict resolves. Covers all three 409 sites (container pre-pass, Pass 1 master/ungrouped, Pass 2 variants); a genuinely-new record still creates fresh; a true collision with no name match still fails cleanly.
… tested-version docs Filament DB >= 1.39.0 can require a Bearer API key. The bridge now reads FILAMENTDB_API_KEY and sends 'Authorization: Bearer <key>' on every Filament DB request when set (empty = unauthenticated, unchanged default). README/CLAUDE/configuration updated; latest tested upstreams noted as FDB 1.42.0 / Spoolman 0.23.1 (verified the 1.42 auto-spool-on-create does not affect the bridge — it never sends filament-level totalWeight).
…t by exact canonical name
…entdb; annotate variant matched rows with parent name
…env var, seed, parser, UI, docs, tests)
…enPrintTag extras from Spoolman filaments
Runs on push to dev/main, PRs to main, and weekly. Adds security + quality static analysis; results surface in the Security tab. Wired as a required status check on main alongside the existing CI jobs.
v3 deprecates Dec 2026; v4 avoids the upcoming Node 20 runner sunset.
- backend/app/__init__.py bumped to 0.2.0 - CHANGELOG: rolled [Unreleased] -> [0.2.0] — 2026-06-15; seeded fresh [Unreleased]; added compare/tag links - README: version badge 0.2.0, new What's New section, refreshed Changelog section - docs/prd.md: FR-20/21/22 version annotations 0.1.0 -> 0.2.0 (status preserved) - standards.md: corrected canonical version reference to 0.2.0
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
There was a problem hiding this comment.
CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
added 4 commits
June 15, 2026 04:30
The standard added required test + SAST checks (homelab-configs bcc5896). This repo already implements both — pytest promoted to a required check and the CodeQL workflow. Updates the standards.md status note (8 required branch-protection contexts) and the CLAUDE.md snippet source pin.
The test hardcoded "0.1.0" and broke on the 0.2.0 bump. Assert against app.__version__ so the version-endpoint test is bump-proof. (Surfaced by Test becoming a required check.)
The catch-all SPA route joined the raw URL path to the static dir and served any existing file, so a crafted path (e.g. URL-encoded ../) could escape it and read arbitrary files. Resolve the candidate and require is_relative_to(static_root) before serving. Fixes the 2 high-severity CodeQL py/path-injection alerts (CWE-22).
security-and-quality duplicated ruff/tsc style checks (77 low-value notes) and added correctness queries CodeQL can't prove. Narrow CodeQL to security queries; code quality stays owned by the lint job.
added 2 commits
June 15, 2026 04:42
Add core/log_safe.scrub() — flattens CR/LF and control chars — and apply it to the exception/upstream-response values logged in the opentag-ignore and conflict apply/import handlers. Closes the 5 medium CodeQL py/log-injection alerts (CWE-117). Adds test_log_safe.py.
Add a Security subsection to [0.2.0]: SPA path-traversal confinement, log-injection sanitization, and CodeQL code scanning as a required check.
Functionally equivalent, but in forms CodeQL models as barriers: - SPA fallback uses os.path.realpath + a static-root prefix check (instead of Path.is_relative_to, which CodeQL doesn't model) to confine served files. - log_safe.scrub ends with the CR/LF-removing str.replace as the final transformation so it's recognized as a log-injection sanitizer.
A job skipped via job-level `if:` posts a 'skipped' check for the required 'CI / Image build' context, which branch protection treats as unsatisfied — leaving every PR stuck 'blocked' even with a green rollup. Run the job on all events (so the required check reports success) and gate just the build steps to pull_request, keeping push events build-free.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
[0.2.0] — 2026-06-15
Added
Minimum upstream version enforcement — the bridge now declares minimum supported
versions (Filament DB 1.33.0, Spoolman 0.22.0) in
core/version.py. When a knownupstream version is below its minimum, all sync is hard-blocked: the sync trigger /
dry-run endpoints and the wizard execute return
409 upstream_version_unsupported("Sync disabled — upgrade …"), auto-sync cannot be enabled, and
run_sync_cycleskips thecycle (so the scheduler becomes a no-op).
GET /api/sync/statusreportssync_blocked+sync_blocked_reasons, the Dashboard shows a red "Sync disabled" banner and disables thesync buttons, and
/api/healthwarns per system. An unknown/unreadable version does notblock (that's a connectivity concern). Minimums are documented in the README Prerequisites.
Expandable Synced Records rows — each row in Synced Records now expands (collapsed by
default; with Expand-all / Collapse-all) to a compact side-by-side detail showing the
per-side last-known values for the things the bridge syncs — Spoolman (emerald) vs
Filament DB (blue): weight (net/gross), bed/nozzle temp, cost, plus material/density/
diameter/color as Spoolman context. Values come from the stored snapshots (no extra upstream
fetch); the
/api/mappingsresponse gained an optionaldetailarray.Seeded OpenTag matcher defaults on new installs — fresh installs now seed
opentag_vendor_aliases(prusa=prusament, polyterra=polymaker) andopentag_color_keywords(galaxy=black, cool=grey, jet=black) so the Settingsfields start with real, editable defaults instead of greyed-out examples. Seeding
uses
on_conflict_do_nothing, so existing installs are never clobbered — anupgrade keeps whatever value the user already had (including an intentionally blank one).
Light/dark/system theme — the UI now supports three color modes: Light, Dark, and System
(tracks OS preference; default). Choice persists in
localStorage(fb_theme). A pre-paintinline script in
index.htmlapplies the theme class before React loads, preventing anywhite flash. An "Appearance" section at the top of Settings exposes the three-way segmented
control; a compact three-button toggle (☀/⊙/☾) also appears at the bottom of the sidebar.
Dark styling covers the shared chrome (Layout, sidebar, modals), all primary pages
(Dashboard, Conflicts, SyncedRecords, SyncLog, Settings, Login, OpenTagCleanup) and Wizard
steps 1, 2, 3, 5 (Variances), and 6. Inner sub-components of the remaining large Wizard step
(StepNPreview) are partially polished and will be completed incrementally. A
gray-750offsetsurface color was added to the Tailwind theme for raised dark header rows (table column headers
and group headers).
Version badge + GitHub update check + release-notes popup — the sidebar now
shows the current version (with a
-dev+<sha>suffix on dev builds) linking toits GitHub release. When a newer release is detected on GitHub (checked server-side,
cached 6 hours), an "↑ vX.Y.Z" pill appears; a one-time modal pops up with the
release notes and a link to the full release. Dev/channel builds suppress the update
nag. New env vars
BRIDGE_CHANNEL(defaultrelease) andBRIDGE_COMMIT(default empty) are baked in at image build time. New public endpoint
GET /api/version.New Dockerfile build args
BUILD_CHANNEL/GIT_COMMIT.Single-account auth + API token — the bridge is now protected by optional password
authentication (default enabled,
AUTH_ENABLEDenv var). First visit shows a setup screento set the admin password; subsequent visits show a login form. Sessions use a stateless
signed
fb_sessionhttpOnly cookie (itsdangerous, 30-day max-age). An optional single APItoken (enable/disable + regenerate in Settings → Security) allows machine access via
Authorization: BearerorX-API-Key. All/api/*routes except/api/health,/api/auth/status,/api/auth/login, and/api/auth/setuprequire authentication.Set
AUTH_ENABLED=falseto bypass auth entirely (for locked-out recovery — seedocs/security.mdfor the procedure). New runtime settings:api_token(read-only display),api_token_enabled. New env var:AUTH_ENABLED(bool, defaulttrue).First-login required-settings gate — after authentication (or on every load when auth is
disabled), the UI checks
required_settings_unsetin the config response. When non-empty (e.g.variant_parent_modeis still"unset"), a dismissible modal prompts the user to visit Settingsand configure the listed items before using the bridge.
Configurable container-parent marker (
container_parent_marker) — runtime-editable string(env
CONTAINER_PARENT_MARKER, default"(Master)") appended to generic-container parent namesso they visually separate from their color-variant children. An empty string disables the suffix.
Wired through
BridgeConfig,ConfigResponse/ConfigUpdateRequest, and the Settings UI(inside the Variant parent mode section, shown only when
generic_containeris selected):a checkbox "Append a marker to container parent names" controls on/off; when checked, a text
input pre-filled with
(Master)lets the user customise the marker. Changing the marker doesnot rename existing containers; the re-run resilient-409 backstop handles any resulting collision.
Editable container-name override at Preview — when a proposed generic-container name collides
with an existing Filament DB record (or another container in the same batch), the Preview step
now renders an editable text box pre-filled with the proposed name and a "Skip cluster" control.
The override persists as
wizard_container_name_overridesinBridgeConfig; execute reads itand uses the user-chosen name (or suppresses the entire cluster on skip). Skipping a cluster
also suppresses all its member filaments in both Pass 1 and Pass 2 of execute so no orphan
records are created. Works regardless of marker setting: an empty-marker collision surfaces the
same rename/skip UI.
"Master / Parent" badge for synthetic container parents in wizard Matches step — bridge-owned
FDB container parents (created by
generic_containermode) previously showed as "Unmatched (FDB)"(alarming, actionable). They now render as a distinct purple "Master / Parent" badge, are excluded
from the "unmatched" counter, and offer no skip/link actions. Detection order:
FilamentMappingwith
is_synthetic_parent=True(authoritative), thenhasVariants=True(fallback fornon-bridge parents), then name-suffix heuristic.
OpenTag color-words map (
OPENTAG_COLOR_KEYWORDS) — new runtime-editable setting that mapscolor/marketing words to canonical base colors (e.g.
galaxy=black,cool=grey,jet=black).The OpenTag matcher uses the map to award base-color credit when both the Spoolman name and the
OpenTag name reduce to the same base color even though the token sets are disjoint ("Jet Black"
and "Galaxy Black" both → "black"). The seed map is in
core/opentag_match.py:DEFAULT_COLOR_KEYWORDSand covers base colors, lightness modifiers, and common marketing names. User entries are merged
on top. Configurable via the new "Color word mappings" field in Settings.
OpenTag unmatched section enriched — each row in the OpenTag Cleanup unmatched section now
shows: color swatch, material badge, Spoolman deep link (
DeepLinks), confidence badge, a red"No manufacturer" error badge when the vendor is missing, and the
no_match_reasonstring sousers understand why the filament didn't match.
Spool age preserved on import — when the bridge creates a spool in Filament DB it now sets
purchaseDatefrom Spoolman'sregistereddate andopenedDatefrom Spoolman'sfirst_useddate (both truncated to date-only to match Filament DB's field format). Applies to both the
Bulk Import Wizard and ongoing new-spool sync, so a roll's age isn't lost moving to Filament DB.
Wizard top action bar — primary Back/Next/Save action buttons now appear at both the top
and bottom of each long wizard step (Matches, Variances, Preview) so users don't have to scroll
to the bottom to proceed.
Variances sort control — segmented Brand A→Z / Material A→Z sort buttons above the
auto-groups, standalone, and manually-grouped sections in the Variances step.
OpenTag "Reprocess records" button — new button on the OpenTag Cleanup dataset-status banner
re-scans Spoolman and recomputes matches against the current cached dataset without re-downloading
it; useful for iterating after correcting Spoolman names.
OpenTag SM filament deep link — SM filament ID in the OpenTag Cleanup card header is now a
clickable
DeepLinkscomponent (links to{spoolmanUrl}/filament/show/{id}).OpenTag 10-candidate dropdown — raised the alternate-candidate cap from 5 to 10 so the
candidate selector shows up to 10 choices.
Actionable name-collision rows in Preview — each collision entry now shows an explanatory
warning and a "Fix variant mapping" button that navigates back to the Variances step.
Vendor in planned-writes spool rows — spool rows in the Preview planned-writes list now
include the vendor/manufacturer name in the label.
Settings pinned to sidebar bottom — Settings link is now visually separated at the bottom
of the sidebar navigation.
Generic container parent mode
variant_parent_modesetting (unset/promote_color/
generic_container) for the Bulk Import Wizard (Spoolman → Filament DB). Ingeneric_containermode the wizard synthesises a colorless, bridge-owned FDB container parentfor every cluster (including single-color clusters); every imported color becomes a child
variant. The container carries the finish tags (Silk / Matte / CF / …) shared by the whole
cluster so the line reads as e.g. "PLA Silk" and variants inherit them. The container has no
Spoolman counterpart and never participates in sync. The wizard is gated on a chosen mode
(no silent default). See
docs/variant-parent-mode.md.or browser refresh/close) with unsaved edits now prompts for confirmation; an "Unsaved changes"
indicator appears next to Save. The app router was migrated to a data router
(
createBrowserRouter) to enableuseBlocker.BackupSafetyDialoggates three destructive actions(Wizard Execute, OpenTag Apply, Enable auto-sync): one-click Spoolman backup
(
POST /api/backup/spoolman) and one-click Filament DB backup (GET /api/snapshotproxied to
DATA_DIR/backups/) before proceeding.cache refresh to recover
secondaryColorsmissing from the FDB feed; multicolor-mismatchbadge on cleanup cards when SM is multicolor but the matched OPT entry is single-color.
retention (days) in Settings;
Sync Logpage gains a windowed view (?windows=N= mostrecent N cycle_ids) and a clear-log action (
DELETE /sync-log).idempotent execute); "Never import empties" global setting replaces per-run checkbox.
debug_modeconfig flag gates two destructive endpoints:clear Spoolman FDB cross-ref extras and reset bridge local state (mappings, snapshots,
conflicts, sync log); both visible in a Settings danger zone.
timezone (naive UTC strings get a
Zappended beforetoLocaleString).MappingRowcarriesmulti_color_hexes,remaining_weight,is_empty, andconflict_id; table gains hide-empty toggle,multicolor swatch, conflict deep-link, and empty-state.
match step; filter bar gains tagged-only, hide-matched, and hide-tagged toggles.
direction, fuzzy vendor+name+color match review, variant grouping, field-variances
reconciliation, dry-run preview, and execute. Decision state persists across browser visits.
per-row decision rehydration, and a Rescan action to re-run matching after data corrections.
hierarchies during the wizard; configurable via
VARIANT_LINE_KEYWORDS; supportsper-member move, standalone, and ignore actions; finish-line auto-split.
for already-matched records; picks the winning value and writes back to Spoolman before
execute.
(
SYNC_INTERVAL_SECONDS); all applied changes are written to the audit log.properties, and new-spool creation each have independent direction (
filamentdb_to_spoolman/
spoolman_to_filamentdb/two_way) and conflict policy (manualornewest_wins).All are runtime-editable in Settings without a restart.
prevents duplicate spool creation when both sides add a spool simultaneously.
Filament DB as usage log entries (
POST .../usage) to preserve the audit trail; weightincreases update
totalWeightdirectly.the wizard and in the ongoing sync engine.
gradient fields (hex arrays, arrangement, direction), version-gated to Filament DB ≥ 1.33.0.
sync as the
filamentdb_material_tagsSpoolman extra field (CSV of ints) and back.OpenPrintTag dataset; per-filament candidate picker (best + top-5 alternates); multicolor
and arrangement-aware scoring; group collapse/expand, ignore-all, sort by SM ID; reviewable
Manufacturer field with vendor find-or-create reassignment; applies
openprinttag_slugandopenprinttag_uuidto Spoolman extra fields and stamps both keys into the Filament DBsettings{}bag.them as conflicts for explicit user action.
resolution; conflict cards show snapshot-derived identity context; filter bar by conflict
type.
locationis carried into Filament DBlocationIdduring the initial wizard seed.VARIANT_LINE_KEYWORDSconfig — comma-separated keywords that prevent filamentsmatching different keywords from being grouped together; runtime-editable in Settings.
OPENTAG_VENDOR_ALIASESconfig — maps Spoolman vendor names to OpenPrintTag brandnames for the brand pre-filter; runtime-editable in Settings.
Cleanup, and Settings pages; all record rows include deep links into both upstream systems.
publish, registry retention, and main branch protection.
Changed
resolve-clarity improvements, and multicolor color display; new_spool conflicts labelled
"Dismiss".
policy are Settings-only; wizard Step 2 only persists
import_direction.docker-compose.ymlis bridge-only — full dev stack (Spoolman + Filament DBdocker-compose.dev.yml.PUID:PGIDafterhealing
/dataownership; no staticUSERdirective.direction × conflict-policy model; Settings page exposes all six per-category controls.
removed the legacy "source of truth for new spools" concept.
filamentdb_material_tagsis stored as a CSV string on the Spoolman extra field (Spoolmantext fields do not accept JSON arrays).
/api/opentag/*to/api/openprinttag/*toavoid ad-blocker interference.
Fixed
filament's bed (or nozzle) temperature in Filament DB never propagated to Spoolman even
under two-way sync, because these are native filament fields on both sides
(
temperatures.bed/nozzle↔settings_bed_temp/settings_extruder_temp) and the ongoingfield-mapper only matched Spoolman extra fields. A new
_sync_material_propsengine passsyncs them both directions under the material_properties direction + policy (with per-field
snapshot baselines and conflict queuing, mirroring the cost sync). SM→FDB writes preserve
sibling temperatures via read-modify-write. (The wizard's initial import already handled these
via its own map; only ongoing sync was missing them.)
(e.g. one print) could compound across sync cycles and drive a mapped spool to 0 g,
ping-ponging the value SM↔FDB with a doubling delta. Two bugs: (1)
fdb_to_spoolman_netsubtracted
usageHistoryfromtotalWeight, but Filament DB already reducestotalWeightwhen usage is logged — double-counting every gram; (2) after a weight push only the source
side's snapshot was refreshed, so the propagated change was re-detected as a fresh change on
the other side next cycle. Net is now
totalWeight − tare(no usage subtraction) and bothsnapshots are refreshed to the agreed state after every push, so weight sync converges.
each settings field used
dark:text-gray-500, which was too dark to read on thedark background; bumped to
dark:text-gray-400.message no longer hardcodes "≈11k records". Each successful grab persists the record
count to BridgeConfig (
opentag_last_count);GET /api/openprinttag/statusnow returnsa
last_countfield (the largest count ever seen, surviving cache-file deletion) and theUI shows e.g. "12,104+ records". Falls back to "thousands of records" before the first grab.
normalize_vendor()now replaces hyphens and underscoreswith spaces before collapsing whitespace. This fixes a 0% / unmatched result for Spoolman vendor
"VOXEL-pla"against OpenTag brand"Voxel PLA": both now normalize to"voxel pla"and landin the same brand bucket so scoring can run.
increased from 0.10 → 0.15 (hex is ground truth) and the color-name component was split into
0.25 token-similarity + 0.05 base-color bonus (via the new color-words map). "PLA Jet Black"
(#222F2E) now surfaces
prusament-pla-prusa-galaxy-blackabove the 30% threshold._container_display_namenow callsstrip_finish_wordson the rawmaterialfield before composing the container name, so aSpoolman filament with
material = "PLA Silk"produces "ELEGOO PLA Silk (Master)" rather than"ELEGOO PLA Silk Silk (Master)".
(Master)— the parenthesised form visually separatesthe marker from the filament name so "ELEGOO PLA (Master)" is distinct from color children
like "ELEGOO PLA Red". The marker is now user-configurable (see
container_parent_markerabove); an empty marker disables the suffix entirely.
wizard now PATCHes the shared finish tags (Silk / Matte / CF / …) onto the container if any are
missing. Existing unrelated tags are preserved (merge, not clobber).
child filament creation is now caught per-record (not per-batch). The record is marked as
failedwith detail"name collision: <name>"; the rest of the batch continues unaffected.multi_color_directionis now always sent alongsidemulti_color_hexes(completes themulticolor 422 trio;
multi_unknowndefaults to"coaxial").new_spoolconflicts are now deduplicated (no duplicate row each cycle) andauto-resolved when the spool becomes mapped.
filamentdb_idcross-reference beforefuzzy matching, making re-runs idempotent.
startup.
"Green/Purple" →
{green, purple}).create_spoolto be skipped when the target spoolalready exists with a different xref; spoolWeight is resolved from the tare before use.
netFilamentWeightis now set on Filament DB filament create so the spool percentage barrenders correctly.
_idis extracted from thecreate_spoolresponse by label match rather thanassuming a fixed position.
when spoolWeight differs between variants.
longer triggers a false collision).
ensure_extra_fieldsis resilient to partialfield sets.
color_hexandmulti_color_hexesto Spoolman simultaneously (wastriggering a 422).
multi_color_directionis not sent to Spoolman without accompanying hex values.#when writing to Filament DB.filamentdb_material_tagsCSV extra field value is no longer included in the genericfield-rows display (preventing duplicate display).
respected; import guards prevent writes in the wrong direction.
does not return 404.
Security
resolved the request path against the static directory and served any file that existed,
so a crafted path (e.g. URL-encoded
../) could escape it. The resolved path is nowrequired to stay within the static root before it is served (path-traversal / CWE-22).
bodies logged by the OpenTag-ignore and conflict apply/import handlers are now flattened
(CR/LF and control chars stripped via
core/log_safe.scrub) so they cannot forge orinject extra log lines (CWE-117).
required check on
main.