/**
* setSlideshowManifest — Studio persist helper for the slideshow JSON island.
*
* The island is a `
// blocks (global + case-insensitive) so we can strip every stale island in one pass.
const ISLAND_RE = slideshowIslandRegex("gi");
export function buildSlideshowIslandHtml(manifest: SlideshowManifest): string {
// Stamp the schema version (preserve an existing one) so future schema
// changes can detect and migrate older islands.
const versioned: SlideshowManifest = {
version: manifest.version ?? SLIDESHOW_MANIFEST_VERSION,
...manifest,
};
// Escape `<` and `>` so that a manifest field containing `` cannot
// break out of the script tag. JSON.parse round-trips > unchanged.
const json = JSON.stringify(versioned, null, 2).replace(//g, "\\u003e");
return ``;
}
export interface PersistSlideshowArgs {
manifest: SlideshowManifest;
/** Live SDK Composition session — used only to read the current serialized HTML. */
sdkSession: Pick;
/** Exact on-disk bytes for the undo-history `before` baseline. */
originalContent: string;
targetPath: string;
deps: CutoverDeps;
/** Optional label override (default: "Edit slideshow"). */
label?: string;
/**
* When provided, threads a coalesceKey into recordEdit so rapid writes
* (e.g. per-keystroke notes changes) collapse to a single undo entry.
*/
coalesceKey?: string;
}
export async function persistSlideshowManifest(args: PersistSlideshowArgs): Promise {
const { manifest, sdkSession, originalContent, targetPath, deps, label, coalesceKey } = args;
const islandHtml = buildSlideshowIslandHtml(manifest);
// Write-time validation: confirm the island we just built round-trips to a
// valid manifest before touching disk, so a malformed edit can't corrupt the
// composition. parseSlideshowManifest throws on a structurally-invalid island.
try {
if (!parseSlideshowManifest(islandHtml)) {
throw new Error("built island did not parse back to a manifest");
}
} catch (err) {
throw new Error(`refusing to persist invalid slideshow manifest: ${(err as Error).message}`);
}
const current = sdkSession.serialize();
// Strip ALL existing islands (handles the case where two stale islands
// accumulated) then insert exactly one fresh island.
const stripped = current.replace(ISLAND_RE, "");
let after: string;
const bodyClose = stripped.lastIndexOf("