Here’s a clean, idempotent pattern you (and Replit) can keep as the gold standard for the setMasterTemplate / version promotion:

import {
  getFirestore, doc, collection, runTransaction, getDocs
} from "firebase/firestore";

type PromoteArgs = {
  slug: string;      // e.g. "business-plan-general-blank-template"
  version: string;   // e.g. "v1" or "v2"
};

export async function setMasterTemplate({ slug, version }: PromoteArgs) {
  const db = getFirestore();

  const slugRef = doc(db, "templates/business-plan/types", slug);
  const versionsCol = collection(db, `templates/business-plan/types/${slug}/versions`);
  const pointerRef = doc(db, "settings/templates.business-plan");

  await runTransaction(db, async (tx) => {
    // --- READ PHASE (all reads before any write) ---
    const [slugSnap, versionsSnap] = await Promise.all([
      tx.get(slugRef),
      tx.get(versionsCol) as any // Firestore SDK returns a QuerySnapshot via tx.get(collectionRef)
    ]);

    if (!slugSnap.exists()) throw new Error(`Slug not found: ${slug}`);

    const targetRef = doc(db, `templates/business-plan/types/${slug}/versions`, version);
    const targetSnap = await tx.get(targetRef);
    if (!targetSnap.exists()) throw new Error(`Version not found: ${slug}/${version}`);

    // (Optional) sanity: ensure target has assets
    const sp = targetSnap.get("storagePaths") || {};
    if (!(sp.docx && sp.preview && sp.thumb)) throw new Error("Target version missing storagePaths");

    // --- WRITE PHASE (no reads after this point) ---
    // Demote all versions (idempotent)
    versionsSnap.forEach((vDoc: any) => {
      tx.update(doc(db, `templates/business-plan/types/${slug}/versions`, vDoc.id), { isMaster: false });
    });

    // Promote target version
    tx.update(targetRef, { isMaster: true });

    // Update slug doc
    tx.update(slugRef, {
      isMaster: true,
      currentVersion: version,
      updatedAt: new Date()
    });

    // Update global pointer
    tx.set(pointerRef, {
      masterSlug: slug,
      masterVersion: version,
      lastRotatedAt: new Date()
    }, { merge: true });
  });
}

Why this is solid

All reads first (slug, all versions, target version).

Writes are idempotent (demote-all + promote-one).

Prevents transient “two masters” states.

Fails fast if assets aren’t present.

Quick post-fix verification

Upload → watch logs for “Transaction committed”.

In Firestore:

templates/business-plan/types/<slug> → currentVersion == yourVersion, isMaster: true.

Exactly one subdoc in /versions has isMaster: true.

settings/templates.business-plan points to the same slug/version.

Public GETs:

preview: /previews/<slug>/<ver>-preview.webp → 200

thumb: /thumbs/<slug>/<ver>-thumb.webp → 200