5 Commits
Author SHA1 Message Date
DumiFlex e5eef0a4c5 test(fixed_values): cover lock seed determinism + ref-out-of-surface + mixed-token render 2026-05-08 19:37:15 +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