Major features:
- Adaptive drag mode with 5 fixed modes (Normal, Freeze+Check, Freeze, Hide, Heavy Deferred)
- Heavy Deferred for lag-free dragging in large workflows (250+ nodes)
- Three drag phases: moving → held → released, with priority-based re-routing during pause
- Node hard-obstacle avoidance — links can never pass through nodes
Performance:
- Progressive routing with frame-budgeted progressive batches
- Fast graph signature for geometry-change detection
- OVG rebuild deferred during large drags
- Collision re-check limited to affected links only
Fixes:
- Hover no longer triggers full-workflow redraw
- Krea2 / Ultimate SD Upscale link disappearance during drag
- Stale sticky paths now validated before reuse
- Drag-endpoint dogleg for straight-line links
New modules: adaptive-policy, drag-policy, draw-policy, profiler, progressive, ui-policy
Tests: 39 automated tests covering routing, adaptive, drag, profiler
Floating bar: Adaptive 🧠 toggle, 5 fixed-mode buttons, debug row, Nodes 2.0 switch
58 lines
2.1 KiB
JavaScript
58 lines
2.1 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import {
|
|
orderedRouteResults,
|
|
processRouteSlice,
|
|
progressiveItemLimit,
|
|
shouldProgressivelyRoute,
|
|
} from "../web/progressive.js";
|
|
|
|
const entry = (id) => ({ link: { id } });
|
|
|
|
test("progressive routing keeps result order while revealing slices", () => {
|
|
const entries = [entry(1), entry(2), entry(3), entry(4)];
|
|
const batch = {
|
|
entries,
|
|
jobs: entries.map((e) => ({ entry: e })),
|
|
index: 0,
|
|
resultsById: new Map(),
|
|
};
|
|
const routeOne = (job) => ({ entry: job.entry, cached: { pts: [] } });
|
|
const first = processRouteSlice(batch, routeOne, { maxItems: 2, budgetMs: Infinity, now: () => 0 });
|
|
assert.deepEqual(first, { processed: 2, done: false, remaining: 2 });
|
|
assert.deepEqual(orderedRouteResults(entries, batch.resultsById).map((r) => r.entry.link.id), [1, 2]);
|
|
const second = processRouteSlice(batch, routeOne, { maxItems: 2, budgetMs: Infinity, now: () => 0 });
|
|
assert.equal(second.done, true);
|
|
assert.deepEqual(orderedRouteResults(entries, batch.resultsById).map((r) => r.entry.link.id), [1, 2, 3, 4]);
|
|
});
|
|
|
|
test("progressive policy leaves small and drag-settle passes synchronous", () => {
|
|
assert.equal(shouldProgressivelyRoute(false, false, 20), true);
|
|
assert.equal(shouldProgressivelyRoute(true, false, 20), false);
|
|
assert.equal(shouldProgressivelyRoute(false, true, 20), false);
|
|
assert.equal(shouldProgressivelyRoute(false, false, 15), false);
|
|
assert.equal(shouldProgressivelyRoute(false, false, 16), true);
|
|
assert.equal(progressiveItemLimit(573, 10), 58);
|
|
});
|
|
|
|
test("a route slice yields after its time budget", () => {
|
|
const entries = [entry(1), entry(2), entry(3), entry(4)];
|
|
const batch = {
|
|
entries,
|
|
jobs: entries.map((e) => ({ entry: e })),
|
|
index: 0,
|
|
resultsById: new Map(),
|
|
};
|
|
let time = 0;
|
|
const slice = processRouteSlice(
|
|
batch,
|
|
(job) => {
|
|
time += 7;
|
|
return { entry: job.entry, cached: { pts: [] } };
|
|
},
|
|
{ maxItems: 10, budgetMs: 12, now: () => time },
|
|
);
|
|
assert.deepEqual(slice, { processed: 2, done: false, remaining: 2 });
|
|
});
|