{
  "schema": "leumas.docs.page/1",
  "id": "pkg:@leumas/site-map",
  "slug": "packages/site-map",
  "kind": "capabilities",
  "bucket": "package",
  "title": "@leumas/site-map",
  "name": "@leumas/site-map",
  "eyebrow": null,
  "chip": null,
  "summary": "The live 'you are here' map of a product's navigation. Walks any resolved nav manifest into a graph of domains → pages → tabs → sub-tabs, locates the current pathname in it, emits it as mermaid, and...",
  "keywords": [
    "site-map",
    "locates",
    "pathname",
    "mermaid",
    "gta-style",
    "leumas site map",
    "radar",
    "navigate"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "# `@leumas/site-map`\n\nThe live **\"you are here\"** map of a product's navigation — a GTA-style radar you can navigate by\nclicking, plus the expanded panel behind it.\n\nIt walks any **resolved nav manifest** into a graph of `root → pillar → domain → page → tab →\nsub-tab`, locates the current pathname in it, and draws it two ways. It also **emits that graph as\nmermaid**, and `ops/tools/domain-diagrams/index.mjs` (`pnpm diagrams`) uses the same emitter — so the\n39 reference pages in `ops/reference/diagrams/` and the map in the HUD are one drawing routine, not\ntwo that agree until the next tab is renamed.\n\n## Using it\n\n```jsx\n/* SUBPATHS, never the barrel, on a boot path — see \"Things that will bite\". */\nimport { SiteMapProvider } from '@leumas/site-map/provider';\nimport { siteMapScreen } from '@leumas/site-map/screen';\nimport SiteMapRadarSlot from '@leumas/site-map/radar-slot';\nimport '@leumas/site-map/site-map.css';\n\n/* MODULE SCOPE. LeviathanWidget keys its open sheet on this array; a fresh identity per render\n   remounts the map under whoever is reading it. */\nconst ASSISTANT_SCREENS = [siteMapScreen()];\n\n<SiteMapProvider nav={resolved} pillars={PILLARS} rootLabel=\"Leumas Studio\" rootHref=\"/admin\">\n  <AppLayout assistantScreens={ASSISTANT_SCREENS}>\n    <SiteMapRadarSlot />\n    …\n  </AppLayout>\n</SiteMapProvider>\n```\n\n`SiteMapRadarSlot` is what makes the radar (and the panel's \"Pin as radar\") work. Mount it and forget\nit: while the radar is closed it reads one value from a store and renders null, and the radar's own\nmodule is never fetched.\n\nThe provider must sit **above** whatever draws the map. In Studio that means outside `AppLayout`,\nbecause `AppLayout` is what renders the Leviathan widget that hosts the map screen.\n\nFor a product with no assistant — the control plane runs `assistant={false}` on purpose — drop the\nscreen and keep the slot, which is the whole reason the radar is not Leviathan-only:\n\n```jsx\n<SiteMapProvider nav={VENDOR_NAV} rootLabel=\"Control plane\" rootHref=\"/\">\n  <AppLayout assistant={false}>\n    <SiteMapRadarSlot storageKey=\"leumas.siteMap.control.v1\" />\n    …\n  </AppLayout>\n</SiteMapProvider>\n```\n\n`storageKey` is **per product**. Sharing one would mean opening the radar in Studio opened it in the\ncontrol plane too.\n\n## The public surface\n\n| Import | What it is |\n|---|---|\n| `SiteMapProvider` / `useSiteMap()` | builds the graph once, re-locates on navigation. `{ graph, scoped, here, level, zoom }` |\n| `SiteMapRadarSlot` | **mount this**, not the radar — a store read while closed, lazy radar when opened |\n| `SiteMapRadar` | the compact floating radar itself — corner-docked, three sizes, closable |\n| `SiteMapPanel` | the expanded map: `[Radar ｜ Diagram]`, .mmd, Pin |\n| `SiteMapStage` | the HUD chrome + pan/zoom surface both renderers sit on |\n| `SiteMapView` | just the radar SVG, if you want to place it yourself |\n| `usePanZoom()` | drag-to-pan, wheel-to-zoom, fit-to-view — for any canvas-like child |\n| `siteMapScreen()` | a `LeviathanWidget` speed-dial screen descriptor |\n| `useNavConfirm(onGo)` / `NavConfirm` | the \"Go to X?\" gate — ONE gate both graphs share |\n| `setRadarOpen(key, open?)` | open/close/toggle the radar from anywhere — a menu, a shortcut |\n| `@leumas/site-map/graph` | **pure, no React** — `buildSiteGraph`, `buildDomainGraph`, `buildProductGraph`, `locate`, `scopeGraph` |\n| `@leumas/site-map/mermaid` | **pure, no React, no Node** — `toMermaid`, `escapeLabel`, `DIAGRAM_PALETTE` |\n\n## Things that will bite\n\n- **`./graph` and `./mermaid` must stay React-free.** `pnpm diagrams` imports them in bare Node with\n  no bundler. A React import there does not fail loudly; it breaks the script that keeps the\n  checked-in reference diagrams honest.\n- **`.lms-hud-tokens` on the host element is load-bearing.** The neon palette (`--hud-cyan`,\n  `--hud-violet`, `--hud-line`) is declared on `.lms-hud, .lms-hud-tokens` in `hud-shell.css`. Drop\n  the class and the widget renders as a flat borderless box **with no error anywhere**.\n- **An href can be claimed by two nodes.** Studio's pillars point straight at their headline domain,\n  so seven live paths have two claimants. `locate` breaks the tie by DEPTH, and `seal` must\n  **recompute** depth rather than trust the one a sub-graph already carried — trusting it made every\n  pillar tie with the domain beneath it and win by array order.\n- **`trail` vs `crumbs`.** Studio really does nest a Devices pillar → a Devices domain → a Devices\n  page. Light the `trail`; print the `crumbs`, which collapse a run of identical labels.\n- **Never import the barrel from a product's `App.jsx`.** No package here declares `sideEffects`, so\n  a bundler will not shake it: importing `SiteMapProvider` from `@leumas/site-map` measurably dragged\n  the panel, the SVG renderer and `treeLayout2D` onto Studio's boot path — 272.1KB gzip with subpaths\n  versus 284.8KB with the barrel. `/provider`, `/screen`, `/radar-slot`, `/store`, `/graph` and\n  `/mermaid` are the boot-path-safe entries.\n- **The viewBox is in PIXELS.** A 0..1 viewBox was the first attempt: `vector-effect: non-scaling-stroke`\n  rescues strokes there, but nothing rescues TEXT — a `font-size: 0.026px` label is clamped up to the\n  browser's minimum rendered size while its advances stay tiny, so every label draws as full-size\n  letters at hairline spacing.\n- **Mermaid is ~600KB and lives behind exactly one `lazy()` edge** (`MermaidView.jsx`). Nothing else\n  in the package imports it. `pnpm check:boot` is what proves that claim.\n- **Every pick is a QUESTION, not a jump.** `useNavConfirm` sits between the renderers and\n  `navigate()`, so a radar dot and a diagram box both raise \"Go to X?\" with Yes/No (Enter/Escape).\n  It is intercepted ONCE in `SiteMapPanel`, which is why neither renderer knows the gate exists and a\n  third would inherit it. NOT the kit's `ConfirmDialog` — that is a Modal for irreversible actions,\n  and `.st-modal__backdrop` is `position: fixed` and unportalled, which canvas-hud.css says is\n  trapped by a `<CanvasHud>`'s `container-type`.\n- **Float the gate off `--lms-chud-footer-h`**, the height CanvasHud measures with a ResizeObserver\n  (`canvas-hud.css:181` uses the same expression). A guessed offset landed the question on top of the\n  zoom control, because the footer's height depends on what a surface puts in it.\n- **The Diagram view is ALWAYS the whole domain**, deliberately — the zoom ladder\n  (`domain → pillar → site`) drives the RADAR only. The radar is an instrument that has to stay\n  legible; the diagram is what you open to see your section whole. Scoping the diagram to the ladder\n  meant \"zoom out\" quietly replaced the picture you asked for.\n- **Mermaid's node ids are NOT `flowchart-<id>-<n>`** — they carry the render id too\n  (`lms-smap-n_devices_door-2-flowchart-n_devices_door-0`). `idFromMermaidNode` splits on the LAST\n  `flowchart-`; anchoring at the start matched nothing and made every diagram node silently\n  unclickable.\n- **Fit with `offsetWidth`, never `getBoundingClientRect()`.** The rect is the TRANSFORMED box, so\n  fitting from it needs the current scale divided back out — and reading that scale inside a\n  ResizeObserver races the render that set it. `offsetWidth` is the untransformed layout size, and\n  because ResizeObserver does not fire on a transform, `fit()` writing a new scale cannot loop.\n- **Container queries against `.lms-chud` must NAME it** (`@container lms-chud (...)`).\n  `canvas-hud.css` sets `container-name` and warns why: `@leumas/inputs` puts an ANONYMOUS\n  `container-type: inline-size` on its controls, so an unnamed query resolves against whichever\n  slider is the nearest ancestor.\n- **`treeLayout2D` sorts siblings alphabetically**, so the radar is alphabetical where the sidebar is\n  authored. That is a property of the shared layout (it must be stable for data arriving in any\n  order), not something this package chose.\n",
  "source": {
    "path": "shared/packages/site-map/README.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 8242,
    "hash": "ba90d22068ca11bf913b9747754c0c89a0e1abc1"
  },
  "urls": {
    "html": "/p/packages/site-map",
    "json": "/docs/packages/site-map.json",
    "md": "/docs/packages/site-map.md"
  },
  "links": {
    "composes": [
      "pkg:@leumas/config-registry",
      "pkg:@leumas/entrance",
      "pkg:@leumas/theme",
      "pkg:@leumas/ui"
    ],
    "usedBy": [
      "pkg:@leumas/admin",
      "pkg:@leumas/studio"
    ],
    "product": [
      "pkg:@leumas/admin",
      "pkg:@leumas/studio"
    ],
    "howTo": [],
    "skills": []
  },
  "exports": {
    "total": 49,
    "component": 16,
    "hook": 4,
    "helper": 29,
    "names": [
      {
        "n": "DIAGRAM_PALETTE",
        "k": "component"
      },
      {
        "n": "DIAGRAM_PALETTE",
        "k": "component"
      },
      {
        "n": "DOMAIN_CLASSES",
        "k": "component"
      },
      {
        "n": "DOMAIN_CLASSES",
        "k": "component"
      },
      {
        "n": "LEVELS",
        "k": "component"
      },
      {
        "n": "LEVELS",
        "k": "component"
      },
      {
        "n": "NavConfirm",
        "k": "component"
      },
      {
        "n": "RADAR_KEY",
        "k": "component"
      },
      {
        "n": "RADAR_KEY",
        "k": "component"
      },
      {
        "n": "SiteMapPanel",
        "k": "component"
      },
      {
        "n": "SiteMapProvider",
        "k": "component"
      },
      {
        "n": "SiteMapProvider",
        "k": "component"
      },
      {
        "n": "SiteMapRadar",
        "k": "component"
      },
      {
        "n": "SiteMapRadarSlot",
        "k": "component"
      },
      {
        "n": "SiteMapStage",
        "k": "component"
      },
      {
        "n": "SiteMapView",
        "k": "component"
      },
      {
        "n": "useNavConfirm",
        "k": "hook"
      },
      {
        "n": "usePanZoom",
        "k": "hook"
      },
      {
        "n": "useSiteMap",
        "k": "hook"
      },
      {
        "n": "useSiteMap",
        "k": "hook"
      },
      {
        "n": "buildDomainGraph",
        "k": "helper"
      },
      {
        "n": "buildDomainGraph",
        "k": "helper"
      },
      {
        "n": "buildProductGraph",
        "k": "helper"
      },
      {
        "n": "buildProductGraph",
        "k": "helper"
      },
      {
        "n": "buildSiteGraph",
        "k": "helper"
      },
      {
        "n": "buildSiteGraph",
        "k": "helper"
      },
      {
        "n": "escapeLabel",
        "k": "helper"
      },
      {
        "n": "escapeLabel",
        "k": "helper"
      },
      {
        "n": "getRadarState",
        "k": "helper"
      },
      {
        "n": "getRadarState",
        "k": "helper"
      },
      {
        "n": "idFromMermaidNode",
        "k": "helper"
      },
      {
        "n": "locate",
        "k": "helper"
      },
      {
        "n": "locate",
        "k": "helper"
      },
      {
        "n": "nid",
        "k": "helper"
      },
      {
        "n": "nid",
        "k": "helper"
      },
      {
        "n": "normalizeRadarState",
        "k": "helper"
      },
      {
        "n": "normalizeRadarState",
        "k": "helper"
      },
      {
        "n": "scopeGraph",
        "k": "helper"
      },
      {
        "n": "scopeGraph",
        "k": "helper"
      },
      {
        "n": "setRadarOpen",
        "k": "helper"
      },
      {
        "n": "setRadarOpen",
        "k": "helper"
      },
      {
        "n": "setRadarState",
        "k": "helper"
      },
      {
        "n": "setRadarState",
        "k": "helper"
      },
      {
        "n": "siteMapScreen",
        "k": "helper"
      },
      {
        "n": "siteMapScreen",
        "k": "helper"
      },
      {
        "n": "subscribeRadar",
        "k": "helper"
      },
      {
        "n": "subscribeRadar",
        "k": "helper"
      },
      {
        "n": "toMermaid",
        "k": "helper"
      },
      {
        "n": "toMermaid",
        "k": "helper"
      }
    ]
  }
}
