Files
olivv-cs-ComfyUI-FunPack/app/legacy/scene_align.js
T
digital-garbageandClaude Sonnet 5 29ac44ffcd feat: v4 UI port Phase A start -- module categories, legacy frontend served live
Two pieces of the "port v4's exact UI onto v5's backend" plan:

- core/contract.py: new optional ModuleSpec.category field (closed set:
  continuity, guidance, conditioning, sampling, post, system), validated in
  core/schema.py, threaded through core/registry.py's FIELDS and
  to_manifest(). Presentation grouping, independent of STAGE (execution
  order) -- a module can be stage="sampling" and category="guidance" at
  once. Only the 4 existing modules that actually render a settings panel
  (alg, sharpen, appearance, full_control) got a category assigned; the
  other 12 have none to categorize (no SETTINGS block) and stay
  uncategorised, which is valid.

- v4's frontend (movie_editor/frontend/*, ~29k lines, 61 files) copied
  verbatim into app/legacy/. core/routes.py's "/" route now serves
  legacy/index.html instead of the composer entry point; a <base> tag in
  that file resolves its own relative script/link tags against
  /funpack/app/legacy/ regardless of what URL it's served at, so every
  file moved as a unit with zero per-file path edits. Verified live: all
  61 assets load 200 OK, the real v4 onboarding screen renders pixel-for-
  pixel (it's the actual DOM/CSS, not a reimplementation), and several v4
  API calls (/api/media, /api/projects, /api/git/status) already succeed
  against v5's real backend by coincidence of shared route naming -- the
  rest 404/405 as expected, since api.js's ~90 functions haven't been
  rewired to v5's actual routes yet (next piece of this plan).

The old composer entry point (app/index.html, app/composer/**) stays in
the tree, unreferenced, until a later cleanup pass once nothing imports it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 23:58:51 +03:00

54 lines
2.5 KiB
JavaScript

// Pure scene→root matching for the global-prompt re-parse, extracted from store.js so it
// can be unit-tested in node (no DOM/state deps). The global prompt is authoritative; when
// it is re-parsed into scene slots, each slot must reuse the old scene root that owns its
// CONTENT — not the root that merely sits at the same array index. Matching by POSITION
// first was the "stale scene" bug: a reorder/insert/delete shifted indices and glued one
// scene's text onto another scene's anchor+render (then that wrong mapping got saved, so it
// survived restarts and key-clears). See scene_align.test.js for the regression cases.
//
// matchParsedToRoots(parsedTexts, rootTexts) → matchByIdx, where matchByIdx[i] is the index
// of the old root that parsed slot i should reuse, or -1 for none (→ ghost/new upstream).
// Both inputs are arrays of ALREADY-normalized text (caller applies its own normalizer so
// this stays the single source of matching truth without duplicating normalization).
(function (root) {
function matchParsedToRoots(parsedTexts, rootTexts) {
const n = parsedTexts.length;
const m = rootTexts.length;
const matchByIdx = new Array(n).fill(-1);
const usedRoot = new Array(m).fill(false);
// Pass 1 — exact text match, nearest original position breaks ties. Pins every
// unchanged scene to its own root through any reorder / insert / delete.
for (let i = 0; i < n; i++) {
const pt = parsedTexts[i];
if (!pt) continue; // empty slots can only be positional (Pass 2)
let best = -1;
let bestDist = Infinity;
for (let ri = 0; ri < m; ri++) {
if (usedRoot[ri]) continue;
if (rootTexts[ri] !== pt) continue;
const dist = Math.abs(ri - i);
if (dist < bestDist) { best = ri; bestDist = dist; }
}
if (best >= 0) { matchByIdx[i] = best; usedRoot[best] = true; }
}
// Pass 2 — leftover slots inherit the leftover roots IN ORDER (in-place text edits keep
// their render/anchor). A slot with no root left stays -1 → a fresh scene upstream.
let rr = 0;
for (let i = 0; i < n; i++) {
if (matchByIdx[i] >= 0) continue;
while (rr < m && usedRoot[rr]) rr++;
if (rr >= m) break;
matchByIdx[i] = rr;
usedRoot[rr] = true;
}
return matchByIdx;
}
const api = { matchParsedToRoots };
if (typeof module !== "undefined" && module.exports) module.exports = api; // node test
if (root) root.SceneAlign = api; // browser global
})(typeof window !== "undefined" ? window : null);