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>
50 lines
2.1 KiB
JavaScript
50 lines
2.1 KiB
JavaScript
// Reference numbering — the "R1, R2, …" a marked media item shows.
|
|
//
|
|
// The number is counted WITHIN the item's own kind, because that is how a reference slot
|
|
// actually resolves: builder._reference_by_slot takes the marks of one kind, in mark order,
|
|
// and picks the nth. "Reference video 1" is the first video marked, not the first mark
|
|
// overall.
|
|
//
|
|
// Numbering the marks globally instead made the two disagree, and the disagreement was not
|
|
// cosmetic: mark an image and then a video, and the video is labelled R2 everywhere while
|
|
// the only slot that can reach it is "Reference video 1". There was no way to have an R1
|
|
// video and an R1 image at the same time, so one of a matched pair always looked like the
|
|
// second of something.
|
|
(function () {
|
|
"use strict";
|
|
|
|
/** Every marked reference of `kind`, in mark order, as media-bin entries. */
|
|
function referencesOfKind(marks, bin, kind) {
|
|
const byId = new Map((bin || []).map((m) => [m.id, m]));
|
|
return (marks || [])
|
|
.map((id) => byId.get(id))
|
|
.filter((m) => m && (m.kind || "image") === kind);
|
|
}
|
|
|
|
/** The R number this media shows, counted within its own kind. 0 when it is not marked
|
|
* (or not in the bin — an id left in the list by a deleted file is not a reference). */
|
|
function referenceNumber(marks, bin, mediaId) {
|
|
const byId = new Map((bin || []).map((m) => [m.id, m]));
|
|
const self = byId.get(mediaId);
|
|
if (!self || !(marks || []).includes(mediaId)) return 0;
|
|
const kind = self.kind || "image";
|
|
let n = 0;
|
|
for (const id of marks) {
|
|
const m = byId.get(id);
|
|
if (!m || (m.kind || "image") !== kind) continue;
|
|
n += 1;
|
|
if (id === mediaId) return n;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** How many references share this media's kind — the "of N" half of the badge. */
|
|
function referenceCountOfKind(marks, bin, kind) {
|
|
return referencesOfKind(marks, bin, kind).length;
|
|
}
|
|
|
|
const api = { referenceNumber, referenceCountOfKind, referencesOfKind };
|
|
if (typeof module !== "undefined" && module.exports) module.exports = api;
|
|
if (typeof window !== "undefined") window.References = api;
|
|
})();
|