Commit Graph
48 Commits
Author SHA1 Message Date
DumiFlex e78592839f feat(spa): "Allow repeats" toggle for the wildcard pool multi-pick
Exposes the pick_independent instance field (engine resolution landed in 33b3600): instance schema + snapshot round-trip, TS instance type + _shell INSTANCE_FIELDS_PER_KIND registry (so reset-overrides + modified-state + the TS/Py parity gate all include it) + preview cache-key, and the canvas PoolSection 'Allow repeats' toggle (shown only while multi-pick is active; off = unique). Instance-only (per-node, canvas runtime), additive + tolerant — no community catalog change.
2026-06-10 00:09:10 +03:00
DumiFlex 94ccd7f192 test(wp_api): lock comma rejection on variable names server-side
The engine handlers already enforce _IDENT_RE on output_var / var_binding
/ fixed values[].name via validate_payload, wired into every
create/update/import through _validate_payload_for_type (returns 400 on
ValueError). Existing tests cover leading-digit / dunder / too-long /
empty cases; add the explicit comma scenario the user reported so the
exact "no settle-delimiter in a var name" contract is regression-proof
for all three module types. No production change — server gate already
existed; this characterizes + locks it.
2026-06-09 15:39:16 +03:00
DumiFlex d3881c84a1 fix(snapshot): include SP2a pick fields in instance baseline + snapshot type 2026-06-08 22:11:11 +03:00
DumiFlex 6703583db8 fix(preview): serialize list-valued vars in resolve endpoint 2026-06-08 21:11:59 +03:00
DumiFlex 39529a669b feat(engine): migrate library v1->v2 (multi-tag) + bump schema version
engine/migrations/v1_to_v2.py: option sub_category->sub_categories[]; sub-category name slugify (whitespace/grammar chars->_, reserved suffixed, de-duped) cascaded to options + tag_groups; instance category_filter list->boolean-expr string + exclude_null flag; nested @{uuid:a,b,null} refs->@{uuid:a or b!null}. Idempotent. Two entry points share one core: envelope migrate_v1_to_v2 (import/export, registered in _CHAIN) + per-row migrate_module/bundle_v1_to_v2 (lazy/bulk DB path).

