All files / src/utils grackleHostStyleVariables.ts

98.16% Statements 107/109
75% Branches 3/4
100% Functions 1/1
98.16% Lines 107/109

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135                                1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x               1x 2x 2x     2x 2x 2x 28x 28x 2x 2x 28x 2x 2x  
import type { McpUiStyles } from "@modelcontextprotocol/ext-apps";
 
/**
 * MCP Apps widgets consume a fixed, standard set of CSS custom properties
 * (`--color-*`, `--font-*`, `--border-radius-*`, `--shadow-*`). Grackle's own
 * theme tokens live under different names (`--bg-*`, `--text-*`, `--radius-*`) in
 * `src/styles/theme.scss`. This module exposes Grackle's current theme to widgets
 * under the MCP-standard names so a widget renders coherently inside the host.
 *
 * `McpUiStyles` requires the complete variable set, so we seed from the standard
 * theme-adaptive defaults (verbatim from modelcontextprotocol/ext-apps
 * examples/basic-host/src/host-styles.ts, commit 9a37ad7) and override the subset
 * we can source from Grackle's live theme.
 */
 
/** Complete MCP-standard default style variables (theme-adaptive via `light-dark()`). */
const DEFAULT_MCP_STYLES: McpUiStyles = {
  "--color-background-primary": "light-dark(#ffffff, #1a1a1a)",
  "--color-background-secondary": "light-dark(#f5f5f5, #2d2d2d)",
  "--color-background-tertiary": "light-dark(#e5e5e5, #404040)",
  "--color-background-inverse": "light-dark(#1a1a1a, #ffffff)",
  "--color-background-ghost": "light-dark(rgba(255,255,255,0), rgba(26,26,26,0))",
  "--color-background-info": "light-dark(#eff6ff, #1e3a5f)",
  "--color-background-danger": "light-dark(#fef2f2, #7f1d1d)",
  "--color-background-success": "light-dark(#f0fdf4, #14532d)",
  "--color-background-warning": "light-dark(#fefce8, #713f12)",
  "--color-background-disabled": "light-dark(rgba(255,255,255,0.5), rgba(26,26,26,0.5))",
  "--color-text-primary": "light-dark(#1f2937, #f3f4f6)",
  "--color-text-secondary": "light-dark(#6b7280, #9ca3af)",
  "--color-text-tertiary": "light-dark(#9ca3af, #6b7280)",
  "--color-text-inverse": "light-dark(#f3f4f6, #1f2937)",
  "--color-text-ghost": "light-dark(rgba(107,114,128,0.5), rgba(156,163,175,0.5))",
  "--color-text-info": "light-dark(#1d4ed8, #60a5fa)",
  "--color-text-danger": "light-dark(#b91c1c, #f87171)",
  "--color-text-success": "light-dark(#15803d, #4ade80)",
  "--color-text-warning": "light-dark(#a16207, #fbbf24)",
  "--color-text-disabled": "light-dark(rgba(31,41,55,0.5), rgba(243,244,246,0.5))",
  "--color-border-primary": "light-dark(#e5e7eb, #404040)",
  "--color-border-secondary": "light-dark(#d1d5db, #525252)",
  "--color-border-tertiary": "light-dark(#f3f4f6, #374151)",
  "--color-border-inverse": "light-dark(rgba(255,255,255,0.3), rgba(0,0,0,0.3))",
  "--color-border-ghost": "light-dark(rgba(229,231,235,0), rgba(64,64,64,0))",
  "--color-border-info": "light-dark(#93c5fd, #1e40af)",
  "--color-border-danger": "light-dark(#fca5a5, #991b1b)",
  "--color-border-success": "light-dark(#86efac, #166534)",
  "--color-border-warning": "light-dark(#fde047, #854d0e)",
  "--color-border-disabled": "light-dark(rgba(229,231,235,0.5), rgba(64,64,64,0.5))",
  "--color-ring-primary": "light-dark(#3b82f6, #60a5fa)",
  "--color-ring-secondary": "light-dark(#6b7280, #9ca3af)",
  "--color-ring-inverse": "light-dark(#ffffff, #1f2937)",
  "--color-ring-info": "light-dark(#2563eb, #3b82f6)",
  "--color-ring-danger": "light-dark(#dc2626, #ef4444)",
  "--color-ring-success": "light-dark(#16a34a, #22c55e)",
  "--color-ring-warning": "light-dark(#ca8a04, #eab308)",
  "--font-sans": "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
  "--font-mono": "ui-monospace, 'SF Mono', Monaco, 'Cascadia Code', monospace",
  "--font-weight-normal": "400",
  "--font-weight-medium": "500",
  "--font-weight-semibold": "600",
  "--font-weight-bold": "700",
  "--font-text-xs-size": "0.75rem",
  "--font-text-sm-size": "0.875rem",
  "--font-text-md-size": "1rem",
  "--font-text-lg-size": "1.125rem",
  "--font-heading-xs-size": "0.75rem",
  "--font-heading-sm-size": "0.875rem",
  "--font-heading-md-size": "1rem",
  "--font-heading-lg-size": "1.25rem",
  "--font-heading-xl-size": "1.5rem",
  "--font-heading-2xl-size": "1.875rem",
  "--font-heading-3xl-size": "2.25rem",
  "--font-text-xs-line-height": "1.4",
  "--font-text-sm-line-height": "1.4",
  "--font-text-md-line-height": "1.5",
  "--font-text-lg-line-height": "1.5",
  "--font-heading-xs-line-height": "1.4",
  "--font-heading-sm-line-height": "1.4",
  "--font-heading-md-line-height": "1.4",
  "--font-heading-lg-line-height": "1.3",
  "--font-heading-xl-line-height": "1.25",
  "--font-heading-2xl-line-height": "1.2",
  "--font-heading-3xl-line-height": "1.1",
  "--border-radius-xs": "2px",
  "--border-radius-sm": "4px",
  "--border-radius-md": "6px",
  "--border-radius-lg": "8px",
  "--border-radius-xl": "12px",
  "--border-radius-full": "9999px",
  "--border-width-regular": "1px",
  "--shadow-hairline": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
  "--shadow-sm": "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1)",
  "--shadow-md": "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)",
  "--shadow-lg": "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)",
};
 
