Skip to content

Release v0.2.0#1

Merged
crzykidd merged 204 commits into
mainfrom
dev
Jun 15, 2026
Merged

Release v0.2.0#1
crzykidd merged 204 commits into
mainfrom
dev

Conversation

@crzykidd

@crzykidd crzykidd commented Jun 15, 2026

Copy link
Copy Markdown
Owner

[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 known
    upstream 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_cycle skips the
    cycle (so the scheduler becomes a no-op). GET /api/sync/status reports sync_blocked +
    sync_blocked_reasons, the Dashboard shows a red "Sync disabled" banner and disables the
    sync buttons, and /api/health warns per system. An unknown/unreadable version does not
    block (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/mappings response gained an optional detail array.

  • Seeded OpenTag matcher defaults on new installs — fresh installs now seed
    opentag_vendor_aliases (prusa=prusament, polyterra=polymaker) and
    opentag_color_keywords (galaxy=black, cool=grey, jet=black) so the Settings
    fields start with real, editable defaults instead of greyed-out examples. Seeding
    uses on_conflict_do_nothing, so existing installs are never clobbered — an
    upgrade 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-paint
    inline script in index.html applies the theme class before React loads, preventing any
    white 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-750 offset
    surface 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 to
    its 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 (default release) and BRIDGE_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_ENABLED env var). First visit shows a setup screen
    to set the admin password; subsequent visits show a login form. Sessions use a stateless
    signed fb_session httpOnly cookie (itsdangerous, 30-day max-age). An optional single API
    token (enable/disable + regenerate in Settings → Security) allows machine access via
    Authorization: Bearer or X-API-Key. All /api/* routes except /api/health,
    /api/auth/status, /api/auth/login, and /api/auth/setup require authentication.
    Set AUTH_ENABLED=false to bypass auth entirely (for locked-out recovery — see
    docs/security.md for the procedure). New runtime settings: api_token (read-only display),
    api_token_enabled. New env var: AUTH_ENABLED (bool, default true).

  • First-login required-settings gate — after authentication (or on every load when auth is
    disabled), the UI checks required_settings_unset in the config response. When non-empty (e.g.
    variant_parent_mode is still "unset"), a dismissible modal prompts the user to visit Settings
    and 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 names
    so 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_container is 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 does
    not 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_overrides in BridgeConfig; execute reads it
    and 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_container mode) 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: FilamentMapping
    with is_synthetic_parent=True (authoritative), then hasVariants=True (fallback for
    non-bridge parents), then name-suffix heuristic.

  • OpenTag color-words map (OPENTAG_COLOR_KEYWORDS) — new runtime-editable setting that maps
    color/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_KEYWORDS
    and 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_reason string so
    users understand why the filament didn't match.

  • Spool age preserved on import — when the bridge creates a spool in Filament DB it now sets
    purchaseDate from Spoolman's registered date and openedDate from Spoolman's first_used
    date (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 DeepLinks component (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

  • Generic container parent mode — new variant_parent_mode setting (unset / promote_color
    / generic_container) for the Bulk Import Wizard (Spoolman → Filament DB). In
    generic_container mode the wizard synthesises a colorless, bridge-owned FDB container parent
    for 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.
  • Unsaved-changes guard on Settings — navigating away from the Settings page (in-app nav
    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 enable useBlocker.
  • Pre-write backup safeguardBackupSafetyDialog gates 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/snapshot
    proxied to DATA_DIR/backups/) before proceeding.
  • OpenTag secondary-colors recovery — fetches the raw OpenPrintTag tarball on each
    cache refresh to recover secondaryColors missing from the FDB feed; multicolor-mismatch
    badge on cleanup cards when SM is multicolor but the matched OPT entry is single-color.
  • Scheduler & Logs settings — runtime-editable sync interval (minutes) and sync-log
    retention (days) in Settings; Sync Log page gains a windowed view (?windows=N = most
    recent N cycle_ids) and a clear-log action (DELETE /sync-log).
  • Bulk Import Wizard — wizard renamed from "Initial Sync Wizard" (re-runnable;
    idempotent execute); "Never import empties" global setting replaces per-run checkbox.
  • Debug mode + reset toolsdebug_mode config 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.
  • Browser-local timestamps — all timestamps in the UI render in the browser's local
    timezone (naive UTC strings get a Z appended before toLocaleString).
  • Synced Records enrichmentMappingRow carries multi_color_hexes,
    remaining_weight, is_empty, and conflict_id; table gains hide-empty toggle,
    multicolor swatch, conflict deep-link, and empty-state.
  • Wizard OPT badge + filters — OpenPrintTag-tagged filaments show an OPT badge in the
    match step; filter bar gains tagged-only, hide-matched, and hide-tagged toggles.
  • Guided initial-sync wizard — multi-step wizard covering connectivity check, import
    direction, fuzzy vendor+name+color match review, variant grouping, field-variances
    reconciliation, dry-run preview, and execute. Decision state persists across browser visits.
  • Match review v2 — unified group-by / sort / per-column-filter table with bulk select,
    per-row decision rehydration, and a Rescan action to re-run matching after data corrections.
  • Variant-grouping step — groups flat Spoolman filaments into Filament DB parent/variant
    hierarchies during the wizard; configurable via VARIANT_LINE_KEYWORDS; supports
    per-member move, standalone, and ignore actions; finish-line auto-split.
  • Variances step — per-field reconcile of differences between Spoolman and Filament DB
    for already-matched records; picks the winning value and writes back to Spoolman before
    execute.
  • Continuous sync engine — snapshot / diff / apply loop on a configurable interval
    (SYNC_INTERVAL_SECONDS); all applied changes are written to the audit log.
  • Per-category sync direction + conflict policy (two-axis model) — weight, material
    properties, and new-spool creation each have independent direction (filamentdb_to_spoolman
    / spoolman_to_filamentdb / two_way) and conflict policy (manual or newest_wins).
    All are runtime-editable in Settings without a restart.
  • Enforced new-spool direction — new-spool creation honors the configured direction;
    prevents duplicate spool creation when both sides add a spool simultaneously.
  • Net ↔ gross weight-model translation — Spoolman weight decrements are forwarded to
    Filament DB as usage log entries (POST .../usage) to preserve the audit trail; weight
    increases update totalWeight directly.
  • Filament cost sync — spool price syncs bidirectionally (spool-price-first); handled in
    the wizard and in the ongoing sync engine.
  • Structured multicolor/gradient sync — bidirectional sync of FDB multi-color and
    gradient fields (hex arrays, arrangement, direction), version-gated to Filament DB ≥ 1.33.0.
  • Material-finish tag round-trip — OpenPrintTag finish-tag IDs (matte, silk, satin, etc.)
    sync as the filamentdb_material_tags Spoolman extra field (CSV of ints) and back.
  • OpenTag (OpenPrintTag) cleanup tool — matches Spoolman filaments against the
    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_slug and
    openprinttag_uuid to Spoolman extra fields and stamps both keys into the Filament DB
    settings{} bag.
  • Upstream-deletion detection — detects records soft-deleted in either system and queues
    them as conflicts for explicit user action.
  • Conflict queue — all conflicts (field-level and deletion) are queued for manual
    resolution; conflict cards show snapshot-derived identity context; filter bar by conflict
    type.
  • Spool location carry-over — Spoolman spool location is carried into Filament DB
    locationId during the initial wizard seed.
  • VARIANT_LINE_KEYWORDS config — comma-separated keywords that prevent filaments
    matching different keywords from being grouped together; runtime-editable in Settings.
  • OPENTAG_VENDOR_ALIASES config — maps Spoolman vendor names to OpenPrintTag brand
    names for the brand pre-filter; runtime-editable in Settings.
  • Web UI — React SPA with Dashboard, Synced Records, Conflicts, Sync Log, OpenTag
    Cleanup, and Settings pages; all record rows include deep links into both upstream systems.
  • CI / publish matrix — GitHub Actions wires lint, test, multi-arch Docker build, GHCR
    publish, registry retention, and main branch protection.

Changed

  • Conflicts page rework — collapsible conflict rows, sort controls, expand-all,
    resolve-clarity improvements, and multicolor color display; new_spool conflicts labelled
    "Dismiss".
  • Ongoing source-of-truth removed from wizard Step 2 — sync direction and conflict
    policy are Settings-only; wizard Step 2 only persists import_direction.
  • Standard docker-compose.yml is bridge-only — full dev stack (Spoolman + Filament DB
    • Mongo + bridge build-from-source) moved to docker-compose.dev.yml.
  • Container runs non-root 1000:1000 — entrypoint chown+gosu drops to PUID:PGID after
    healing /data ownership; no static USER directive.
  • Replaced the single source-of-truth model with the two-axis
    direction × conflict-policy model; Settings page exposes all six per-category controls.
  • Enforced new-spool sync direction is now a first-class setting written by the wizard;
    removed the legacy "source of truth for new spools" concept.
  • filamentdb_material_tags is stored as a CSV string on the Spoolman extra field (Spoolman
    text fields do not accept JSON arrays).
  • OpenTag/OpenPrintTag API routes renamed from /api/opentag/* to /api/openprinttag/* to
    avoid ad-blocker interference.

Fixed

  • Bed / nozzle temperature now sync between Filament DB and Spoolman — editing a
    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/nozzlesettings_bed_temp/settings_extruder_temp) and the ongoing
    field-mapper only matched Spoolman extra fields. A new _sync_material_props engine pass
    syncs 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.)
  • Runaway weight-decrement loop in two-way sync — a single Spoolman-side decrement
    (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_net
    subtracted usageHistory from totalWeight, but Filament DB already reduces totalWeight
    when 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 both
    snapshots are refreshed to the agreed state after every push, so weight sync converges.
  • Settings helper-text contrast in dark mode — the small descriptive text under
    each settings field used dark:text-gray-500, which was too dark to read on the
    dark background; bumped to dark:text-gray-400.
  • OpenTag download hint tracks dataset size — the "first load downloads…" status
    message no longer hardcodes "≈11k records". Each successful grab persists the record
    count to BridgeConfig (opentag_last_count); GET /api/openprinttag/status now returns
    a last_count field (the largest count ever seen, surviving cache-file deletion) and the
    UI shows e.g. "12,104+ records". Falls back to "thousands of records" before the first grab.
  • OpenTag VOXEL-pla brand gatenormalize_vendor() now replaces hyphens and underscores
    with 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 land
    in the same brand bucket so scoring can run.
  • OpenTag color scoring — "Jet Black" matches "Galaxy Black" — the hex proximity weight was
    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-black above the 30% threshold.
  • P0.1 Double finish word in container name_container_display_name now calls
    strip_finish_words on the raw material field before composing the container name, so a
    Spoolman filament with material = "PLA Silk" produces "ELEGOO PLA Silk (Master)" rather than
    "ELEGOO PLA Silk Silk (Master)".
  • P0.2 Container marker changed to (Master) — the parenthesised form visually separates
    the 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_marker
    above); an empty marker disables the suffix entirely.
  • P0.3 optTags on container reuse — when a pre-existing container is reused on re-run, the
    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).
  • P1.1 Resilient 409 on filament create — a 409 Conflict from Filament DB during container or
    child filament creation is now caught per-record (not per-batch). The record is marked as
    failed with detail "name collision: <name>"; the rest of the batch continues unaffected.
  • multi_color_direction is now always sent alongside multi_color_hexes (completes the
    multicolor 422 trio; multi_unknown defaults to "coaxial").
  • new_spool conflicts are now deduplicated (no duplicate row each cycle) and
    auto-resolved when the spool becomes mapped.
  • Wizard pre-matches already-linked records via filamentdb_id cross-reference before
    fuzzy matching, making re-runs idempotent.
  • Readonly-DB crash on a root-owned volume is self-healed by the entrypoint chown before
    startup.
  • OpenTag color-name tokenization now splits on non-alphanumeric characters (fixes
    "Green/Purple" → {green, purple}).
  • All backend ruff lint errors resolved (74 → 0).
  • Stale cross-reference no longer causes create_spool to be skipped when the target spool
    already exists with a different xref; spoolWeight is resolved from the tare before use.
  • netFilamentWeight is now set on Filament DB filament create so the spool percentage bar
    renders correctly.
  • Spool _id is extracted from the create_spool response by label match rather than
    assuming a fixed position.
  • Tare weight is excluded from variant-property conflict detection to prevent false conflicts
    when spoolWeight differs between variants.
  • Wizard name-collision detection is now vendor-aware (same name under different vendors no
    longer triggers a false collision).
  • OpenTag apply self-heals missing extra fields; ensure_extra_fields is resilient to partial
    field sets.
  • Never send both color_hex and multi_color_hexes to Spoolman simultaneously (was
    triggering a 422).
  • multi_color_direction is not sent to Spoolman without accompanying hex values.
  • Hex color values are prefixed with # when writing to Filament DB.
  • filamentdb_material_tags CSV extra field value is no longer included in the generic
    field-rows display (preventing duplicate display).
  • OpenTag matcher uses color name as the primary discriminator within a brand+material bucket.
  • OpenTag matcher correctly gates on polymer family and applies finish-aware scoring.
  • One-way sync correctness: Spoolman PATCH is used (not PUT); weight precision setting
    respected; import guards prevent writes in the wrong direction.
  • docker-compose made fully deployable; SPA route fallback added so browser-side navigation
    does not return 404.

Security

  • SPA static-file route confined to the static root — the catch-all frontend route
    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 now
    required to stay within the static root before it is served (path-traversal / CWE-22).
  • Untrusted values sanitized before logging — exception text and upstream API response
    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 or
    inject extra log lines (CWE-117).
  • CodeQL code scanning added to CI (security-extended suite, Python + JS/TS) as a
    required check on main.

crzykidd added 30 commits May 28, 2026 21:24
…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)
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.
crzykidd and others added 15 commits June 13, 2026 16:51
…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).
…entdb; annotate variant matched rows with parent name
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
@github-advanced-security

Copy link
Copy Markdown

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:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

Comment thread frontend/src/pages/Settings.test.tsx Fixed
Comment thread frontend/src/pages/Wizard/StepNPreview.test.tsx Fixed

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

Managed by Ansible 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.
Comment thread backend/app/main.py Fixed
Comment thread backend/app/main.py Fixed
Comment thread backend/app/main.py Dismissed
Comment thread backend/app/api/conflicts.py Fixed
Comment thread backend/app/api/conflicts.py Fixed
Comment thread backend/app/api/opentag.py Fixed
Comment thread backend/app/api/opentag.py Fixed
Comment thread backend/app/api/opentag.py Fixed
Managed by Ansible 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.
Comment thread backend/app/api/conflicts.py Dismissed
Comment thread backend/app/api/conflicts.py Dismissed
Comment thread backend/app/api/opentag.py Dismissed
Comment thread backend/app/api/opentag.py Dismissed
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.
Comment thread backend/app/main.py Dismissed
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.
@crzykidd crzykidd merged commit c1fa268 into main Jun 15, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants