14 Commits
Author SHA1 Message Date
DumiFlex e41ae4ded0 feat(debug): trace steps say why they came out that way, per kind (#370)
Wildcard steps list their options with weights and why each was cut, the pool chain, locked seed, separator and matched value; fixed values, combines and node templates show what they read; constraint steps link the steps they re-weighted; Model Info, Toggles, Injector and Context Loop rows carry their own facts.
2026-11-12 02:33:22 +01:00
Dumitru Picalau 8d0fa337b2 chore: fixes from the 2026-10-21 review (#93)
Shorter release notes, SeedList help, small-model docs; _chat_turn split and pyright in CI; ESLint a11y ratchet and Modal focus trap; tags/models/Image Filter tests; AI LAN warning, web allow expiry, one thinking setting per answer on local models, recommended hints and Reset to recommended (16k context minimum, 32k recommended); Assembler Clear template clears the negative; Vue 3.5.44, SECURITY and Dependabot.
2026-10-22 06:15:13 +02:00
Dumitru Picalau 26df88216f feat(ai): chat round, node negatives, pack builder quality fixes (#69)
Chat round (explain on selection, page starters, reactions, streamed notes, thinking), negatives on node fixed values and combines, Pack builder quality fixes from 31 real-model eval rounds, and small-model support (scoped tools, checked tool arguments on local servers, two-phase steps, fixed prefix shown in Test connection).
2026-10-17 01:23:58 +02:00
Dumitru Picalau df9a19a624 fix: engine rng keying, widget teardown, import validation and connection leak (#72)
Derivation and fixed values modules roll on their own rng stream (seeds through their alternations can resolve differently). DOM widgets tear down when their node is removed (effect scope, cleanups, shared onConnectionsChange wrapper, seed state in WeakMaps, capped topbar rAF loop). /import/commit validates module and bundle child payloads. WP_Context and preview close their library connection; the node path migrates once. Pinned option missing warns. CLAUDE.md says which parts of the schema machine are wired; js budget 473 KB.
2026-10-17 01:07:34 +02:00
Claude 9b567f38d8 fix(canvas): toggle fixed values one at a time and keep wildcard icons purple
Fixed-value rows without ids were all keyed by the same missing id, so
disabling one disabled every row. Rows are now keyed by their id, or by
their position when the id is missing or repeated, in the canvas editor and
the engine alike.

The wildcard type icon took its color from a bare selector that tied with
the shared grey default, which several lazy chunks carry. Whichever chunk
loaded last won, so wildcard icons turned grey after opening a modal or
adding some nodes. A data-kind selector now outranks it.
2026-09-30 14:50:17 +00:00
Claude 7b02fcfd58 feat(engine): per-variable negatives for send-to-negative
Wildcard options, fixed values and combines can carry a `negative`, and a
derivation action gains the `negative` ("Add to negative") mode. The
engine files negatives per variable in `__wp_negatives__`, carries them
across nodes, and the Assembler gets a `negative` output built from the
variables its template rendered, filled into a `negative_template` at
`$negatives`. Negatives resolve on their own seed stream and refs inside
them resolve quietly, so picks never shift. Injector rows can set a
negative, the Prompt Cleaner cleans an optional negative with its own
rule column, and the preview endpoint returns the negatives table.
Schema v8 stamps payloads that use a negative.
2026-09-30 00:50:47 +00:00
DumiFlex 58c5b27922 fix(engine): hold the resolved VALUE across iterations, not just the seed
seed_scope=hold previously swapped in a constant seed and re-picked each
iteration. That failed two ways: (1) a held wildcard whose pool is reshaped by
a constraint (varying source) picks a different option from the same seed; and
(2) under Override seed, the 'hold' seed equalled the per-iteration derived[k]
(effective_chain_seed returns an override verbatim, ignoring loop_index=0), so
hold was a no-op. Nested @{} refs in a held value also re-rolled.

Now hold freezes the frame-0 RESOLVED value. context_node computes a constant
hold seed from the loop's derived-series head; pipeline runs a throwaway base
pass at loop_index=0 when a loop drives + any module holds, and the
wildcard/combine/fixed-values handlers copy their resolved value from that base
context verbatim — skipping the pick AND resolve_text, so a constrained or
@{}-bearing held wildcard stays frozen (green jeans stays green jeans).

Tests: constrained held wildcard + nested-ref held wildcard each stay constant
across iterations while their vary counterparts move.
2026-07-01 03:17:58 +03:00
DumiFlex 15c1ade3ef feat(engine): fixed_values honors hold seed_scope 2026-06-26 06:43:28 +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 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 6cfeec8cef feat(fixed_values): engine resolves inline syntax per value + locked_seed
Each emitted value now passes through resolve_text with
surface="fixed_values" so {a|b|c} alternations + {N\$\$sep\$\$...} repeats
+ escapes resolve. \$var reads + @{uuid} refs are gated off (lenient:
warn + render literal) — fixed_values is a binding PRODUCER, not a
consumer.

Seed lock: instance.locked_seed pins {a|b|c} resolution per instance
via the shared engine.modules._seed.derive_module_rng helper, keyed by
ctx["__wp_module_id__"] when present (else "fixed_values" literal).

Backward compat: ctx=None / minimal-dict callers skip the
syntax-resolution path via _ctx_supports_resolve guard.

5 new tests: alternation per value, locked_seed determinism, escape
renders literal \$, \$var warns + literal, values_overrides precedence.
2026-05-08 18:33:05 +03:00
DumiFlex 487873822e fix: surface silent-failure paths flagged by audit
Code review (silent-failure-hunter + feature-dev:code-reviewer) flagged seven paths where the engine or scanner silently swallowed problems instead of warning. Fixed the high-confidence ones:

1. `engine/modules/wildcard_handler.py:_apply_constraint_to_options` — `mode: "allow"` with `factor != 1` silently ignored the factor (SPA editors expose the factor field for every mode, so users naturally try `allow + factor=0.5` expecting weight reduction). Added a `constraint_factor_ignored_on_allow` warning surfaced via the existing `__wp_warnings__` channel. Defensive `unknown_constraint_mode` path also added — currently dead code (validator rejects typo'd modes upfront) but cheap belt-and-suspenders for any future path that bypasses validate.

2. Constraint excludes-all detection — when the matrix excluded every option in the target's pool the post-application total weight was 0 and `_pick_weighted` fell back silently to `options[0]`. The user got the same first option forever with no signal that their constraint over-narrowed. Added a `constraint_excludes_all_options` warning fired when constraints applied AND total weight collapsed to 0.

3. `engine/modules/constraint_handler.py:_ctx_set_constraint` — both the setter API and dict-fallback paths swallowed `Exception` silently, leaving the constraint un-registered with zero signal. Added a `constraint_register_failed` error-severity warning so a fundamental wiring break shows up in WP_Debug instead of being mysterious.

4. `engine/modules/fixed_values_handler.py` — non-list `instance.values_overrides` (string/dict from old SPA save or hand-edited JSON) silently fell back to library payload, dropping the user's edits without explanation. Added a `fixed_values_overrides_malformed` warning naming the wrong type so the fallback is debuggable.

5. `src/extension/preview-resolver.ts` — failure ledger was a `Set<string>` that made every failure permanent for the session. A single page-load 5xx flake left a uuid stuck rendering as `$ae07018b` until full reload. Replaced with `Map<string, {at, permanent}>` plus a 30s retry TTL: 404 stays permanent (server confirms the uuid doesn't exist), every other status retries after the TTL. Network/JSON errors are also retryable now and log once via the same `window.__wp_walker_log__` flag the walker uses.

6. `src/extension/conflicts.ts` — `localWildcardIndex` only tracked ENABLED wildcards. A constraint pointing at a disabled local wildcard would falsely flag as "constraint_source_missing" / "constraint_target_missing" because the disabled module was invisible to the index. Track both enabled and disabled — disabled-state is a separate concern from "does the uuid live in this node."

Tests: 3 new constraint-integration cases (factor-ignored-on-allow, excludes-all, plus the existing positive-path checks unchanged), 2 new fixed_values cases (malformed warning fires, no-warning when overrides absent).

468 pytest, 523 vitest, typecheck clean, entry 2.71 KB / total 186 KB gzipped.

Audit found other items still outstanding (sameAsLibrary index-strict compare, multi-constraint composition surprise, shadows+duplicate dual-emit policy, ctx as growing god-object) — deferring those as design questions rather than reflexive code changes.
2026-05-03 01:54:04 +03:00
DumiFlex da03077e62 feat(fixed_values): two-tier library/instance model with reset-to-library
Fixed_values now mirrors the wildcard pattern: library-picked modules treat `payload.values` as the immutable library snapshot, and user edits land in `instance.values_overrides` instead of mutating the snapshot in place. Aligns the kind with how wildcards already work — "modified" becomes a real card-level signal, "reset to library" becomes a one-click affordance, and library drift detection no longer collides with local edits.

Engine (`fixed_values_handler.py`): two-tier read. Overrides win when present and non-empty; otherwise the library `payload.values` resolves as before. Defensive against malformed (non-list) overrides — defaults to library payload.

Modal save: branches on `payload_hash` presence. Library-tracked modules write entries to `instance.values_overrides`; library `payload.values` is never touched. Inline-created modules (no `payload_hash`) keep editing `payload.values` directly — original behaviour, no library anchor to preserve. Save also includes a soft-reset path: when entries exactly match library values, the override is deleted instead of stored, so the modified dot clears automatically.

Modal UI: when a library-tracked fixed_values has overrides set, a "reset" bulk-button appears next to "add". Clicking reverts entries to `payload.values` and drops the override field. The user still has to Save to commit, preserving the cancel-discards contract.

ContextWidget `isModified`: extends to fixed_values via `instance.values_overrides` non-empty check. Inline-created modules never light up — they have no library anchor to diverge from. Existing wildcard branches unchanged.

Frontend mirrors of the engine read (`extension/graph.ts:writeBindings`, `extension/conflicts.ts:writesOf`): both consult overrides first, fall back to entries + payload.values. Keeps the assembler preview and the conflict scanner consistent with the runtime resolver.

Migration: workflows saved before this change have edits stored in `payload.values` for library-tracked fixed_values. Engine still reads payload.values when no override is set, so they continue to resolve correctly. The modified dot only fires for new edits via the new modal save path — existing pre-migration content shows as "in library" until next opened + saved. Acceptable cost; the alternative would require a server round-trip to compare against live library hashes.

Tests: 6 new engine handler cases (override path, empty fallback, add/drop entries, malformed override defense), 6 new modal save/reset cases (library-tracked write to overrides, no-edit clean save, inline payload write, reset button visibility + click, both negative cases). 512/514 vitest, 457/461 pytest.
2026-05-02 17:15:01 +03:00
DumiFlex f0be23f0a8 feat(engine): fixedvalueshandler + route legacy dispatch through it 2026-04-26 16:30:49 +03:00