// `sectionEnter`: the flag on the first clip of each section, which is what // makes the legacy footer's marker slide from one node to the next // (report-to-video/build-video.mjs reads it; README "The marker slides"). // // report-to-video only ever READS it; the generators author it. This writes // the rule down for umtool's one writer that re-orders a timeline // (lib/report/manifest.mjs moveEntry), derived from what the builder reads and // checked against every real manifest: in the one that carries the flag // (quartering-employee-count, seven of nineteen clips) a clip carries it // exactly when its `section` differs from the previous CLIP's -- the first // clip counts, a card between two clips does not break a section, and a clip // with no `section` never enters one. Re-applying it to every real manifest // under ~/reports changes nothing (lib/report/sections.test.mjs has the shape). // // `section` itself is the author's: it says which chapter an entry belongs // to, and a move does not change that. Only the flag follows the order. // // A manifest that carries no `sectionEnter` key at all (every deck-era cut: // the deck draws no footer) is left exactly as it is: `applySectionEnter` // changes nothing unless some entry already carries the key. /** * The flag each entry should carry, by index: true on a clip whose `section` * is set and differs from the previous clip's; false everywhere else. * * @param {Array>} timeline * @returns {boolean[]} */ export function sectionEnterFlags(timeline) { let prev; return (timeline ?? []).map((e) => { if (e?.type !== "clip") return false; const s = e.section; const enters = s !== undefined && s !== null && s !== prev; prev = s; return enters; }); } /** Does this timeline use the flag at all? */ export const usesSectionEnter = (timeline) => (timeline ?? []).some((e) => e && Object.hasOwn(e, "sectionEnter")); /** * Recompute `sectionEnter` in place after a re-order. Only on a timeline that * already uses it; written as `true` or removed (an absent key reads as false, * and `"sectionEnter": false` is noise a human reads as a decision). Returns * the ids whose flag changed. * * @param {Array>} timeline * @returns {string[]} */ export function applySectionEnter(timeline) { if (!usesSectionEnter(timeline)) return []; const flags = sectionEnterFlags(timeline); const changed = []; timeline.forEach((e, i) => { const was = e.sectionEnter === true; if (flags[i] === was) return; if (flags[i]) e.sectionEnter = true; else delete e.sectionEnter; changed.push(String(e.id)); }); return changed; }