Register _CHAIN[1] + bump engine CURRENT_SCHEMA_VERSION 1->2. New SQL migration 016 marches stale DB rows 1->2 at boot via run_bulk_lazy + the per-row migrators (014's documented bump pattern). TS mirror (migrations.ts) + client CURRENT bump follow next (no cross-language version assertion couples them). Update version/entity-count assertions in payload-migration, migration-runner, and export-build tests.
2026-06-07 01:40:53 +03:00
DumiFlex c13351d21c feat(context): type-aware drift/missing + id-clash state via classifyOne
Workflow drift/missing now runs through the shared classifyOne verdict: a same-id, DIFFERENT-kind library row surfaces as a distinct 'clash' state (violet badge) instead of a misleading drift/missing dot, and Refresh is suppressed + guarded in mergeRefresh so it can never clobber an unrelated item. Hybrid content-key: the workflow path compares payload_hash (drift semantics unchanged) while the import picker keeps fingerprint; classifyOne is content-key agnostic. /wp/api/modules/hashes now returns {type, payload_hash} per id; drift-store map + AppLayout fingerprint + setLibraryHash updated to the object shape.
2026-06-06 16:39:49 +03:00
DumiFlex 885f7fb98a feat(api): modules/hashes returns type + fingerprint per id 2026-06-06 16:10:33 +03:00
DumiFlex 710205768b feat(manager): detect server restart and prompt stale page reload 2026-06-03 03:24:26 +03:00
DumiFlex 04d3795103 feat(api): expose db config get/put endpoints with pending-move 2026-06-03 02:16:51 +03:00
DumiFlex c03fc070ba feat(engine): honor sidecar preference and add root db location 2026-06-03 01:54:05 +03:00
DumiFlex 0909817018 feat(api): add POST /wp/api/database/maintenance 2026-06-03 00:19:59 +03:00
DumiFlex dc839bfb99 feat(api): add GET /wp/api/database/info 2026-06-03 00:15:04 +03:00
DumiFlex b7d672396a feat(import-export): add templates bucket to engine exporter + importer 2026-05-28 00:21:51 +03:00
DumiFlex 65080db689 feat(templates): add prompt template table, repository + REST API
Introduces a persisted prompt-template library: migration 012 creates the
templates table (name/description/category_id FK/tags/is_favorite/
template_string), TemplateRepository provides CRUD with partial-update
sentinel semantics, and wp_api/templates.py exposes list/get/create/update/
delete/favorite with FK violations mapped to 400. Bumps the migration-head
version assertions to 12.
2026-05-27 00:25:52 +03:00
DumiFlex 4a69c5e212 fix: cross-node constraint drag + scanner slot allocator + library fallback + save hydrate
- Cross-node drag MOVE-becomes-COPY race: ContextWidget.onDrop sync-claims
  consumedBy before any await so the source widget's dragend reads the
  set value. dragend filters by _uid (idempotent) instead of stale
  srcIdx — fixes the secondary issue where the backup pendingHandoffs
  queue + dragend double-removed an adjacent target wildcard.
- pendingHandoffs queue in drag-store: belt-and-suspenders source-side
  cleanup that runs even when dragend skips (constraint rows inside the
  bundle frame were the trigger).
- Conflict scanner constraint_orphan_target slot allocator: per-target
  sorted local positions + remaining downstream count. Each constraint
  claims smallest local slot > own index, else a downstream slot. Fixes
  duplicated constraints with target downstream cross-node + correctly
  flags when only one local target sits BEFORE the constraint.
- collectDownstreamWildcardUuids: per-instance occurrences (not Set-
  deduped) so duplicated downstream targets each count as a separate
  claimable slot.
- RichTextPreview + ConstraintInstanceModal: cross-Context refs fall
  back to preview-resolver cache (sub_categories, option values,
  options_by_id, hasNullOption) so the canvas modal renders the actual
  wildcard name, sub-cat list, and chip suffix instead of "source"/
  "target" placeholders + stale matrix keys.
- Preview-resolver schema extended with subCategories / optionValues /
  optionsById / hasNullOption populated from embed-bundle response.
- Constraint save hydrate: wp_api modules + bundles endpoints fill
  exception source_value / target_value from source_id / target_id by
  looking up the wildcard's option list before validation. Heals
  library rows that stored only ids.
- TestRunner constraint table uses RichTextPreview for column headers
  + row values so nested @{uuid} refs render as chips.
- Inverted null filter UI: SubcategoryFilterPicker "Exclude null"
  checkbox + RefChip "!null" suffix render.
- Bundle size budget bumped 330 KB to 340 KB.
- Drop stale CHECKPOINT-*.md docs.
2026-05-25 00:17:20 +03:00
DumiFlex 93e644d466 feat(repo): backfill 8-hex ids for wildcard options on create/update 2026-05-24 06:23:43 +03:00
DumiFlex 331062c0ee feat(wp_api): POST /wp/api/cascade/apply + /wp/api/cascade/undo endpoints 2026-05-24 03:06:25 +03:00
DumiFlex 16d6f8f72f refactor: drop legacy import/export + rename v2 to plain
Removes the v1 picker UI that lived side-by-side with the new
7-bucket picker/modal pipeline. Renames every `*V2*` / `*-v2*` symbol
to its plain form now that there is only one path.

Frontend:
- ImportExport.vue 2066 → 757 lines. Drops Mode "export"/"import"
  variants + their state, helpers, panes. Default mode is the picker
  flow.
- Deletes utils/bundleSelection.ts (only legacy consumed it) + its test.
- Deletes __tests__/ImportExport.test.ts (only exercised the legacy
  template).
- Drops api.exportBundle + api.importBundle from client.ts + their
  types (ImportBundle, ImportResult) from types.ts. Their tests in
  api-client.test.ts go with them.
- Renames inside ImportExport.vue: importV2State → importState,
  importV2Selection → importSelection, importV2Committing →
  importCommitting, clearImportV2 → clearImport,
  onImportV2SelectionReady → onImportSelectionReady,
  onImportV2PayloadReady → onImportPayloadReady. Same on CSS classes
  (wp-io-export-v2-pane → wp-io-export-pane, etc.) and data-test ids
  (io-tab-export-v2 → io-tab-export, io-export-v2-pane → io-export-pane,
  ...). Tab labels relabeled to plain "Export" / "Import".
- Same v2 → plain pass on inner components (ExportTab, ImportTab,
  ImportPicker, ConflictModal docstrings + test ids).

Backend:
- wp_api/import_export.py drops export_bundle + import_bundle handlers
  and the GET /wp/api/export + POST /wp/api/import routes.
- tests/wp_api/test_import_export.py deleted (covered the legacy
  endpoints exclusively).
- tests/wp_api/test_export_build.py — the
  test_legacy_export_endpoint_still_returns_legacy_shape regression
  case removed.

Verification:
- pnpm typecheck — clean
- pnpm vitest run src/manager — 997/997 pass + 3 pre-existing skips
- pytest tests/engine tests/wp_api — 522/522 pass
- pnpm build:manager — clean; ImportExport chunk dropped from
  84 KB to 65 KB (gzip 23.6 KB → 18.3 KB)
2026-05-24 00:04:35 +03:00
DumiFlex f3e1749914 feat(wp_api): /wp/api/import/commit + /wp/api/import/undo endpoints
Add two new POST handlers in wp_api/import_export.py wrapping the
engine.importer.commit_import / undo_import functions from Task 13.

- /wp/api/import/commit: body {adds, replaces, renames}; 200 with
  {ok, undo_entry_id, summary} on success; 400 on contract/DB errors.
- /wp/api/import/undo: body {undo_entry_id}; 200 with {ok: True} on
  success; 404 on unknown entry; 400 on shape/other errors.
- Both guard request body via validate_body_size (5 MB cap).
- Engine returns undo_id; handler renames to undo_entry_id at the
  HTTP boundary to keep the locked endpoint contract decoupled from
  the engine field name.
- Legacy export_bundle + import_bundle + new export_build handlers
  remain unchanged.
2026-05-22 22:21:33 +03:00
DumiFlex c3377d8d09 feat(wp_api): post /wp/api/export/build endpoint
Wraps engine.exporter.build_export_payload behind an HTTP route so the
SPA can drive a 7-bucket export with picker-selected UUID sets. Body
shape uses 7 keys (bundle_uuids, wildcard_uuids, fixed_values_uuids,
combine_uuids, derivation_uuids, constraint_uuids, category_uuids);
each key is optional and defaults to []. Mis-typed UUIDs are silently
dropped by the exporter (cross-bucket isolation guarantee). Response
is the exporter payload returned directly, matching the legacy
/wp/api/export shape convention.
2026-05-22 20:16:06 +03:00
DumiFlex 3433e93243 test(engine): independent expected fp + name-only update guard 2026-05-22 17:56:14 +03:00
DumiFlex fb4ae039d0 feat(engine): backfill snapshot_fingerprint on module create/update 2026-05-22 17:50:11 +03:00
DumiFlex 6917bd9351 feat(api): bilateral tier-2 integrity — reject bundle-child adds on B when parents reference B
The A-side write check (previous commit) prevents building A → B → C from A's perspective, but B can be updated unilaterally. Without a matching check on B's update, B could gain bundle children of its own and silently demote every parent A's reference into a tier-3 structure on read — the resolver would happily attach the new bundle children, the SPA pre-flatten would see two levels of bundles below A, and the tier-2 ceiling would be effectively gone.

Lock the ceiling from both sides:

- _find_parents_referencing scans the library for any bundle whose children contain a bundle ref pointing at the row being updated.
- _validate_parents_compatible runs only when the patch carries a  key AND that new list contains at least one bundle entry — rename / recolor / leaf-only edits skip it entirely.
- 409 Conflict (not 400) because the failure is a state-vs-write conflict, not malformed input. Error body cites the offending parents (up to 3, then '… N more') so the SPA can route the user to detach references first.

5 new tests cover the 409 path, the no-parents allow path, the leaf-only-edit allow path, the metadata-only allow path, and the multi-parent listing in the error message. Full wp_api suite green (174/174).
2026-05-20 17:31:44 +03:00
DumiFlex e3e74fb919 feat(api): tier-2 bundle nesting via references (not snapshots)
Bundles can now contain other bundles as children — but stored as id-only POINTERS, not deep snapshots. The reference model sidesteps the drift cascade that a snapshot model would introduce (parent goes stale every time the inner bundle is edited).

Storage shape for a bundle child:

    { 'id': <ref-id>, 'type': 'bundle', 'name'?: str, 'color'?: str }

Display fields (name, color) are cached so the parent renders a sensible placeholder if the referenced bundle is later deleted; the actual children belong to the referenced row.

Three pieces wired into POST + PUT /wp/api/bundles:

- _normalise_bundle_children strips every field except the reference pointers off bundle-typed children before insert, so a SPA round-tripping the GET-expanded shape doesn't accidentally persist a stale snapshot.
- _validate_bundle_refs rejects three cases against the live repo state: self-include (A → A, PUT only), missing reference (id not in repo), and tier-3 (referenced bundle already contains a bundle child). Tier-2 cap is the architectural ceiling.
- _expand_bundle_children runs on the GET response: every bundle-typed child gets the referenced bundle's CURRENT children attached inline under the same 'children' key, plus _resolved_from / _missing_ref markers. The SPA pre-flatten path (next commit) reads these inline children verbatim — same shape as a snapshot model would have produced, just always fresh.

Headline benefit: an inner-bundle edit shows up in every parent's GET without any propagation pass. No _propagate_bundle_to_bundles, no recursive walk through children blobs. The reference is the source of truth.

11 new tests cover storage stripping, GET expansion, live propagation through reads, tier-2 rejection, missing-ref handling, and mixed leaf/ref children.
2026-05-20 17:24:14 +03:00
DumiFlex 0a8f376f4f fix(api,engine): qa hardening sweep — validators, concurrency, orphans
Lands the 15-bug catalogue from the 2026-05-19 creative-QA pass.

API layer (wp_api/):
- New _validators.py centralises meta + identifier + body-size checks
  so modules and bundles share the same shape rules
- SPA catch-all now returns JSON 404 for unknown /wp/api/* paths
  instead of the SPA shell (debugging hostile previously)
- create/update/import_from_workflow validate meta (name/desc/tags/
  category_id) before hitting the repo; tag list now rejects non-str
  entries (was silently stored, breaking the SPA type contract)
- IntegrityError on dangling category_id surfaces as 400, not 500
- Optional If-Match header on PUT enables opt-in optimistic
  concurrency — stale version → 409 instead of silent overwrite
- Bundle name collisions auto-suffixed with (copy)/(copy N) mirroring
  the modules-side forkModule rule
- Bundle children deduped by id at create + update time
- Deleting a module flags meta.orphaned=true on every bundle child
  whose snapshot referenced it so the SPA can render a stale badge

Engine layer (engine/modules/*_handler.py):
- wildcard: reject empty + __dunder var_binding, cap at 64 chars,
  reject duplicate option ids
- combine: reject empty template, cap template at 8000 chars, cap
  output_var at 64, reject __dunder output_var
- fixed_values: cap value name length, reject __dunder names, reject
  duplicate names across rows (was silent last-write-wins)
- derivation: reject duplicate rule ids (was nondeterministic for
  disabled_rule_ids / rule_order_override lookups)

Tests:
- New test_qa_hardening.py exercises every endpoint-level fix
- Extended test_payload_validation.py parametrised rejection matrix
- New test_spa_fallback.py case for /wp/api/* JSON 404

655 pytest pass, 1665 vitest pass, ruff clean.
2026-05-19 20:12:41 +03:00
DumiFlex 909af12b0e feat(api,spa): phase d + push-to-library follow-ups
Phase D — alias removal:

- Dropped PUT /wp/api/modules/{id}/payload + library_save.py module.
  PushToLibraryModal now calls the canonical PUT /wp/api/modules/{id}
  with flattened meta fields (name, description, tags) instead of the
  nested meta sub-object the alias accepted. Lifetime ended at one
  workflow review cycle; no external consumers exist.
- Updated test_save_to_library_unified.py and test_payload_validation
  .py to point at the canonical route. Deleted test_library_save_
  endpoint.py.

Bug 3b — modal silent on description/tags:

- Workflow rows only carry meta.name + meta.library_name at insert
  time, so opening PushToLibraryModal on an existing library-tracked
  row left description/tags blank even when the library row had them.
- Added seedFromLibrary() that fetches /wp/api/modules/<id> on modal
  open and fills in any blanks. Respects in-modal edits: only writes
  to fields that are still empty when the response lands.

Bug 3c — wildcard binding override didn't surface as outer mod state:

- Combine already checked inst.variable_binding in isModified();
  wildcard didn't. Brought the wildcard branch in line so renaming
  the binding on a wildcard surfaces the same orange dot a combine
  rename does. INSTANCE_FIELDS_PER_KIND already listed binding on
  both kinds — this row brings the outer dot to match the modal's
  mod gauge.

Bug 3a — reset-icon semantics after Update existing:

- The right-click push-to-library flow updated the library entry but
  never refreshed the workflow row's meta.library_name; the per-field
  reset-to-library button then offered to revert to the pre-save
  name. Both right-click onPushSaved and the in-modal
  onPushToLibrarySaved now stamp library_name alongside name +
  payload_hash on Update mode so reset reflects the new library
  state.

Tests: 1665 SPA + 59 Python passing. Ruff + lint clean.
Bundle size: 306499 / 307200 (701 bytes headroom).
Live QA pending: needs ComfyUI restart to pick up the route removal +
Python validator paths.
2026-05-19 18:54:13 +03:00
DumiFlex 2c8d94bb9e feat(api,spa): phase b+c — unified push-to-library with bundle propagation
Lands the broader save-to-library rethink across the server + extension
surfaces. Closes Bugs 1-5 from the 2026-05-19 live QA report; Bug 6
(payload validation) was already plugged by the previous commit.

API contract (Phase B):

- PUT /wp/api/modules/{id} now accepts {payload, name, description,
  tags, propagate_to_bundles} and persists all of them in one
  transaction. Returns bundles_updated[] so the caller can surface
  "this save touched N bundles" feedback.
- PUT /wp/api/modules/{id}/payload kept as a backwards-compat alias
  that mirrors the same body shape (payload + meta + propagate). The
  older route silently dropped meta; now it routes through the same
  helpers as the canonical PUT.
- Bundle propagation: when a module write happens, every bundle whose
  children[].id matches the updated module gets its frozen child
  snapshot rewritten in lockstep (payload + meta) and its
  payload_hash recomputed. Atomic via shared db_session.
- GET /wp/api/bundles?contains_module=<id> filters the list to bundles
  that reference that module — drives the modal's "this save affects
  N bundles" preview.
- 7 new pytest cases in test_save_to_library_unified.py covering meta
  persistence on both PUT routes, opt-in/opt-out propagation, and the
  bundles filter.

SPA (Phase C):

- New PushToLibraryModal.vue. Owns editable name/description/tags +
  payload preview + bundles-affected list + propagate checkbox. Two
  buttons: Save as new entry (always available — POST /modules) and
  Update existing (greyed when payload_hash is empty). Replaces the
  five per-kind onXxxSaveToLibraryClick handlers + their five copies
  of the same PUT body that lived in ModuleEditModal.
- ContextWidget right-click menu entry becomes Push to library and
  routes to the same modal. Predicate relaxed from
  isMissingFromLibrary(m) to !!m.payload, so inline-created modules
  (no payload_hash) can finally reach the library — they get the
  fork-only path in the modal.
- The five instance modals canSaveToLibrary computeds drop the
  isLibraryTracked + isModified gate. Button is visible whenever
  there is a payload; the modal decides what to do with it. Tests
  updated to match the new contract.
- 8 new vitest cases in PushToLibraryModal.test.ts covering field
  seeding, button disabling, PUT/POST request shape, bundle list
  rendering, and the propagate opt-out round-trip.
- Legacy saveToLibrary(m) function in ContextWidget is gone (no
  callers since the menu predicate change). Its bypass POST to
  import-from-workflow lost the meta-editing path the new modal
  exposes, so deletion is net positive.

Size note: total extension bundle is now 306254 / 307200 bytes
gzipped — 946 bytes of headroom on the size gate. Future feature
work touching this surface needs to factor in budget.

Live QA pending: extension code needs ComfyUI restart to reload.
2026-05-19 18:12:16 +03:00
DumiFlex d661c6d87b fix(api): plug payload validation hole + draft save-to-library rethink
Bug 6 fix: wildcard + fixed_values handlers had no validate_payload
method, so the base no-op was silently accepting any shape on every
write boundary. Verified live: PUT /payload with {options: 'not-an-array'}
returned 200 and persisted the garbage; POST accepted {options: 42};
cross-type pollution (combine-shaped payload PUT to a wildcard) also
succeeded with 200.

Changes:
- engine/modules/wildcard_handler.py: validate_payload checks options
  list shape + per-option value/weight types + non-negative weights +
  optional sub_category strings + var_binding identifier + sub_categories
  list-of-strings.
- engine/modules/fixed_values_handler.py: validate_payload checks values
  list shape + per-value name/value types + identifier rule on non-empty
  names.
- wp_api/modules.py: validates payload on POST /modules, PUT /modules/{id}
  (when payload field is present), and POST /modules/import-from-workflow.
  Shared _validate_payload_for_type helper.
- wp_api/bundles.py: validates every child snapshot's payload on POST
  /bundles and PUT /bundles/{id} when children[] is in the body — closes
  the side door that let bundle writes bypass per-module validation.
- tests/wp_api/test_payload_validation.py: 26 cases covering happy paths
  + malformed payloads per kind, cross-type pollution, import-from-
  workflow, and bundle-children side door.
- tests/wp_api/test_bundles_crud.py: fixture combine child gained the
  required output_var field (it was implicitly invalid before).

Plus docs/superpowers/specs/2026-05-19-save-to-library-rethink-design.md:
the broader four-phase plan that addresses Bugs 1-5 (meta drop, bundle
propagation, fork discoverability, modal button visibility, inline
modules can't save). Phase A (this commit) plugs the validation hole.
Phases B-D will land separately after design review.

Live QA reproduction order remains in the design doc.
2026-05-19 17:34:37 +03:00
DumiFlex 6a0c0bf97b refactor: remove pipelines feature; add unified AllItems library view
Pipelines never paid off as a separate module kind, so peel out:

- Engine: drop pipeline_handler, remove "pipeline" from valid module
  types in repositories + schema parity.
- Manager: delete Pipelines/PipelineEditor views, PipelineSteps +
  PipelineStepPicker components and their tests. Router, sidebar,
  topbar, dashboard, TestRunner, ImportExport, resolver and
  bundleSelection repointed off the removed routes/types.
- wp_api: drop pipeline branch from modules.py + import_export.py
  (and its test).

In its place, add a unified library view:

- src/manager/views/AllItems.vue — modules + bundles in one filter
  panel with kind multi-select (OR across the 6 kinds). Reachable
  via the Dashboard "View all" link.
- ModuleListView grew the slots/props AllItems needs; BundleAddChild
  + Dashboard + ImportExport updated alongside.

Misc polish along the way: kind-icons / theme tokens / display-prefs
touch-ups, ContextWidget + DebugViewer + AssemblerHelper small fixes,
MockModuleCard + ModulePickerModal tweaks, _shared widget cleanup.
2026-05-17 20:50:26 +03:00
DumiFlex a09a8e5833 feat(bundles): repository + CRUD API 2026-05-11 02:51:01 +03:00
DumiFlex 3247fb9705 fix(instance): legacy exception shape + uniform None defaults from review
Two CRITICAL/IMPORTANT findings from final review:

1. DisabledExceptionsSection + prune.ts read only Tier 2 `source_value`/
   `target_value` while engine accepts both Tier 2 and legacy
   `source`/`target` (constraint_handler.py:215). UI on a legacy-
   shaped payload would emit encodeKey([undefined,undefined]) and
   silently drop saved disable selections + falsely flag valid keys
   as stale on refresh. Mirror the engine fallback chain in TS.

2. _fresh_instance() defaulted variable_binding to '' and internal to
   False. The frontend modified-state predicate is value != null, so
   any non-None sentinel falsely lights up the orange dot, forces the
   modal to default to Instance tab, and tags the section as modified
   on a pristine snapshot. Engine readers already coerce None at the
   read site (wildcard_handler.py:216 falsy-or fallback, pipeline.py:160
   bool() coercion), so we keep a single contract: None everywhere =
   no override.

Also: prune.ts now uses encodeKey() helper instead of raw
JSON.stringify for consistency with the cross-language contract.
2026-05-07 15:49:17 +03:00
DumiFlex dbfb7979bb feat(api): add PUT /wp/api/modules/<id>/payload for save-to-library round-trip
adds wp_api/library_save.py with a single route handler that validates the
incoming payload via the module type's registered handler, persists it through
ModuleRepository.update, and returns {"ok": true, "new_hash": "<sha256>"}.
covered by 5 new async tests in tests/wp_api/test_library_save_endpoint.py.

also fixes pre-existing ruff E501 violations in test_derivation_handler.py
and test_instance_schema_parity.py that were blocking the pre-commit hook.
2026-05-07 13:42:46 +03:00
DumiFlex 7a8c216e12 feat(engine): tier 2 instance defaults in _fresh_instance 2026-05-07 12:15:35 +03:00
DumiFlex 17e29a77f5 fix(context): all-kind hashes + hide pipeline from picker
Two bugs surfaced by manual QA after picker unblock:

1. Every freshly-picked non-wildcard module rendered a red "missing"
   dot on the card. Root cause: GET /wp/api/modules/hashes hard-coded
   `type="wildcard"` (5.5.5 spec §4.2 reasoned "catalog only contains
   wildcards"), so when the in-graph WP_Context now embeds a combine /
   derivation / constraint / fixed_values, the resulting payload_hash
   never appeared in the libraryHashes map. The frontend predicate
   `isMissingFromLibrary` then flagged every non-wildcard as missing.
   Fix: drop the `type=` filter so the endpoint returns hashes for
   every kind. Cost: ~50% larger payload for typical libraries; still
   sub-10 KB for the largest libraries we've seen.

2. Picker enabled pipeline kind, contradicting the user-stated scope
   that pipelines stay deferred until the modal grows a pipeline
   preview. Fix: introduce a `HIDDEN_KINDS` set + `visibleModules`
   computed that filters pipeline rows out of the picker entirely.
   Pipeline tab dropped from the kind-tab strip. The defensive guard
   in `isPickable` reads from the same set so any future programmatic
   pick path inherits the gate.

Tests:
- Updated picker test to reflect 5 visible rows (pipeline filtered)
  + asserts the "Pipelines" tab and the literal pipeline row text are
  absent from the rendered picker.
- Renamed + updated the backend hashes test to lock the new
  every-kind contract; combine row now expected in the response map.

485 vitest + pytest green.
2026-04-30 12:40:52 +03:00
DumiFlex 6921606570 feat(context): module picker + lock/internal + card indicators (#8)
Phase 5.5.4 (SPA library picker for ContextWidget) plus QA-driven polish.

* New ModulePickerModal with search, kind tabs, single + multi-pick, and
  compact filter UX (favorites/hide-added inline; category + tag chips
  behind popover with active-count badge).
* ModuleEditModal kind-aware body — wildcard option editor with
  enable/disable, weight overrides, mode picker (random/subset/pinned),
  sub-category multi-select.
* Lock + internal per-instance overrides ported and adapted: locked_seed
  derives a per-wildcard rng via sha256(seed:binding); internal hides
  bindings from the public PIPELINE_CONTEXT payload.
* Card indicators: modified dot (option-pool overrides), missing-from-
  library dot (polled hash check), in-card lock + internal toggles.
* RNG-faithful assembler preview via new /wp/api/preview/resolve; header
  shows "PREVIEW · 42". UNRESOLVED chips clickable to strip $var.
* Bulk collapse/expand toggle, focus toast w/ pulsing border overlay
  that tracks node bounds, friendly @name substitution in modal.
* Backend: new /wp/api/modules/import-from-workflow endpoint preserves
  uuid; engine emits per-module effective seeds via WP_Context UI
  payload for authoritative lock-toggle defaults.
2026-04-30 00:53:13 +03:00
DumiFlex 36e6e1159e fix(api): list /modules total reflects unpaginated row count
Dashboard count cards displayed "1" for every kind regardless of how
many rows the user actually had in the library. Root cause: the list
endpoint returned `total = len(items)` AFTER pagination, so any client
that passed `limit=1` for cheap polling (Dashboard does exactly this)
got `total=1` no matter what.

Adds `ModuleRepository.count(...)` that runs the same WHERE-clause
filters as `list()` but issues `SELECT COUNT(*)` with no LIMIT/OFFSET,
and reroutes both calls through a shared `_build_filter_clause` helper
so filter shape can never drift between the two paths.

Pinned by `test_list_total_ignores_limit` — seeds 3 wildcards, queries
`?type=wildcard&limit=1`, asserts items has 1 row but total is 3.
2026-04-28 23:01:27 +03:00
DumiFlex dc3e42ec2e test(api): pin uuid + payload_hash in list/get responses 2026-04-28 13:58:30 +03:00
DumiFlex 60d2a8956d feat(api): add /modules/hashes endpoint for drift detection 2026-04-28 13:55:09 +03:00
DumiFlex e66388968f fix(api): flatten embed-bundle response to match other endpoints 2026-04-28 13:47:18 +03:00
DumiFlex db8684166f feat(api): add embed-bundle endpoint for lazy walk + transitive deps 2026-04-28 13:44:11 +03:00
DumiFlex 006b0c8ed4 fix(test-runner): revert response envelope to flat shape 2026-04-28 13:31:01 +03:00
DumiFlex bd2cfe1635 feat(test-runner): lazy catalog — resolve nested @{uuid} refs
Wire walk_transitive_refs into _build_ctx_with_catalog so the SPA Test
Runner populates __wp_catalog__ from the live DB before resolution. Also
fixes _resolve_ref to support SnapshotEntry payload.options shape alongside
the legacy flat options shape, and updates db_session to accept an injected
connection from request.app["wp_db"] for test isolation.
2026-04-28 13:23:03 +03:00
DumiFlex fcb3f8ca68 feat(wp-api): extract_referenced_uuids helper for lazy catalog roots 2026-04-28 13:13:59 +03:00
DumiFlex 1ff08eed1c fix(api): atomic import + recursion guard + negative pagination + dry now_iso 2026-04-26 17:16:46 +03:00
DumiFlex f1e3a5eb9d feat(api): spa fallback route + comfyui registration + coverage gates 2026-04-26 17:00:17 +03:00
DumiFlex e51e06f0d4 feat(api): test runner + library import/export endpoints 2026-04-26 16:53:49 +03:00
DumiFlex 27a526b5ce feat(api): snapshot + match + duplicate + favorite + categories endpoints 2026-04-26 16:47:25 +03:00
DumiFlex a97940f4f7 feat(api): /wp/api/modules crud endpoints 2026-04-26 16:40:46 +03:00