src/document/doc-utils.js/rec
function mapDoc(doc, cb) {
  // Within a doc tree, the same subtrees can be found multiple times.
  // E.g., often this happens in conditional groups.
  // As an optimization (those subtrees can be huge) and to maintain the
  // reference structure of the tree, the mapping results are cached in
  // a map and reused.
  const mapped = new Map();

  return rec(doc);

  function rec(doc) {
    if (mapped.has(doc)) {
      return mapped.get(doc);
    }
    const result = process(doc);
    mapped.set(doc, result);
    return result;
  }

  function process(doc) {
    if (Array.isArray(doc)) {
      return cb(doc.map(rec));
    }

    if (doc.type === "concat" || doc.type === "fill") {
      const parts = doc.parts.map(rec);
      return cb({ ...doc, parts });
    }

    if (doc.type === "if-break") {
      const breakContents = doc.breakContents && rec(doc.breakContents);
      const flatContents = doc.flatContents && rec(doc.flatContents);
      return cb({ ...doc, breakContents, flatContents });
    }

    if (doc.type === "group" && doc.expandedStates) {
      const expandedStates = doc.expandedStates.map(rec);
      const contents = expandedStates[0];
      return cb({ ...doc, contents, expandedStates });
    }

    if (doc.contents) {
      const contents = rec(doc.contents);
      return cb({ ...doc, contents });
    }

    return cb(doc);
  }
}