{"version":3,"sources":["../src/bundle.ts"],"sourcesContent":["// The bundle scope — what a component rendered INSIDE a bundle may know about it\n// (R3-174; MDX_FROM_MOUNT_SPEC §2, §7 1a).\n//\n// A bundle viewer can register components the bundle itself ships, so an entry writes\n// `<ProjectIndex/>` and the viewer resolves it — no engine fork, no import line\n// (`MDX_FROM_MOUNT_SPEC` §2's \"provider composition is consumer policy\"). Those components\n// live in the CONTENT filesystem and the viewer lives in its own, and the seam between\n// them is this package: a content component cannot import the viewer (it would resolve a\n// second copy from the registry, with its own module state, and read the wrong bundle), so\n// anything the viewer must tell it has to arrive through a surface both sides already\n// share. This is that surface.\n//\n// **Why paths here are bundle-absolute, not filesystem-absolute.** The metadata store is\n// keyed by absolute module path, which under dispatch means `/mnt/<hash>/roadmap/x.mdx` —\n// the host-minted chroot. That prefix is host knowledge a viewer may read THROUGH but must\n// never publish (the property `grove/src/components/DirectoryList.test.tsx` pins with\n// `expect(el.innerHTML).not.toContain('mnt')`), and it is not stable across loads. A\n// content component that saw it would embed it in hrefs and keys. So the bundle scope\n// rebases every path it hands out to the bundle root (`/roadmap/x.mdx`), which is the same\n// address under fork, library and dispatch composition — `PLATFORM_LAYERING_SPEC` §1.1's\n// mode-invariance rule, applied to metadata rather than to links.\n//\n// **Why the href mapping is injected rather than derived.** Bundle path → in-app URL is\n// VIEWER policy, and the two packagings genuinely disagree (a fork's URLs are anchored at\n// its app root and are already published and cited; a dispatched viewer's are bundle-\n// relative). A content component must not have to know which it is in, and the SDK must\n// not guess — so the viewer supplies `toHref` and content never computes a URL itself.\n\nimport { createContext, use, useMemo } from 'react';\nimport { useMetadataStore } from './metadataSource';\nimport type { Metadata } from './sandboxTypes';\n\n/** What a bundle viewer declares about the bundle it is rendering. */\nexport interface BundleScope {\n  /**\n   * Absolute filesystem path of the bundle root, WITHOUT a trailing slash\n   * (`/app/content` in a fork, `/mnt/<hash>` under dispatch), or `null` when the\n   * surrounding app is not rendering a bundle — the default, which leaves every hook here\n   * returning the empty result rather than guessing a root.\n   */\n  root: string | null;\n  /** Bundle-absolute path of the entry currently being read (`/roadmap/R3-174.mdx`), or\n   *  `null` when nothing is. This is the entry, NOT the file being rendered: a component\n   *  in a `_layout.mdx` wrapping that entry sees the entry, which is what makes furniture\n   *  in the layout chain (a status line, a dependency rail) able to describe the page. */\n  entry: string | null;\n  /** Bundle-absolute path → the href to navigate to it. Supplied by the viewer; the\n   *  identity function by default, which is correct for a viewer whose URL space IS the\n   *  bundle space. */\n  toHref: (bundlePath: string) => string;\n}\n\nconst EMPTY_SCOPE: BundleScope = { root: null, entry: null, toHref: (p) => p };\n\n/** Ambient bundle scope. A bundle-rendering app wraps its content tree in\n *  `<BundleContext value={{ root, entry, toHref }}>`; nesting a second provider inside a\n *  rendered sub-bundle makes the innermost win, as with {@link LinkSpaceContext}. */\nexport const BundleContext = createContext<BundleScope>(EMPTY_SCOPE);\n\n/** Read the ambient bundle scope. Every hook below is a convenience over this. */\nexport const useBundle = (): BundleScope => use(BundleContext);\n\n/**\n * Filesystem-absolute path → bundle-absolute, or `null` when the path is not inside the\n * bundle. Exact-root and separator-boundary aware, so `/mnt/hash2/x` is not treated as\n * living under `/mnt/hash`.\n */\nexport const toBundlePath = (absolute: string, root: string | null): string | null => {\n  if (root === null) return null;\n  const base = root.replace(/\\/+$/, '');\n  if (base === '') return absolute;\n  if (!absolute.startsWith(`${base}/`)) return null;\n  return absolute.slice(base.length);\n};\n\n/** Bundle-absolute path → filesystem-absolute, or `null` without a root. The inverse of\n *  {@link toBundlePath}, for the rare content component that must read raw bytes. */\nexport const fromBundlePath = (bundlePath: string, root: string | null): string | null => {\n  if (root === null) return null;\n  const base = root.replace(/\\/+$/, '');\n  return `${base}${bundlePath.startsWith('/') ? '' : '/'}${bundlePath}`;\n};\n\n/** One entry as content sees it: where it is, where it links, and its frontmatter. */\nexport interface BundleEntry<T = Metadata> {\n  /** Bundle-absolute path (`/roadmap/R3-174.mdx`). */\n  path: string;\n  /** The href that navigates to it, per the viewer's mapping. */\n  href: string;\n  meta: T;\n}\n\n/**\n * Every entry in the surrounding bundle, with bundle-absolute paths and viewer-supplied\n * hrefs — the surface a content component queries instead of {@link useMetadataQuery},\n * whose keys are filesystem-absolute and therefore carry the mount prefix.\n *\n * Returns an empty array outside a bundle, so a component written for a bundle renders\n * nothing rather than throwing when someone drops it elsewhere. The array keeps its\n * identity while the store and root are unchanged, so it is safe in dependency arrays.\n */\nexport const useBundleEntries = <T = Metadata>(): BundleEntry<T>[] => {\n  const files = useMetadataStore<T>();\n  const { root, toHref } = useBundle();\n  return useMemo(() => {\n    if (root === null) return [];\n    const out: BundleEntry<T>[] = [];\n    for (const [absolute, meta] of Object.entries(files)) {\n      const path = toBundlePath(absolute, root);\n      if (path === null) continue;\n      out.push({ path, href: toHref(path), meta: meta as T });\n    }\n    // Path order, so a consumer that does not sort still renders deterministically\n    // (object key order is insertion order, which is scan order, which is not stable).\n    out.sort((a, b) => a.path.localeCompare(b.path));\n    return out;\n  }, [files, root, toHref]);\n};\n\n/** One entry's frontmatter by bundle-absolute path, or `undefined`. */\nexport const useBundleEntry = <T = Metadata>(bundlePath: string): T | undefined => {\n  const files = useMetadataStore<T>();\n  const { root } = useBundle();\n  return useMemo(() => {\n    const absolute = fromBundlePath(bundlePath, root);\n    return absolute === null ? undefined : files[absolute];\n  }, [bundlePath, files, root]);\n};\n\n/** The entry currently being read — `null` outside a bundle, or when the viewer declares\n *  no entry (a directory listing, a 404). See {@link BundleScope.entry} for why this is\n *  the entry rather than the file the component happens to be written in. */\nexport const useCurrentEntry = <T = Metadata>(): BundleEntry<T> | null => {\n  const files = useMetadataStore<T>();\n  const { root, entry, toHref } = useBundle();\n  return useMemo(() => {\n    if (root === null || entry === null) return null;\n    const absolute = fromBundlePath(entry, root);\n    const meta = absolute === null ? undefined : files[absolute];\n    if (meta === undefined) return null;\n    return { path: entry, href: toHref(entry), meta: meta as T };\n  }, [files, root, entry, toHref]);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4BA,mBAA4C;AAC5C,4BAAiC;AAuBjC,MAAM,cAA2B,EAAE,MAAM,MAAM,OAAO,MAAM,QAAQ,CAAC,MAAM,EAAE;AAKtE,MAAM,oBAAgB,4BAA2B,WAAW;AAG5D,MAAM,YAAY,UAAmB,kBAAI,aAAa;AAOtD,MAAM,eAAe,CAAC,UAAkB,SAAuC;AACpF,MAAI,SAAS,KAAM,QAAO;AAC1B,QAAM,OAAO,KAAK,QAAQ,QAAQ,EAAE;AACpC,MAAI,SAAS,GAAI,QAAO;AACxB,MAAI,CAAC,SAAS,WAAW,GAAG,IAAI,GAAG,EAAG,QAAO;AAC7C,SAAO,SAAS,MAAM,KAAK,MAAM;AACnC;AAIO,MAAM,iBAAiB,CAAC,YAAoB,SAAuC;AACxF,MAAI,SAAS,KAAM,QAAO;AAC1B,QAAM,OAAO,KAAK,QAAQ,QAAQ,EAAE;AACpC,SAAO,GAAG,IAAI,GAAG,WAAW,WAAW,GAAG,IAAI,KAAK,GAAG,GAAG,UAAU;AACrE;AAoBO,MAAM,mBAAmB,MAAsC;AACpE,QAAM,YAAQ,wCAAoB;AAClC,QAAM,EAAE,MAAM,OAAO,IAAI,UAAU;AACnC,aAAO,sBAAQ,MAAM;AACnB,QAAI,SAAS,KAAM,QAAO,CAAC;AAC3B,UAAM,MAAwB,CAAC;AAC/B,eAAW,CAAC,UAAU,IAAI,KAAK,OAAO,QAAQ,KAAK,GAAG;AACpD,YAAM,OAAO,aAAa,UAAU,IAAI;AACxC,UAAI,SAAS,KAAM;AACnB,UAAI,KAAK,EAAE,MAAM,MAAM,OAAO,IAAI,GAAG,KAAgB,CAAC;AAAA,IACxD;AAGA,QAAI,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAC/C,WAAO;AAAA,EACT,GAAG,CAAC,OAAO,MAAM,MAAM,CAAC;AAC1B;AAGO,MAAM,iBAAiB,CAAe,eAAsC;AACjF,QAAM,YAAQ,wCAAoB;AAClC,QAAM,EAAE,KAAK,IAAI,UAAU;AAC3B,aAAO,sBAAQ,MAAM;AACnB,UAAM,WAAW,eAAe,YAAY,IAAI;AAChD,WAAO,aAAa,OAAO,SAAY,MAAM,QAAQ;AAAA,EACvD,GAAG,CAAC,YAAY,OAAO,IAAI,CAAC;AAC9B;AAKO,MAAM,kBAAkB,MAA2C;AACxE,QAAM,YAAQ,wCAAoB;AAClC,QAAM,EAAE,MAAM,OAAO,OAAO,IAAI,UAAU;AAC1C,aAAO,sBAAQ,MAAM;AACnB,QAAI,SAAS,QAAQ,UAAU,KAAM,QAAO;AAC5C,UAAM,WAAW,eAAe,OAAO,IAAI;AAC3C,UAAM,OAAO,aAAa,OAAO,SAAY,MAAM,QAAQ;AAC3D,QAAI,SAAS,OAAW,QAAO;AAC/B,WAAO,EAAE,MAAM,OAAO,MAAM,OAAO,KAAK,GAAG,KAAgB;AAAA,EAC7D,GAAG,CAAC,OAAO,MAAM,OAAO,MAAM,CAAC;AACjC;","names":[]}