Commit Graph
50 Commits
Author SHA1 Message Date
DumiFlex 6703583db8 fix(preview): serialize list-valued vars in resolve endpoint 2026-06-08 21:11:59 +03:00
DumiFlex 13de622c6b feat(engine): multi-tag options + boolean sub-category filters (v2)
Option sub_category -> sub_categories[] (default []). Both pool filters (instance category_filter, nested @{} ref) become boolean expressions (and/or/not/parens; comma=or) matched against an option's tag set via the shared engine/syntax/subcat_filter matcher, applied BEFORE constraints. Exclude-null is a separate flag: instance exclude_null + the trailing !null ref marker, replacing the old 'null' keyword.

4-segment ref grammar @{uuid#name:expr!null} single-sourced across tokenize.py/scan.py/pipeline.py/_constraints.py (! excluded from name/expr captures); tokenizer REF meta now carries filter_expr + exclude_null instead of a comma-split list. scan.py findall unpack sites updated for the 4th group.

validate_payload: per-option sub_categories[] must be in the registry; optional tag_groups (UI-only axes, members in-registry, <=1 group); registry names validated via subcat_filter.validate_subcat_name (rejects whitespace, boolean/ref chars, reserved words incl. null). wp_api/_validators delegates to the same function.

Constraints stay alive via a primary-tag stopgap: _apply_constraint_to_options + _record_pick use sub_categories[0] as the matrix key; true multi-tag matrix matching is SP3. Cascade fix_subcat_delete/rename + orchestrator scrub the per-option list; instance_schemas category_filter -> string. Existing engine tests migrated to the v2 shape; added tests for the ref regex, payload validation, and multi-tag pool filtering.
2026-06-07 00:43:55 +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 bbd1a37bd8 feat(publish): writeback community origin to local row after publish
Publishing a local row to community never linked the row back to the
resulting post, so: no community pill, no update-available tracking,
and the re-publish version-bump flow was unreachable (origin slug was
always null -> publishMode could only ever be 'create').

Adds the writeback path:
- ModuleRepository / BundleRepository.set_community_origin(id, slug,
  version): stamps community_post_slug + community_version_number
  WITHOUT a version bump or fingerprint change -- origin metadata is
  not runtime-affecting, so a published row stays byte-identical to
  what consumers installed.
- wp_api: POST /wp/api/modules/{id}/community-origin + the bundles
  twin. Validates {post_slug: str, version_number: int}.
- api client: modules.setCommunityOrigin + bundles.setCommunityOrigin.
- main.ts host bridge: new optional `markPublished(localId, kind,
  slug, version)` method. Calls the right setCommunityOrigin, then
  refreshes both catalogs so the pill appears without a reload.
  Optional + try/catch-returns-false so a writeback miss never breaks
  the embed's publish-success path. No ABI bump -- adding a method is
  non-breaking; the embed guards on its presence.

Tests: set_community_origin stamps slug+version, does NOT bump
version/fingerprint, raises on missing row.

Pairs with the community embed commit that calls markPublished after
createPost / createVersion.
2026-06-06 06:48:43 +03:00
DumiFlex 99374cf76f feat(manager): nsfw toggle in editors + pill on library rows
Wires the content_rating axis end-to-end on the SPA + HTTP layer:

- TS types: ModuleRow / BundleRow / *CreateInput / *UpdateInput +
  InstallOrigin all gain a `content_rating?: 'safe' | 'nsfw'` field.
- install.ts: when `opts.origin.content_rating` is set, every entity
  going in has its content_rating stamped -- the embed's community
  install passes 'nsfw' through to the engine without needing a
  follow-up fetch.
- IdentityCard: adds a Toggle in a new "Content rating" field. Same
  component is the source for all 6 module editors + BundleEditor,
  so the toggle lives in one place. A `showContentRating` opt-out
  lets TemplateEditor hide the field (templates table doesn't carry
  the column).
- All 6 module editors (Fixed, Wildcard, Combine, Constraint,
  Derivation, Bundle) plumb a `contentRating` ref through load,
  save, and IdentityCard binding.
- wp_api modules + bundles: POST/PUT endpoints accept content_rating
  in the body and forward to the repository.
- ModuleListView: renders a new `wp-row-community-pill--nsfw` pill
  on rows where row.content_rating === 'nsfw'. Uses the harmonized
  primitive from b5ce9e4 -- the comment block there explicitly
  seeded this variant.
2026-06-06 05:43:40 +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 00b0c735bb feat(api): run pending db move before migration on startup 2026-06-03 02:02:40 +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 3bd71588b3 fix(constraint): empty source/target valid for null-option exceptions 2026-05-26 19:40:05 +03:00
DumiFlex 85cf827d56 chore: pre-pair-badge polish (bundle, cascade, debug, light-theme, cleaner)
Accumulated branch work that was uncommitted alongside the pair-badge feature in the prior commit. Captured as a single sweep commit so the branch can merge cleanly; future work should commit per-feature to avoid this.
2026-05-26 11:24:43 +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 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 19e9b4b3df refactor(wp_api): structural 404 for missing undo entry
Replace the substring-match `"not found" in error` line in
`import_undo` with an explicit `get_undo_entry` existence check
before calling `undo_import`. The handler now returns 404 only
when the undo row is structurally absent, mirroring the typed-
exception pattern (`ModuleNotFound`, `BundleNotFound`,
`CategoryNotFound`) used elsewhere in wp_api. A future engine
wording change can no longer silently degrade the 404 to 400.

Both reads share a single `db_session` so the existence check
and the destructive undo stay in one transaction context.
2026-05-22 22:33:47 +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 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 48db6be8b6 feat(bundles): drift indicator surfaces per-child + library-side drift
Module-level drift detection already polls module library hashes and
drives the per-card drift dot, but bundles had no equivalent: a
BundleHeader carried zero drift signal even when its children had
fallen out of sync OR the library entry had been edited since insert.
The user can already act on either (Reset to library / Refresh
drifted live in the bundle ctxmenu), but they had no way to spot
when action was warranted.

Two drift sources, surfaced together:

- Per-child drift — sum `isDrifted` over the bundle's child range.
  Drives the existing `${n} drifted` suffix on the bundle subtitle.
  Reused the already-computed per-module `isDrifted` so no extra
  fetches.

- Library-side drift — compare `BundleInstance.inserted_at_hash`
  against the library's current `payload_hash`. A library entry can
  change without any local child changing (e.g. the library author
  reordered children, swapped a child the user detached locally,
  or just bumped the hash via a meta-only edit). Needed a new
  source of truth: `/wp/api/bundles/hashes` (new endpoint, same
  shape as `/wp/api/modules/hashes`), polled by an extended
  `drift-store` exporting a parallel `bundleHashes` ref.

BundleHeader gains a small amber dot next to the bundle name when
either drift kind is active. Tooltip shapes itself to the actual
combination ("library updated + N children drifted", "library
updated", or "N children drifted") and points at the bundle
ctxmenu for the reset actions. Subtle pulse on first appear so a
mid-session library edit catches the eye.

`drift-store.fetchHashes` now fans out to both endpoints in
parallel; each call has its own try/catch so a 500 on one doesn't
blank out the other's state. Test updated to expect the new 2-fetch
fan-out shape on `forceRefresh`.
2026-05-16 17:36:10 +03:00
DumiFlex a09a8e5833 feat(bundles): repository + CRUD API 2026-05-11 02:51:01 +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 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 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 25ba30b76f perf(test-runner): hoist catalog walk above per-sample loop 2026-04-28 13:37:07 +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 657fe57890 refactor(engine): wildcard handler delegates to resolve_text 2026-04-28 06:39:39 +03:00
DumiFlex 2d89a7391d feat(engine): scaffold combine/derivation/constraint/pipeline module types 2026-04-27 03:54:13 +03:00
DumiFlex 190e06aa35 fix(api): run migrations on register_routes so production db has tables 2026-04-26 20:23:42 +03:00
DumiFlex 1ff08eed1c fix(api): atomic import + recursion guard + negative pagination + dry now_iso 2026-04-26 17:16:46 +03:00
DumiFlex 6e44903b3c fix(api): use streamresponse return type for spa fallback handlers 2026-04-26 17:03:43 +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