/** MCP-standard variable name -> Grackle theme token to read its value from. */
const GRACKLE_TOKEN_BY_MCP_NAME: Readonly<Record<string, string>> = {
  "--color-background-primary": "--bg-base",
  "--color-background-secondary": "--bg-surface",
  "--color-background-tertiary": "--bg-elevated",
  "--color-text-primary": "--text-primary",
  "--color-text-secondary": "--text-secondary",
  "--color-text-tertiary": "--text-tertiary",
  "--color-border-primary": "--border-primary",
  "--color-border-secondary": "--border-subtle",
  "--font-sans": "--font-sans",
  "--font-mono": "--font-mono",
  "--border-radius-sm": "--radius-sm",
  "--border-radius-md": "--radius-md",
  "--border-radius-lg": "--radius-lg",
  "--border-radius-full": "--radius-full",
};
 
/**
 * Build the MCP-standard style-variable map from Grackle's live theme, falling
 * back to theme-adaptive defaults for any token that cannot be read.
 *
 * @returns Style variables suitable for `hostContext.styles.variables`.
 */
export function grackleHostStyleVariables(): McpUiStyles {
  const out: McpUiStyles = { ...DEFAULT_MCP_STYLES };
  if (typeof window === "undefined" || typeof document === "undefined") {
    return out;
  }
  const computed: CSSStyleDeclaration = window.getComputedStyle(document.documentElement);
  const writable: Record<string, string> = out as Record<string, string>;
  for (const [mcpName, grackleToken] of Object.entries(GRACKLE_TOKEN_BY_MCP_NAME)) {
    const value: string = computed.getPropertyValue(grackleToken).trim();
    if (value) {
      writable[mcpName] = value;
    }
  }
  return out;
}