tmp/webapp-docs/src/lib/wrapperStore.js

/**
 * Browser-local "wrapper" presets: a named pair of DPL snippets — a START and an END —
 * that frame the prompt (the v3 root layer = open + middle + close). Stored in localStorage
 * only, the no-server equivalent of a saved wrapper. See notes/plans/v3-layers.md.
 * @module web-app/lib/wrapperStore
 */

const KEY = "rap.wrappers.v1";
const DEFAULT_KEY = "rap.wrapper.default.v1";

// The hard-coded SEED for the built-in "Default" wrapper. Derived from the most common v2 prompt
// framing: a quality lead-in as the START, and the auto fx/artists + the recurring finishing tags
// (intricate, sharp focus, wide shot, volumetric light, dap) as the END — written in DPL so the
// probabilities apply. This is the immutable fallback; the live Default (below) is a copy of this
// that the user can edit, and which is re-created from this seed whenever it is reset/deleted.
// See notes/plans/v3-layers.md.
export const DEFAULT_WRAPPER_SEED = {
  start: "masterpiece, best quality, highly detailed",
  end: "{#fx}, {#artists}\n- intricate detail\n- sharp focus\n- 50% wide shot\n- {#rays}\n- 35% {#dap}"
};

// Back-compat alias: existing callers import DEFAULT_WRAPPER. It now reflects the *seed*; use
// getDefaultWrapper() to read the live (possibly user-edited) Default.
export const DEFAULT_WRAPPER = DEFAULT_WRAPPER_SEED;

/**
 * The live "Default" wrapper: the user-editable copy of the seed. It behaves like a preset you
 * can't delete — if its backing entry is missing (never edited, or reset), it is the seed.
 * @returns {{start: string, end: string}} The live default.
 */
export function getDefaultWrapper() {
  try {
    const raw = localStorage.getItem(DEFAULT_KEY);
    if (raw) {
      const o = JSON.parse(raw);
      return {
        start: o.start || "",
        end: o.end || ""
      };
    }
  } catch {
    // fall through to seed
  }
  return {
    ...DEFAULT_WRAPPER_SEED
  };
}

/**
 * Persist edits to the live Default wrapper (kept local; never overwritten unless reset).
 * @param {{start: string, end: string}} value The edited start/end.
 * @returns {void}
 */
export function saveDefaultWrapper(value) {
  try {
    localStorage.setItem(DEFAULT_KEY, JSON.stringify({
      start: value.start || "",
      end: value.end || ""
    }));
  } catch {
    // best-effort
  }
}

/**
 * Reset the Default wrapper back to the hard-coded seed (drops the local edit).
 * @returns {void}
 */
export function resetDefaultWrapper() {
  try {
    localStorage.removeItem(DEFAULT_KEY);
  } catch {
    // best-effort
  }
}

/** @returns {object} The wrapper presets (`{ name: { start, end } }`). */
function read() {
  try {
    return JSON.parse(localStorage.getItem(KEY) || "{}");
  } catch {
    return {};
  }
}
/** @param {object} obj The presets to persist. @returns {void} */
function write(obj) {
  try {
    localStorage.setItem(KEY, JSON.stringify(obj));
  } catch {
    // best-effort
  }
}

/** @returns {object} All wrapper presets (`{ name: { start, end } }`). */
export function getWrappers() {
  return read();
}
/**
 * Save (or overwrite) a wrapper preset.
 * @param {string} name The preset name.
 * @param {{start: string, end: string}} value The start/end snippets.
 * @returns {void}
 */
export function saveWrapper(name, value) {
  const o = read();
  o[name] = {
    start: value.start || "",
    end: value.end || ""
  };
  write(o);
}
/**
 * Rename a wrapper preset (no-op if `from` is missing or `to` is blank).
 * @param {string} from The current name.
 * @param {string} to The new name.
 * @returns {void}
 */
export function renameWrapper(from, to) {
  const o = read();
  if (!(from in o) || !to || from === to) return;
  o[to] = o[from];
  delete o[from];
  write(o);
}
/** @param {string} name The preset to remove. @returns {void} */
export function removeWrapper(name) {
  const o = read();
  delete o[name];
  write(o);
}