Commit Graph
21 Commits
Author SHA1 Message Date
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 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 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 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 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 2d89a7391d feat(engine): scaffold combine/derivation/constraint/pipeline module types 2026-04-27 03:54:13 +03:00
DumiFlex 1ff08eed1c fix(api): atomic import + recursion guard + negative pagination + dry now_iso 2026-04-26 17:16:46 +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