* fix(web): generation-agnostic Comfy draft-quota guard "Failed to save workflow draft" toasts returned on ComfyUI frontend 1.44+ because draft storage moved to new localStorage key names (per-workspace V1 keys, then a V2 index+payload scheme) that the old exact-name guard embedded in ltx_director.js never matched - and the 1.44 V2 layer latches a session-wide storage-unavailable flag after one failed save+evict cycle, re-toasting on every edit until reload. Move the guard to its own global extension, web/koolook_draft_guard.js: - match draft keys by prefix (Comfy.Workflow.Draft) across all known generations, with unknown future keys as last-resort evictions - rescue quota failures inside localStorage.setItem (evict the oldest draft across generations, retry) before Comfy's own catch sees the error, so the save succeeds and the latch never trips - boot prune: drop the suffixed-V1 dead weight the frontend's V1->V2 migration never deletes, scope corrupt-key cleanup to the offending key, drop oversized entries, enforce a total draft budget - ship the guard with dev-sync-audio so a scoped sync cannot strand a dev install guard-less Behavior is covered by tests/js/test_draft_guard.mjs (12 scenarios, wired into CI next to the existing node smoke test); static integration contracts live in tests/sidebar/test_draft_guard.py. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(web): address deep-review findings on the draft guard - latch the "still full" hint toast once per session so a genuinely full origin cannot re-toast on repeated failing autosave attempts; regression assertion added to the exhaustion scenario - document the bounded-eviction termination contract and the post-rethrow storage-unavailable latch at the wrapper, plus the budget's V1-blob-only (1.5M) -> all-generations (2M) scope widening (also now stated in the CHANGELOG entry) - version-robust wording for the suffixed-V1 cleanup rationale: the shipped 1.44 migration leaves the keys behind (verified against the installed 1.44.19 dist - no removeItem exists there), later frontends clean up on successful migration, and an interrupted migration can leak the keys on any version - comment the intentional best-effort swallow in the toast helper Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
60 lines
2.6 KiB
Python
60 lines
2.6 KiB
Python
"""Static contracts for the global Comfy draft-storage guard.
|
|
|
|
Behavior is covered by tests/js/test_draft_guard.mjs (run by CI's node
|
|
step); the checks here pin the integration surfaces the node suite cannot
|
|
see: which file ships the guard, the key-prefix contract that makes it
|
|
generation-agnostic, that the scoped audio dev-sync still carries it, and
|
|
that CI actually runs the node suite.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
GUARD_JS = REPO_ROOT / "web" / "koolook_draft_guard.js"
|
|
|
|
|
|
def test_guard_is_generation_agnostic() -> None:
|
|
source = GUARD_JS.read_text(encoding="utf-8")
|
|
|
|
# Prefix matching is the future-proofing contract: every known draft
|
|
# key generation starts with this prefix, and unknown future keys under
|
|
# it degrade to last-resort eviction instead of being invisible (which
|
|
# is how the previous exact-name guard went stale on frontend 1.44).
|
|
assert 'DRAFT_KEY_PREFIX = "Comfy.Workflow.Draft"' in source
|
|
assert 'V2_INDEX_PREFIX = "Comfy.Workflow.DraftIndex.v2:"' in source
|
|
assert 'V2_PAYLOAD_PREFIX = "Comfy.Workflow.Draft.v2:"' in source
|
|
# Multi-install collision sentinel: older guard copies (pre-move
|
|
# releases still embed one in ltx_director.js) check the same flag, so
|
|
# exactly one wrapper installs no matter which copy loads first.
|
|
assert "__koolookDraftQuotaGuardInstalled" in source
|
|
# Both mechanisms must run at page load.
|
|
assert "installComfyDraftQuotaGuard();" in source
|
|
assert "pruneComfyDraftCache();" in source
|
|
# Multi-browser quota error detection (Chrome name, Firefox name/codes).
|
|
assert "QuotaExceededError" in source
|
|
assert "NS_ERROR_DOM_QUOTA_REACHED" in source
|
|
|
|
|
|
def test_scoped_audio_sync_ships_the_guard() -> None:
|
|
"""dev-sync-audio replaces web/whatdreamscost_koolook/, which used to
|
|
embed the guard. Shipping the global guard file alongside keeps a
|
|
scoped sync from stranding a dev install with no guard at all.
|
|
"""
|
|
script = REPO_ROOT / "scripts" / "sync_to_dev_audio.py"
|
|
spec = importlib.util.spec_from_file_location(
|
|
"sync_to_dev_audio_for_draft_guard_test", script
|
|
)
|
|
assert spec is not None and spec.loader is not None
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
|
|
assert "web/koolook_draft_guard.js" in module.AUDIO_PATHS
|
|
|
|
|
|
def test_ci_runs_the_node_behavior_suite() -> None:
|
|
ci = (REPO_ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8")
|
|
assert "node tests/js/test_draft_guard.mjs" in ci
|