diff --git a/CHANGELOG.md b/CHANGELOG.md index e66c42d..d114a89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.5.8 + +- Count each comparison preview file once, even when cached execution notifications replay it; keep the selected result on replay. +- Remove duplicate preview entries when reloading older saved comparison histories. + ## 0.5.7 - Accumulate comparison results across runs, select the newest result and preserve each result's matching original, including after workflow reload. diff --git a/ROLLBACK.md b/ROLLBACK.md index 9ccd3c1..ec3dc11 100644 --- a/ROLLBACK.md +++ b/ROLLBACK.md @@ -1,10 +1,11 @@ # Git versions and rollback -The current local version is **v0.5.7**. Both the installed plugin and the source repository retain local commits and tags. This update is also pushed to the GitHub main branch. +The current local version is **v0.5.8**. Both the installed plugin and the source repository retain local commits and tags. This update is also pushed to the GitHub main branch. | Tag | Contents | |---|---| -| `v0.5.7` | Fixed comparison images; wheel navigates the workflow; retain result history across runs | +| `v0.5.8` | Deduplicate replayed comparison previews and repair duplicate saved history | +| `v0.5.7` / `local-before-v0.5.8` | Fixed comparison images; wheel navigates the workflow; retain result history across runs | | `v0.5.6` / `local-before-v0.5.7` | Exclusive selector follows Get/Set and virtual routes correctly | | `v0.5.5` / `local-before-v0.5.6` | Bind canvas/panel widths to node geometry and retain vertical resizing | | `v0.5.4` / `local-before-v0.5.5` | Isolate Fit clicks and refresh editor assets on upgrade | @@ -19,13 +20,13 @@ To undo only this update, run in the plugin folder: ```powershell git status --short -git switch --detach local-before-v0.5.7 +git switch --detach local-before-v0.5.8 ``` Return to this version with: ```powershell -git switch --detach v0.5.7 +git switch --detach v0.5.8 ``` Restart ComfyUI and hard-refresh the browser (`Ctrl+F5`) after switching versions. diff --git a/VERIFICATION.md b/VERIFICATION.md index 3a78710..3781c90 100644 --- a/VERIFICATION.md +++ b/VERIFICATION.md @@ -49,6 +49,7 @@ Environment: Windows, local ComfyUI 0.19.3, ComfyUI frontend 1.42.11, PyTorch 2. - 0.5.7: actual browser wheel events over a real queued comparison image change workflow navigation while the comparison transform stays fitted and centered. Verified on frontends 1.42.11 and 1.52.7 in classic nodes and Nodes 2.0, with left-result/right-original pixels, hover/leave behavior, batch selection, adoption preserving mask/history, workflow reload, rejected mismatched adoption and ordinary-upload mask reset. - 0.5.7: three real queued two-image batches accumulate 2, 4 and 6 results and select the newest result. After adoption, old results retain their old original and new results use the adopted original; all six results survive workflow reload in both frontend versions/renderers. Dependency-free tests additionally cover legacy batch mapping, empty outputs and immutable appending. +- 0.5.8: real single-image queues on frontend 1.52.7 in classic nodes and Nodes 2.0 show 1/1 then 2/2. Replaying the same execution output twice does not increase the count or reset a manually selected result. Reloading a workflow containing duplicate preview references removes duplicates. Unit checks distinguish filenames, subfolders and file types. The sampling path was also checked for noise-schedule continuity against the real `common_ksampler`/`KSampler` functions in the local ComfyUI; an actual checkpoint has not been loaded to test GPU image quality or every model family, so CPU/interface tests must not be treated as model-quality verification. diff --git a/pyproject.toml b/pyproject.toml index 2066147..098073c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "adachi-inpaint-workbench" -version = "0.5.7" +version = "0.5.8" description = "Inline mask painting and WebUI-style inpainting nodes for ComfyUI" requires-python = ">=3.10" license = { file = "LICENSE" } diff --git a/tests/test_compare_history.mjs b/tests/test_compare_history.mjs index 4a35c63..7fef74f 100644 --- a/tests/test_compare_history.mjs +++ b/tests/test_compare_history.mjs @@ -12,5 +12,12 @@ assert.deepEqual(appendComparison(legacy, {originals: ["c"], results: [5]}), { assert.deepEqual(appendComparison(undefined, first), first); assert.deepEqual(appendComparison(first, {}), first); assert.deepEqual(appendComparison({}, {results: ["orphan"]}), {originals: [], results: []}); -assert.deepEqual(appendComparison(JSON.parse(JSON.stringify(second)), first).results, ["result-a", "result-b", "result-c", "result-a"]); +assert.deepEqual(appendComparison(JSON.parse(JSON.stringify(second)), first).results, ["result-a", "result-b", "result-c"], "Cached execution replay must not count twice"); +const preview = {filename: "preview_001.png", subfolder: "", type: "temp"}; +const one = {originals: ["source-a"], results: [preview]}; +assert.equal(appendComparison(one, {originals: ["source-a"], results: [{...preview}]}).results.length, 1); +assert.equal(appendComparison(one, {originals: ["source-a"], results: [{...preview, filename: "preview_002.png"}]}).results.length, 2); +assert.equal(appendComparison(one, {originals: ["source-a"], results: [{...preview, subfolder: "other"}]}).results.length, 2); +assert.equal(appendComparison(one, {originals: ["source-a"], results: [{...preview, type: "output"}]}).results.length, 2); +assert.deepEqual(appendComparison({originals: ["source-a", "source-a"], results: [preview, {...preview}]}), one, "Repair duplicates already saved in workflows"); console.log("Comparison history checks passed"); diff --git a/web/compare_history.js b/web/compare_history.js index 66eb2d9..84b4702 100644 --- a/web/compare_history.js +++ b/web/compare_history.js @@ -11,5 +11,16 @@ export function appendComparison(previous, incoming) { }; }; const before = pairs(previous), next = pairs(incoming); - return { originals: [...before.originals, ...next.originals], results: [...before.results, ...next.results] }; + const history = { originals: [], results: [] }, seen = new Set(); + for (const data of [before, next]) data.results.forEach((result, index) => { + // Cached execution notifications can replay the same preview. Its file + // identity is stable, while a genuinely new generation gets a new file. + const key = typeof result === "object" && result !== null + ? JSON.stringify([result.type || "temp", result.subfolder || "", result.filename]) + : JSON.stringify(result); + if (seen.has(key)) return; + seen.add(key); + history.originals.push(data.originals[index]); history.results.push(result); + }); + return history; } diff --git a/web/compare_node.js b/web/compare_node.js index d08dd01..bf1a54e 100644 --- a/web/compare_node.js +++ b/web/compare_node.js @@ -1,7 +1,7 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js"; -import { ImageCompareView } from "./compare.js?v=0.5.7"; -import { appendComparison } from "./compare_history.js?v=0.5.7"; +import { ImageCompareView } from "./compare.js?v=0.5.8"; +import { appendComparison } from "./compare_history.js?v=0.5.8"; import { routeWheelOutsideImage } from "./wheel_routing.js"; import { T } from "./i18n.js"; import { bindWidgetToNodeWidth } from "./widget_layout.js?v=0.5.5"; @@ -62,18 +62,24 @@ app.registerExtension({ node.onExecuted = function (output) { const result = executed?.apply(this, arguments); if (!output?.adachi_originals?.length || !output?.adachi_results?.length) return result; - const data = appendComparison(node.properties.adachi_compare, { + const previous = appendComparison(node.properties.adachi_compare); + const data = appendComparison(previous, { originals: output.adachi_originals, results: output.adachi_results, }); node.properties.adachi_compare = data; - node.properties.adachi_result_index = data.results.length - 1; + node.properties.adachi_result_index = data.results.length > previous.results.length + ? data.results.length - 1 : Math.max(0, Math.min(data.results.length - 1, node.properties.adachi_result_index || 0)); compare.setData(data, node.properties.adachi_result_index); return result; }; const configured = node.onConfigure; node.onConfigure = function () { const result = configured?.apply(this, arguments); - if (node.properties.adachi_compare) compare.setData(node.properties.adachi_compare, node.properties.adachi_result_index || 0); + if (node.properties.adachi_compare) { + node.properties.adachi_compare = appendComparison(node.properties.adachi_compare); + node.properties.adachi_result_index = Math.max(0, Math.min(node.properties.adachi_compare.results.length - 1, node.properties.adachi_result_index || 0)); + compare.setData(node.properties.adachi_compare, node.properties.adachi_result_index); + } return result; }; const removed = node.onRemoved;