{"version":3,"sources":["../src/detection.ts","../src/theme-defaults.ts"],"names":[],"mappings":";;;AAGA,IAAM,aAAA,GAAuB;AAAA,EAC3B,IAAA,EAAM,OAAA;AAAA,EACN,QAAQ;AACV,CAAA;AAEO,SAAS,aAAa,GAAA,EAAmD;AAC9E,EAAA,IAAI,CAAC,GAAA,EAAK,OAAO,EAAE,GAAG,aAAA,EAAc;AAGpC,EAAA,MAAM,IAAA,GAAO,IAAI,KAAA,KAAU,OAAA,IAAW,IAAI,KAAA,KAAU,MAAA,GAAS,GAAA,CAAI,KAAA,GAAQ,aAAA,CAAc,IAAA;AAGvF,EAAA,MAAM,SAAA,GAAY,IAAI,MAAA,EAAQ,SAAA;AAC9B,EAAA,MAAM,MAAA,GACJ,SAAA,IAAa,OAAO,SAAA,KAAc,QAAA,IAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,SAAS,CAAA,GACjE,SAAA,GACD,EAAC;AAEP,EAAA,OAAO,EAAE,MAAM,MAAA,EAAO;AACxB;AAMO,SAAS,mBAAmB,GAAA,EAAgE;AACjG,EAAA,MAAM,KAAA,GAAQ,GAAA,EAAK,MAAA,EAAQ,GAAA,EAAK,KAAA;AAChC,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,KAAK,KAAA,GAAQ,MAAA;AAC7D;;;ACgBA,IAAM,KAAA,GAAgC;AAAA;AAAA,EAEpC,4BAAA,EAA8B,SAAA;AAAA,EAC9B,8BAAA,EAAgC,SAAA;AAAA,EAChC,6BAAA,EAA+B,SAAA;AAAA;AAAA,EAE/B,sBAAA,EAAwB,SAAA;AAAA,EACxB,wBAAA,EAA0B,SAAA;AAAA,EAC1B,uBAAA,EAAyB,SAAA;AAAA,EACzB,qBAAA,EAAuB,SAAA;AAAA,EACvB,8BAAA,EAAgC,SAAA;AAAA;AAAA,EAEhC,wBAAA,EAA0B,SAAA;AAAA,EAC1B,0BAAA,EAA4B,SAAA;AAAA,EAC5B,sBAAA,EAAwB,SAAA;AAAA;AAAA,EAExB,mBAAA,EAAqB,SAAA;AAAA,EACrB,8BAAA,EAAgC,SAAA;AAAA,EAChC,oBAAA,EAAsB,SAAA;AAAA,EACtB,oBAAA,EAAsB,SAAA;AAAA,EACtB,uBAAA,EAAyB,SAAA;AAAA,EACzB,6BAAA,EAA+B,SAAA;AAAA,EAC/B,uBAAA,EAAyB;AAC3B,CAAA;AASA,IAAM,IAAA,GAA+B;AAAA;AAAA,EAEnC,4BAAA,EAA8B,SAAA;AAAA,EAC9B,8BAAA,EAAgC,SAAA;AAAA,EAChC,6BAAA,EAA+B,SAAA;AAAA;AAAA,EAE/B,sBAAA,EAAwB,SAAA;AAAA,EACxB,wBAAA,EAA0B,SAAA;AAAA,EAC1B,uBAAA,EAAyB,SAAA;AAAA,EACzB,qBAAA,EAAuB,SAAA;AAAA,EACvB,8BAAA,EAAgC,SAAA;AAAA;AAAA,EAEhC,wBAAA,EAA0B,SAAA;AAAA,EAC1B,0BAAA,EAA4B,SAAA;AAAA,EAC5B,sBAAA,EAAwB,SAAA;AAAA;AAAA,EAExB,mBAAA,EAAqB,SAAA;AAAA;AAAA,EAErB,8BAAA,EAAgC,SAAA;AAAA,EAChC,oBAAA,EAAsB,SAAA;AAAA,EACtB,oBAAA,EAAsB,SAAA;AAAA,EACtB,uBAAA,EAAyB,SAAA;AAAA,EACzB,6BAAA,EAA+B,SAAA;AAAA,EAC/B,uBAAA,EAAyB;AAC3B,CAAA;AAMO,IAAM,kBAAA,GAAuE;AAAA,EAClF,KAAA,EAAO,KAAA;AAAA,EACP,IAAA,EAAM;AACR,CAAA;AAGO,IAAM,mBAAA,GAAsB,kBAAA;AAG5B,IAAM,iBAAA,GAAoB,wBAAA;AAIjC,IAAI,iBAAA,uBAAwB,GAAA,EAAY;AAsBxC,SAAS,oBAAA,GAAgC;AACvC,EAAA,OACE,OAAO,QAAA,KAAa,WAAA,IACpB,OAAO,SAAS,cAAA,KAAmB,UAAA,IACnC,OAAO,QAAA,CAAS,aAAA,KAAkB,UAAA,IAClC,OAAO,QAAA,CAAS,MAAM,OAAA,KAAY,UAAA;AAEtC;AAuBA,SAAS,uBAAuB,IAAA,EAA8B;AAC5D,EAAA,IAAI,CAAC,sBAAqB,EAAG;AAM7B,EAAA,MAAM,YAAA,GAAe,OAAO,OAAA,CAAQ,kBAAA,CAAmB,IAAI,CAAC,CAAA,CACzD,IAAI,CAAC,CAAC,GAAG,CAAC,CAAA,KAAM,OAAO,CAAC,CAAA,EAAA,EAAK,CAAC,CAAA,CAAA,CAAG,CAAA,CACjC,KAAK,IAAI,CAAA;AACZ,EAAA,MAAM,GAAA,GAAM,UAAU,mBAAmB,CAAA;AAAA;AAAA,EAAkB,YAAY;AAAA;AAAA,CAAA,CAAA;AAEvE,EAAA,MAAM,QAAA,GAAW,QAAA,CAAS,cAAA,CAAe,iBAAiB,CAAA;AAC1D,EAAA,IAAI,QAAA,EAAU;AAEZ,IAAA,IAAI,QAAA,CAAS,WAAA,KAAgB,GAAA,EAAK,QAAA,CAAS,WAAA,GAAc,GAAA;AACzD,IAAA;AAAA,EACF;AACA,EAAA,MAAM,EAAA,GAAK,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA;AACzC,EAAA,EAAA,CAAG,EAAA,GAAK,iBAAA;AACR,EAAA,EAAA,CAAG,WAAA,GAAc,GAAA;AAIjB,EAAA,QAAA,CAAS,IAAA,CAAK,QAAQ,EAAE,CAAA;AAC1B;AA2CO,SAAS,mBAAA,CACd,MACA,QAAA,EACM;AACN,EAAA,IAAI,OAAO,aAAa,WAAA,EAAa;AACrC,EAAA,sBAAA,CAAuB,IAAI,CAAA;AAU3B,EAAA,MAAM,WAAmC,EAAC;AAC1C,EAAA,IAAI,QAAA,IAAY,OAAO,QAAA,KAAa,QAAA,EAAU;AAC5C,IAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,KAAK,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA,EAAG;AAC7C,MAAA,IAAI,OAAO,MAAM,QAAA,IAAY,OAAO,MAAM,QAAA,EAAU,QAAA,CAAS,CAAC,CAAA,GAAI,CAAA;AAAA,IACpE;AAAA,EACF;AAEA,EAAA,MAAM,IAAA,GAAO,SAAS,eAAA,CAAgB,KAAA;AAQtC,EAAA,IAAI,OAAO,IAAA,CAAK,cAAA,KAAmB,UAAA,EAAY;AAC7C,IAAA,KAAA,MAAW,KAAK,iBAAA,EAAmB;AACjC,MAAA,IAAI,EAAE,CAAA,IAAK,QAAA,CAAA,EAAW,IAAA,CAAK,eAAe,CAAC,CAAA;AAAA,IAC7C;AAAA,EACF;AACA,EAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,CAAA,IAAK,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA,EAAG,IAAA,CAAK,WAAA,CAAY,CAAA,EAAG,CAAC,CAAA;AACpE,EAAA,iBAAA,GAAoB,IAAI,GAAA,CAAI,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAC,CAAA;AACnD;AAQO,SAAS,UAAA,CACd,MACA,QAAA,EACM;AACN,EAAA,mBAAA,CAAoB,MAAM,QAAQ,CAAA;AACpC","file":"chunk-YSL7KJNU.cjs","sourcesContent":["import type { McpUiHostContext } from \"@modelcontextprotocol/ext-apps\";\nimport type { Theme } from \"./types\";\n\nconst DEFAULT_THEME: Theme = {\n  mode: \"light\",\n  tokens: {},\n};\n\nexport function extractTheme(ctx: Partial<McpUiHostContext> | undefined): Theme {\n  if (!ctx) return { ...DEFAULT_THEME };\n\n  // Spec: theme is a string (\"light\" | \"dark\")\n  const mode = ctx.theme === \"light\" || ctx.theme === \"dark\" ? ctx.theme : DEFAULT_THEME.mode;\n\n  // Spec: tokens live under styles.variables\n  const variables = ctx.styles?.variables;\n  const tokens =\n    variables && typeof variables === \"object\" && !Array.isArray(variables)\n      ? (variables as Record<string, string>)\n      : {};\n\n  return { mode, tokens };\n}\n\n/**\n * The host's `@font-face` CSS, from the spec's `styles.css.fonts`, or\n * `undefined` when this context carries none.\n */\nexport function extractHostFontCss(ctx: Partial<McpUiHostContext> | undefined): string | undefined {\n  const fonts = ctx?.styles?.css?.fonts;\n  return typeof fonts === \"string\" && fonts !== \"\" ? fonts : undefined;\n}\n","/**\n * Theme-aware default backing for the `@nimblebrain/synapse/ui` token contract.\n *\n * `tokens` (see `ui/tokens.ts`) are `var(--token, fallback)` references. A CSS\n * `var()` fallback is a *static literal* — it cannot branch on light vs. dark.\n * So any var the host does NOT inject resolves to its single hardcoded (light)\n * fallback in BOTH themes. Pair such a token with a theme-aware one and you get\n * white-on-white in dark mode: it looks correct in light, passes `tsc`/build,\n * and only breaks when the theme is toggled.\n *\n * This module closes that gap with a default layer the SDK controls and that\n * CAN branch on theme. {@link applyThemeVariables} puts the neutral defaults for\n * the active `mode` in a cascade layer and the host's variables inline — so:\n *   - the host's (brand) values always win for the keys it provides, through\n *     either the protocol or a stylesheet it injects, and\n *   - any var nobody declares still resolves to a theme-correct neutral value.\n *\n * It's the `var()` fallback, but able to branch on theme. The values stay\n * neutral grays, a generic blue, and generic semantic hues for danger, success,\n * warning and processing — never a *brand* value. Brand arrives only by host\n * injection, mirroring `ui/tokens.ts`. `__tests__/ui/tokens.test.ts` holds the\n * line: every hex here must be in the sanctioned set. This keeps\n * the library host-agnostic while guaranteeing every token resolves correctly\n * in both themes even against an incomplete host, a standalone `connect()`\n * widget, or a third-party host.\n *\n * Font *faces* are the one part of theming CSS variables cannot carry — a token\n * names a family, it cannot load one. The spec carries them separately, as\n * `@font-face` CSS in `hostContext.styles.css.fonts`, and each client applies\n * that string itself. The CSS is injected as the host sent it: it is trusted,\n * because the host already controls the frame it renders into.\n *\n * Only theme-sensitive (color) vars are listed here. Theme-invariant vars\n * (radii, type scale, shadows, font stacks, weights, border widths) look the\n * same in both themes, so their static `var()` fallback is already correct —\n * they are intentionally absent. The `tokens` regression test enforces this\n * with a TOTAL partition: every referenced var must be either declared\n * theme-invariant (an explicit allowlist) or defined in both maps below, so a\n * newly added theme-sensitive token can't slip through unbacked.\n */\n\n/**\n * Light-theme neutral defaults. Values match the (light) fallbacks baked into\n * `tokens` — applying them changes nothing observable in light mode; they exist\n * so the light path flows through the same code as dark and so a standalone\n * render with an explicit `light` theme is backed identically to its fallbacks.\n */\nconst LIGHT: Record<string, string> = {\n  // ── Surfaces ──\n  \"--color-background-primary\": \"#ffffff\",\n  \"--color-background-secondary\": \"#fafafa\",\n  \"--color-background-tertiary\": \"#f3f4f6\",\n  // ── Text ──\n  \"--color-text-primary\": \"#111827\",\n  \"--color-text-secondary\": \"#6b7280\",\n  \"--color-text-tertiary\": \"#9ca3af\",\n  \"--color-text-accent\": \"#2563eb\",\n  \"--nb-color-accent-foreground\": \"#ffffff\",\n  // ── Border / ring ──\n  \"--color-border-primary\": \"#e5e7eb\",\n  \"--color-border-secondary\": \"#d1d5db\",\n  \"--color-ring-primary\": \"#2563eb\",\n  // ── Status / brand semantics ──\n  \"--nb-color-danger\": \"#dc2626\",\n  \"--nb-color-danger-foreground\": \"#ffffff\",\n  \"--nb-color-success\": \"#059669\",\n  \"--nb-color-warning\": \"#f59e0b\",\n  \"--nb-color-processing\": \"#7c3aed\",\n  \"--nb-color-processing-light\": \"#f3eeff\",\n  \"--nb-color-info-light\": \"#eef4ff\",\n};\n\n/**\n * Dark-theme neutral defaults. A monotonic neutral ladder — the three surface\n * tiers are the darkest, borders sit a step lighter so they stay visible\n * against every surface, and text inverts to light. \"Subtle\"/\"strong\" keep\n * their light-mode semantics: `bgSubtle` reads as a lifted hover/inset tint and\n * `borderStrong` is more prominent than `border`.\n */\nconst DARK: Record<string, string> = {\n  // ── Surfaces (base → lifted) ──\n  \"--color-background-primary\": \"#18181b\",\n  \"--color-background-secondary\": \"#27272a\",\n  \"--color-background-tertiary\": \"#2f2f34\",\n  // ── Text ──\n  \"--color-text-primary\": \"#fafafa\",\n  \"--color-text-secondary\": \"#a1a1aa\",\n  \"--color-text-tertiary\": \"#71717a\",\n  \"--color-text-accent\": \"#818cf8\",\n  \"--nb-color-accent-foreground\": \"#ffffff\",\n  // ── Border / ring (lighter than surfaces so they remain visible) ──\n  \"--color-border-primary\": \"#3f3f46\",\n  \"--color-border-secondary\": \"#52525b\",\n  \"--color-ring-primary\": \"#818cf8\",\n  // ── Status / brand semantics (brightened for contrast on dark) ──\n  \"--nb-color-danger\": \"#f87171\",\n  // Dark text on the brightened red: white on #f87171 is under 3:1.\n  \"--nb-color-danger-foreground\": \"#18181b\",\n  \"--nb-color-success\": \"#34d399\",\n  \"--nb-color-warning\": \"#fbbf24\",\n  \"--nb-color-processing\": \"#a78bfa\",\n  \"--nb-color-processing-light\": \"#2a2440\",\n  \"--nb-color-info-light\": \"#1e2a44\",\n};\n\n/**\n * The neutral default theme, keyed by mode. Exported for the regression test\n * that asserts every color var referenced by `tokens` is backed in both modes.\n */\nexport const DEFAULT_THEME_VARS: Record<\"light\" | \"dark\", Record<string, string>> = {\n  light: LIGHT,\n  dark: DARK,\n};\n\n/** Cascade layer holding the neutral defaults. Named so an app can order it. */\nexport const DEFAULTS_LAYER_NAME = \"synapse-defaults\";\n\n/** `id` of the single `<style>` element this module owns. */\nexport const DEFAULTS_STYLE_ID = \"synapse-theme-defaults\";\n\n/** Custom properties this module last wrote inline, so a later apply clears only\n *  what it owns and never an inline property the app set itself. */\nlet appliedInlineKeys = new Set<string>();\n\n/** Test seam — forget what was written inline so a fresh apply is observable. */\nexport function resetAppliedInlineKeys(): void {\n  appliedInlineKeys = new Set();\n}\n\n/**\n * Whether this document can host the defaults stylesheet.\n *\n * Not the same question as `typeof document !== \"undefined\"`. An embedder or a\n * test harness may install a *partial* `document` — `documentElement.style` plus\n * listeners and nothing else — which is everything this module needed while the\n * defaults were inline properties. Moving them into a stylesheet added three new\n * requirements (`getElementById`, `createElement`, `head.prepend`), and reaching\n * for them on such a document throws.\n *\n * A throw here is not contained: it unwinds through `applyTheme` into the\n * handshake, so an app in that environment never finishes connecting. The\n * default layer is a rendering nicety and the connection is not, so a document\n * that cannot carry a stylesheet gets no layer rather than no session.\n */\nfunction canInstallStylesheet(): boolean {\n  return (\n    typeof document !== \"undefined\" &&\n    typeof document.getElementById === \"function\" &&\n    typeof document.createElement === \"function\" &&\n    typeof document.head?.prepend === \"function\"\n  );\n}\n\n/**\n * Install the neutral defaults for `mode` as a cascade layer.\n *\n * A layer, not inline properties on `documentElement`, and that distinction is\n * the whole point: **a default has to lose.** An inline style outranks every\n * author stylesheet, so writing defaults there made them beat any rule a host or\n * app had written for the same var — the opposite of a fallback.\n *\n * A host cannot always deliver a var through the protocol.\n * `hostContext.styles.variables` is a closed enum, so a host whose design system\n * is larger than that enum has to put the remainder in a stylesheet it injects\n * into the app document. Those declarations are ordinary unlayered author rules.\n * Unlayered beats layered, so they now win — while a var nobody declares still\n * resolves to a neutral default, which is what this map is for.\n *\n * An app's own `:root` rule wins for the same reason, so overriding a default\n * needs no `!important` and no knowledge of this module.\n *\n * One element, replaced in place, so a mode flip swaps the whole map atomically\n * and repeat calls are cheap. SSR-safe.\n */\nfunction applyDefaultThemeLayer(mode: \"light\" | \"dark\"): void {\n  if (!canInstallStylesheet()) return;\n\n  // Safe to concatenate ONLY because every key and value here is module-local\n  // (`DEFAULT_THEME_VARS`). A host-supplied value containing `}` would escape\n  // the rule, so never fold host or app input into this template; host\n  // variables go through `setProperty`, which cannot escape.\n  const declarations = Object.entries(DEFAULT_THEME_VARS[mode])\n    .map(([k, v]) => `    ${k}: ${v};`)\n    .join(\"\\n\");\n  const css = `@layer ${DEFAULTS_LAYER_NAME} {\\n  :root {\\n${declarations}\\n  }\\n}`;\n\n  const existing = document.getElementById(DEFAULTS_STYLE_ID);\n  if (existing) {\n    // Avoid a needless style invalidation when the mode hasn't changed.\n    if (existing.textContent !== css) existing.textContent = css;\n    return;\n  }\n  const el = document.createElement(\"style\");\n  el.id = DEFAULTS_STYLE_ID;\n  el.textContent = css;\n  // Prepended so this is the first layer the document declares, which puts it\n  // first in layer order — an app that declares its own layers sorts after, and\n  // therefore wins. Unlayered app rules win regardless of DOM order.\n  document.head.prepend(el);\n}\n\n/**\n * Apply theme CSS custom properties to the app document.\n *\n * The host's variables go inline on `documentElement`, where they outrank\n * everything; the neutral defaults for `mode` go into a cascade layer, where\n * they lose to any rule that actually declares the var. So a host value beats\n * this module's default through *either* channel — the protocol or a stylesheet\n * it injects — and a var nobody declares still resolves to a theme-correct\n * default. Only the protocol channel also outranks the app: a host's injected\n * stylesheet and the app's own `:root` are both unlayered rules of equal\n * specificity, so document order decides between them, not this module.\n *\n * A key this module wrote on a previous call and the incoming set does NOT carry\n * is *removed* inline rather than left behind, because a host may legitimately\n * narrow its key set: stop sending a var and it must stop applying, or it stays\n * pinned inline forever where the layer, the host's own stylesheet and the app's\n * own rules all cannot reach it. Removing lets the cascade resolve it against\n * those instead, which is the property this module exists to establish, and is\n * strictly less destructive than overwriting the key with our own default.\n *\n * Tracked as the set of keys last written, not as `DEFAULT_THEME_VARS`'s keys.\n * Those are two different sets: a host can send any spec-enum var, and ~25\n * theme-sensitive ones (`--color-text-danger`, `--color-background-inverse`, …)\n * have no neutral default, so keying the removal off the default map would pin\n * exactly those at the previous mode's value. Tracking what we wrote also means\n * this module never clears an inline property it did not set — an app writing its\n * own `documentElement.style` is left alone.\n *\n * Today an empty var set also arrives for a reason that is a bug rather than a\n * narrowing — `core.ts` replaces the host context wholesale, so a partial\n * `host-context-changed` that omits `styles` reads as \"no tokens\" (#46;\n * `connect.ts` already carries them forward). Fixing #46 is the better outcome —\n * it keeps the host's brand across a mode flip instead of collapsing to our\n * neutral — and does not make this loop unnecessary.\n *\n * SSR-safe (no-ops when `document` is unavailable). Idempotent — re-applying on\n * every theme change is correct and cheap.\n *\n * Prefer {@link applyTheme}: it applies variables *and* font faces together, so\n * a caller cannot wire up half a theme.\n */\nexport function applyThemeVariables(\n  mode: \"light\" | \"dark\",\n  hostVars: Record<string, string> | undefined | null,\n): void {\n  if (typeof document === \"undefined\") return;\n  applyDefaultThemeLayer(mode);\n\n  // ONE definition of \"the host provided this key\", read by both loops below.\n  // Wire data is untyped, so a key can arrive with a non-string value; that is\n  // not a provided value, so the clear loop must reach it. Two predicates —\n  // `k in hostVars` to clear, `typeof v === \"string\"` to write — leave such a key\n  // in the gap: neither removed nor written, so the previous theme's value stays\n  // pinned inline where the layer, the host's stylesheet and the app's `:root`\n  // all cannot reach it. That is the un-self-healing pin this module exists to\n  // eliminate, re-entered by a different door.\n  const incoming: Record<string, string> = {};\n  if (hostVars && typeof hostVars === \"object\") {\n    for (const [k, v] of Object.entries(hostVars)) {\n      if (typeof k === \"string\" && typeof v === \"string\") incoming[k] = v;\n    }\n  }\n\n  const root = document.documentElement.style;\n  // `removeProperty` is feature-checked and `setProperty` deliberately is not.\n  // The setter is the one capability this module has always required, so a\n  // document lacking it was never supported and gets no new promise here; the\n  // clear pass arrived with the defaults layer, so a `style` carrying only the\n  // setter is a shape that used to work. Dropping the clear pass for it is far\n  // better than throwing out of the loop and leaving the host's variables\n  // unwritten entirely.\n  if (typeof root.removeProperty === \"function\") {\n    for (const k of appliedInlineKeys) {\n      if (!(k in incoming)) root.removeProperty(k);\n    }\n  }\n  for (const [k, v] of Object.entries(incoming)) root.setProperty(k, v);\n  appliedInlineKeys = new Set(Object.keys(incoming));\n}\n\n/**\n * Apply a resolved theme's variables to the app document.\n *\n * The single path by which theme variables reach the DOM: the handshake and\n * `host-context-changed` both funnel through here.\n */\nexport function applyTheme(\n  mode: \"light\" | \"dark\",\n  hostVars: Record<string, string> | undefined | null,\n): void {\n  applyThemeVariables(mode, hostVars);\n}\n"]}