{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calculator",
  "title": "Mirai Scientific Calculator",
  "description": "An editable scientific, graphing, statistics, and math tools calculator.",
  "dependencies": [
    "@openmirai/calculator-core@^0.2.0",
    "lucide-react@^1.28.0"
  ],
  "registryDependencies": [
    "badge",
    "button",
    "card",
    "input",
    "scroll-area",
    "select",
    "separator",
    "switch",
    "tabs",
    "textarea",
    "tooltip"
  ],
  "files": [
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/calculator-empty-state-action.tsx",
      "content": "interface CalculatorEmptyStateActionProps {\n  label: string\n  onClick: () => void\n}\n\n/** Renders the inline Add action used by calculator empty-state guidance. */\nexport function CalculatorEmptyStateAction({ label, onClick }: CalculatorEmptyStateActionProps) {\n  return (\n    <button\n      type=\"button\"\n      aria-label={label}\n      onClick={onClick}\n      className=\"font-semibold text-foreground underline-offset-4 hover:underline focus-visible:rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\"\n    >\n      Add\n    </button>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/calculator-empty-state-action.tsx"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/calculator-extension-definitions.tsx",
      "content": "import { GraphingMode } from \"@/components/mirai-calculator/modes/graphing-mode\"\nimport { ScientificMode } from \"@/components/mirai-calculator/modes/scientific-mode\"\nimport { StatisticsMode } from \"@/components/mirai-calculator/modes/statistics-mode\"\nimport { ToolsMode } from \"@/components/mirai-calculator/modes/tools-mode\"\nimport { CalculatorExtension } from \"@openmirai/calculator-core/configuration\"\nimport type {\n  CalculatorEngine,\n  AngleMode,\n  NumberFormatOptions,\n} from \"@openmirai/calculator-core/engine\"\nimport type { GraphingInitialData } from \"@openmirai/calculator-core/graphing-data\"\nimport type { GraphView } from \"@openmirai/calculator-core/graphing-view\"\nimport type { StatisticsInitialData } from \"@openmirai/calculator-core/statistics-data\"\nimport type { ToolsInitialData } from \"@openmirai/calculator-core/tools\"\n\ninterface ModeRendererProps {\n  resetSignal: number\n  engine: CalculatorEngine\n  activeAngleMode: AngleMode\n  ans: number\n  definitions: Array<string>\n  setDefinitions: (definitions: Array<string>) => void\n  setAns: (ans: number) => void\n  formatOptions: NumberFormatOptions\n  formatNumber: (value: number) => string\n  setVariables: (variables: Record<string, number>) => void\n  graphView: GraphView\n  setGraphView: (view: GraphView) => void\n  graphGridVisible: boolean\n  setGraphGridVisible: (visible: boolean) => void\n  resolvedTheme: \"light\" | \"dark\"\n  defaultGraphingData?: GraphingInitialData\n  defaultStatisticsData?: StatisticsInitialData\n  defaultToolsData?: ToolsInitialData\n}\n\ninterface ExtensionDefinition {\n  label: string\n  compactLabel: string\n  usesAngleMode: boolean\n  hasGraphSettings: boolean\n  render: (props: ModeRendererProps) => React.ReactNode\n}\n\nexport const CALCULATOR_EXTENSION_DEFINITIONS: Record<CalculatorExtension, ExtensionDefinition> = {\n  [CalculatorExtension.SCIENTIFIC]: {\n    label: \"Scientific\",\n    compactLabel: \"Sci\",\n    usesAngleMode: true,\n    hasGraphSettings: false,\n    render: ({ resetSignal, engine, definitions, setDefinitions, setAns, formatOptions }) => (\n      <ScientificMode\n        key={`scientific-${resetSignal}`}\n        engine={engine}\n        definitions={definitions}\n        onDefinitionsChange={setDefinitions}\n        onAnsChange={setAns}\n        formatOptions={formatOptions}\n      />\n    ),\n  },\n  [CalculatorExtension.GRAPHING]: {\n    label: \"Graphing\",\n    compactLabel: \"Graph\",\n    usesAngleMode: true,\n    hasGraphSettings: true,\n    render: ({\n      resetSignal,\n      activeAngleMode,\n      ans,\n      definitions,\n      formatNumber,\n      setVariables,\n      graphView,\n      setGraphView,\n      graphGridVisible,\n      setGraphGridVisible,\n      resolvedTheme,\n      defaultGraphingData,\n    }) => (\n      <GraphingMode\n        key={`graphing-${resetSignal}`}\n        angleMode={activeAngleMode}\n        ans={ans}\n        definitions={definitions}\n        formatNumber={formatNumber}\n        onVariablesChange={setVariables}\n        view={graphView}\n        onViewChange={setGraphView}\n        gridVisible={graphGridVisible}\n        onGridVisibleChange={setGraphGridVisible}\n        colorScheme={resolvedTheme}\n        defaultData={defaultGraphingData}\n      />\n    ),\n  },\n  [CalculatorExtension.STATISTICS]: {\n    label: \"Stats\",\n    compactLabel: \"Stats\",\n    usesAngleMode: false,\n    hasGraphSettings: false,\n    render: ({ resetSignal, formatNumber, resolvedTheme, defaultStatisticsData }) => (\n      <StatisticsMode\n        key={`statistics-${resetSignal}`}\n        formatNumber={formatNumber}\n        colorScheme={resolvedTheme}\n        defaultData={defaultStatisticsData}\n      />\n    ),\n  },\n  [CalculatorExtension.TOOLS]: {\n    label: \"Tools\",\n    compactLabel: \"Tools\",\n    usesAngleMode: false,\n    hasGraphSettings: false,\n    render: ({ resetSignal, formatNumber, defaultToolsData }) => (\n      <ToolsMode\n        key={`tools-${resetSignal}`}\n        formatNumber={formatNumber}\n        defaultData={defaultToolsData}\n      />\n    ),\n  },\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/calculator-extension-definitions.tsx"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/calculator-header-icon-button.tsx",
      "content": "import { buttonVariants } from \"@/components/ui/button\"\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/components/ui/tooltip\"\nimport { cn } from \"@/lib/utils\"\n\n/** Renders a compact calculator header control with a themed tooltip. */\nexport function CalculatorHeaderIconButton({\n  label,\n  children,\n  onClick,\n  portalTheme,\n}: {\n  label: string\n  children: React.ReactNode\n  onClick: () => void\n  portalTheme: \"light\" | \"dark\"\n}) {\n  return (\n    <Tooltip>\n      <TooltipTrigger\n        onClick={onClick}\n        aria-label={label}\n        className={cn(buttonVariants({ variant: \"outline\", size: \"icon\" }), \"size-7.5 rounded-md\")}\n      >\n        {children}\n      </TooltipTrigger>\n      <TooltipContent\n        className={cn(\"mirai-calculator-package mirai-calculator-portal z-2147483002\", portalTheme)}\n        data-theme={portalTheme}\n      >\n        {label}\n      </TooltipContent>\n    </Tooltip>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/calculator-header-icon-button.tsx"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/calculator-settings-dialog.tsx",
      "content": "import { useEffect } from \"react\"\nimport { createPortal } from \"react-dom\"\nimport { Settings2, X } from \"lucide-react\"\n\nimport {\n  CALCULATOR_ANGLE_MODE_LABELS,\n  CALCULATOR_DECIMAL_OPTIONS,\n  CALCULATOR_GRAPH_BOUNDARY_KEYS,\n  CALCULATOR_NOTATION_LABELS,\n  CALCULATOR_THEME_LABELS,\n} from \"@/components/mirai-calculator/calculator-ui-config\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\"\nimport { Switch } from \"@/components/ui/switch\"\nimport type {\n  CalculatorDisplaySettings,\n  CalculatorTheme,\n} from \"@openmirai/calculator-core/configuration\"\nimport type { AngleMode } from \"@openmirai/calculator-core/engine\"\nimport type { GraphView } from \"@openmirai/calculator-core/graphing-view\"\nimport { cn } from \"@/lib/utils\"\n\ninterface CalculatorSettingsDialogProps {\n  settingsOpen: boolean\n  calculatorContainer: HTMLDivElement | null\n  setSettingsOpen: React.Dispatch<React.SetStateAction<boolean>>\n  resolvedTheme: \"light\" | \"dark\"\n  showsAngleMode: boolean\n  activeAngleMode: AngleMode\n  setAngleMode: (mode: AngleMode) => void\n  displaySettings: CalculatorDisplaySettings\n  setDisplaySettings: React.Dispatch<React.SetStateAction<CalculatorDisplaySettings>>\n  showsGraphSettings: boolean\n  graphGridVisible: boolean\n  setGraphGridVisible: React.Dispatch<React.SetStateAction<boolean>>\n  graphView: GraphView\n  updateGraphBoundary: (key: keyof GraphView, source: string) => void\n  activeTheme: CalculatorTheme\n  setTheme: (theme: CalculatorTheme) => void\n}\n\n/** Renders calculator settings inside the calculator container rather than the host page. */\nexport function CalculatorSettingsDialog({\n  settingsOpen,\n  calculatorContainer,\n  setSettingsOpen,\n  resolvedTheme,\n  showsAngleMode,\n  activeAngleMode,\n  setAngleMode,\n  displaySettings,\n  setDisplaySettings,\n  showsGraphSettings,\n  graphGridVisible,\n  setGraphGridVisible,\n  graphView,\n  updateGraphBoundary,\n  activeTheme,\n  setTheme,\n}: CalculatorSettingsDialogProps) {\n  useEffect(() => {\n    if (!settingsOpen) {\n      return undefined\n    }\n\n    const closeOnEscape = (event: KeyboardEvent) => {\n      if (event.key === \"Escape\") {\n        setSettingsOpen(false)\n      }\n    }\n    document.addEventListener(\"keydown\", closeOnEscape)\n    return () => document.removeEventListener(\"keydown\", closeOnEscape)\n  }, [settingsOpen, setSettingsOpen])\n\n  return (\n    <>\n      {settingsOpen && calculatorContainer\n        ? createPortal(\n            <div\n              role=\"presentation\"\n              className=\"absolute inset-0 z-60 flex items-center justify-center overflow-hidden rounded-[inherit] bg-black/25 p-3 backdrop-blur-[7px]\"\n              onPointerDown={(event) => {\n                if (event.target === event.currentTarget) {\n                  setSettingsOpen(false)\n                }\n              }}\n            >\n              <section\n                role=\"dialog\"\n                aria-modal=\"true\"\n                aria-label=\"Calculator settings\"\n                tabIndex={-1}\n                className={cn(\n                  \"mirai-calculator-package mirai-calculator-portal mirai-calculator-settings-dialog relative grid min-h-0 max-h-[calc(100%-1.5rem)] w-[calc(100%-1.5rem)] max-w-160 grid-rows-[auto_minmax(0,1fr)_auto] gap-0 overflow-hidden rounded-xl border bg-popover p-0 shadow-2xl ring-1 ring-foreground/10\",\n                  resolvedTheme\n                )}\n                data-theme={resolvedTheme}\n              >\n                <Button\n                  type=\"button\"\n                  aria-label=\"Close calculator settings\"\n                  variant=\"ghost\"\n                  size=\"icon-sm\"\n                  onClick={() => setSettingsOpen(false)}\n                  className=\"absolute top-2 right-2 z-10\"\n                >\n                  <X />\n                </Button>\n                <div className=\"border-b bg-muted/20 px-5 py-4 pr-12\">\n                  <div className=\"flex items-center gap-3\">\n                    <span className=\"flex size-9 shrink-0 items-center justify-center rounded-lg border bg-background text-muted-foreground shadow-xs\">\n                      <Settings2 className=\"size-4\" />\n                    </span>\n                    <div className=\"min-w-0\">\n                      <h2 className=\"text-sm font-semibold\">Calculator settings</h2>\n                      <p className=\"mt-1 text-xs text-muted-foreground\">\n                        Format results and tune the active workspace.\n                      </p>\n                    </div>\n                  </div>\n                </div>\n\n                <div\n                  data-calculator-settings-body=\"\"\n                  className=\"grid min-h-0 gap-4 overflow-y-auto overscroll-contain p-5 sm:grid-cols-2\"\n                >\n                  <section\n                    aria-labelledby=\"mirai-number-format-heading\"\n                    className=\"rounded-lg border bg-background/60 p-4\"\n                  >\n                    <h3\n                      id=\"mirai-number-format-heading\"\n                      className=\"mb-2 text-xs font-semibold uppercase tracking-[0.08em] text-muted-foreground\"\n                    >\n                      Number format\n                    </h3>\n                    <div className=\"grid grid-cols-2 gap-3\">\n                      {showsAngleMode && (\n                        <div className=\"grid gap-1.5 text-xs font-medium text-muted-foreground\">\n                          <span>Angle mode</span>\n                          <Select\n                            value={activeAngleMode}\n                            onValueChange={(value: string | null) => {\n                              if (value === \"degrees\" || value === \"radians\") {\n                                setAngleMode(value)\n                              }\n                            }}\n                          >\n                            <SelectTrigger\n                              size=\"sm\"\n                              className=\"w-full bg-background\"\n                              aria-label=\"Angle mode\"\n                            >\n                              <SelectValue>\n                                {CALCULATOR_ANGLE_MODE_LABELS[activeAngleMode]}\n                              </SelectValue>\n                            </SelectTrigger>\n                            <SelectContent\n                              className={cn(\n                                \"mirai-calculator-package mirai-calculator-portal z-2147483002\",\n                                resolvedTheme\n                              )}\n                              data-theme={resolvedTheme}\n                            >\n                              <SelectItem value=\"degrees\">Degrees</SelectItem>\n                              <SelectItem value=\"radians\">Radians</SelectItem>\n                            </SelectContent>\n                          </Select>\n                        </div>\n                      )}\n\n                      <div className=\"grid gap-1.5 text-xs font-medium text-muted-foreground\">\n                        <span>Notation</span>\n                        <Select\n                          value={displaySettings.notation}\n                          onValueChange={(value: string | null) => {\n                            if (value !== \"auto\" && value !== \"scientific\") {\n                              return\n                            }\n                            setDisplaySettings((current) => ({\n                              ...current,\n                              notation: value,\n                            }))\n                          }}\n                        >\n                          <SelectTrigger\n                            size=\"sm\"\n                            className=\"w-full bg-background\"\n                            aria-label=\"Notation\"\n                          >\n                            <SelectValue>\n                              {CALCULATOR_NOTATION_LABELS[displaySettings.notation]}\n                            </SelectValue>\n                          </SelectTrigger>\n                          <SelectContent\n                            className={cn(\n                              \"mirai-calculator-package mirai-calculator-portal z-2147483002\",\n                              resolvedTheme\n                            )}\n                            data-theme={resolvedTheme}\n                          >\n                            <SelectItem value=\"auto\">Automatic</SelectItem>\n                            <SelectItem value=\"scientific\">Scientific</SelectItem>\n                          </SelectContent>\n                        </Select>\n                      </div>\n\n                      <div className=\"grid gap-1.5 text-xs font-medium text-muted-foreground\">\n                        <span>Decimals</span>\n                        <Select\n                          value={String(displaySettings.decimals)}\n                          onValueChange={(value) => {\n                            if (value === null) {\n                              return\n                            }\n                            setDisplaySettings((current) => ({\n                              ...current,\n                              decimals: value === \"auto\" ? \"auto\" : Number(value),\n                            }))\n                          }}\n                        >\n                          <SelectTrigger\n                            size=\"sm\"\n                            className=\"w-full bg-background\"\n                            aria-label=\"Decimals\"\n                          >\n                            <SelectValue>\n                              {displaySettings.decimals === \"auto\"\n                                ? \"Auto\"\n                                : String(displaySettings.decimals)}\n                            </SelectValue>\n                          </SelectTrigger>\n                          <SelectContent\n                            className={cn(\n                              \"mirai-calculator-package mirai-calculator-portal z-2147483002\",\n                              resolvedTheme\n                            )}\n                            data-theme={resolvedTheme}\n                          >\n                            <SelectItem value=\"auto\">Auto</SelectItem>\n                            {CALCULATOR_DECIMAL_OPTIONS.map((value) => (\n                              <SelectItem key={value} value={String(value)}>\n                                {value}\n                              </SelectItem>\n                            ))}\n                          </SelectContent>\n                        </Select>\n                      </div>\n\n                      <label\n                        htmlFor=\"mirai-significant-figures\"\n                        className=\"grid gap-1.5 text-xs font-medium text-muted-foreground\"\n                      >\n                        Significant figures\n                        <Input\n                          id=\"mirai-significant-figures\"\n                          type=\"number\"\n                          min={1}\n                          max={15}\n                          value={displaySettings.significantFigures}\n                          onChange={(event) =>\n                            setDisplaySettings((current) => ({\n                              ...current,\n                              significantFigures: Math.max(\n                                1,\n                                Math.min(15, Number(event.target.value) || 1)\n                              ),\n                            }))\n                          }\n                          className=\"h-7 w-full bg-background\"\n                        />\n                      </label>\n                    </div>\n\n                    <div className=\"mt-3 flex items-center justify-between gap-4 rounded-md border bg-muted/25 px-3 py-2.5 text-sm\">\n                      <span>\n                        <strong className=\"block text-xs font-medium\">Thousands separators</strong>\n                        <small className=\"text-xs text-muted-foreground\">\n                          Group large values for readability.\n                        </small>\n                      </span>\n                      <Switch\n                        aria-label=\"Thousands separators\"\n                        checked={displaySettings.thousandsSeparator}\n                        onCheckedChange={(checked) =>\n                          setDisplaySettings((current) => ({\n                            ...current,\n                            thousandsSeparator: checked,\n                          }))\n                        }\n                      />\n                    </div>\n                  </section>\n\n                  {showsGraphSettings && (\n                    <section\n                      aria-labelledby=\"mirai-graph-viewport-heading\"\n                      className=\"rounded-lg border bg-background/60 p-4\"\n                    >\n                      <div className=\"mb-2 flex items-center justify-between gap-3\">\n                        <h3\n                          id=\"mirai-graph-viewport-heading\"\n                          className=\"text-xs font-semibold uppercase tracking-[0.08em] text-muted-foreground\"\n                        >\n                          Graph viewport\n                        </h3>\n                        <div className=\"flex items-center gap-2 text-xs\">\n                          <span>Grid lines</span>\n                          <Switch\n                            aria-label=\"Grid lines\"\n                            checked={graphGridVisible}\n                            onCheckedChange={setGraphGridVisible}\n                          />\n                        </div>\n                      </div>\n                      <div className=\"grid grid-cols-2 gap-3\">\n                        {CALCULATOR_GRAPH_BOUNDARY_KEYS.map((key) => (\n                          <label\n                            key={key}\n                            htmlFor={`mirai-graph-${key}`}\n                            className=\"grid gap-1.5 text-xs font-medium text-muted-foreground\"\n                          >\n                            {key.replace(\"min\", \" min\").replace(\"max\", \" max\")}\n                            <Input\n                              id={`mirai-graph-${key}`}\n                              type=\"number\"\n                              value={graphView[key]}\n                              onChange={(event) => updateGraphBoundary(key, event.target.value)}\n                              className=\"h-7 w-full bg-background font-mono\"\n                            />\n                          </label>\n                        ))}\n                      </div>\n                    </section>\n                  )}\n\n                  <section\n                    aria-labelledby=\"mirai-appearance-heading\"\n                    className=\"rounded-lg border bg-background/60 p-4\"\n                  >\n                    <h3\n                      id=\"mirai-appearance-heading\"\n                      className=\"mb-2 text-xs font-semibold uppercase tracking-[0.08em] text-muted-foreground\"\n                    >\n                      Appearance\n                    </h3>\n                    <div className=\"grid gap-1.5 text-xs font-medium text-muted-foreground\">\n                      <span>Theme</span>\n                      <Select\n                        value={activeTheme}\n                        onValueChange={(value: string | null) => {\n                          if (value === \"light\" || value === \"dark\" || value === \"system\") {\n                            setTheme(value)\n                          }\n                        }}\n                      >\n                        <SelectTrigger\n                          size=\"sm\"\n                          className=\"w-full bg-background\"\n                          aria-label=\"Theme\"\n                        >\n                          <SelectValue>{CALCULATOR_THEME_LABELS[activeTheme]}</SelectValue>\n                        </SelectTrigger>\n                        <SelectContent\n                          className={cn(\n                            \"mirai-calculator-package mirai-calculator-portal z-2147483002\",\n                            resolvedTheme\n                          )}\n                          data-theme={resolvedTheme}\n                        >\n                          <SelectItem value=\"light\">Light</SelectItem>\n                          <SelectItem value=\"dark\">Dark</SelectItem>\n                          <SelectItem value=\"system\">System</SelectItem>\n                        </SelectContent>\n                      </Select>\n                    </div>\n                  </section>\n                </div>\n                <div className=\"flex justify-end border-t bg-muted/20 px-5 py-3\">\n                  <Button variant=\"outline\" size=\"sm\" onClick={() => setSettingsOpen(false)}>\n                    Done\n                  </Button>\n                </div>\n              </section>\n            </div>,\n            calculatorContainer\n          )\n        : null}\n    </>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/calculator-settings-dialog.tsx"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/calculator-ui-config.ts",
      "content": "/** Supported decimal-place options displayed by calculator settings. */\nexport const CALCULATOR_DECIMAL_OPTIONS = [0, 1, 2, 3, 4, 6] as const\n\n/** Graph viewport properties exposed by calculator settings. */\nexport const CALCULATOR_GRAPH_BOUNDARY_KEYS = [\"xmin\", \"xmax\", \"ymin\", \"ymax\"] as const\n\n/** Human-readable angle-mode labels. */\nexport const CALCULATOR_ANGLE_MODE_LABELS = {\n  degrees: \"Degrees\",\n  radians: \"Radians\",\n} as const\n\n/** Human-readable number-notation labels. */\nexport const CALCULATOR_NOTATION_LABELS = {\n  auto: \"Automatic\",\n  scientific: \"Scientific\",\n} as const\n\n/** Human-readable calculator theme labels. */\nexport const CALCULATOR_THEME_LABELS = {\n  dark: \"Dark\",\n  light: \"Light\",\n  system: \"System\",\n} as const\n\n/** Semantic shadcn chart tokens used by calculator visualizations. */\nexport const CALCULATOR_CHART_TOKENS = [\n  \"var(--chart-1)\",\n  \"var(--chart-2)\",\n  \"var(--chart-3)\",\n  \"var(--chart-4)\",\n  \"var(--chart-5)\",\n] as const\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/calculator-ui-config.ts"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/calculator-viewport-portal.tsx",
      "content": "import { useLayoutEffect, useRef, useState } from \"react\"\nimport { createPortal } from \"react-dom\"\n\n/** Moves calculator content between its inline host and the fullscreen viewport layer. */\nexport function CalculatorViewportPortal({\n  fullscreen,\n  children,\n}: {\n  fullscreen: boolean\n  children: React.ReactNode\n}) {\n  const inlineContainerRef = useRef<HTMLDivElement>(null)\n  const [portalHost, setPortalHost] = useState<HTMLDivElement | null>(null)\n\n  useLayoutEffect(() => {\n    const host = document.createElement(\"div\")\n    host.style.display = \"contents\"\n    setPortalHost(host)\n    return () => host.remove()\n  }, [])\n\n  useLayoutEffect(() => {\n    if (!portalHost) {\n      return\n    }\n    const target = fullscreen ? document.body : inlineContainerRef.current\n    target?.append(portalHost)\n  }, [fullscreen, portalHost])\n\n  return (\n    <div ref={inlineContainerRef} style={{ display: \"contents\" }}>\n      {portalHost\n        ? createPortal(\n            <>\n              {fullscreen && (\n                <div\n                  aria-hidden=\"true\"\n                  data-mirai-fullscreen-guard=\"\"\n                  className=\"fixed inset-0 z-2147482999 bg-black/35 backdrop-blur-[1px]\"\n                />\n              )}\n              {children}\n            </>,\n            portalHost\n          )\n        : children}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/calculator-viewport-portal.tsx"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/canvas-theme.ts",
      "content": "import { CALCULATOR_CHART_TOKENS } from \"@/components/mirai-calculator/calculator-ui-config\"\n\n/** Reads a shadcn color property with a computed foreground or background fallback. */\nexport function readCanvasColor(element: Element, property: string): string {\n  const computed = getComputedStyle(element)\n  const value = computed.getPropertyValue(property).trim()\n  return value || (property === \"--background\" ? computed.backgroundColor : computed.color)\n}\n\n/** Resolves a CSS custom-property token to a canvas-compatible color. */\nexport function resolveCanvasColor(element: Element, token: string): string {\n  const computed = getComputedStyle(element)\n  const match = /^var\\((--[^)]+)\\)$/.exec(token.trim())\n  if (!match) {\n    return token || computed.color\n  }\n  return computed.getPropertyValue(match[1]).trim() || computed.color\n}\n\n/** Returns every configured shadcn chart color resolved for a canvas element. */\nexport function readCanvasChartColors(element: Element): Array<string> {\n  return CALCULATOR_CHART_TOKENS.map((token) => resolveCanvasColor(element, token))\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/canvas-theme.ts"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/mirai-calculator.tsx",
      "content": "import { useCallback, useEffect, useMemo, useRef, useState } from \"react\"\nimport {\n  Calculator as CalculatorIcon,\n  Expand,\n  EyeOff,\n  Minimize2,\n  Moon,\n  RotateCcw,\n  Settings2,\n  Sun,\n  X,\n} from \"lucide-react\"\n\nimport { CalculatorHeaderIconButton } from \"@/components/mirai-calculator/calculator-header-icon-button\"\nimport { CALCULATOR_EXTENSION_DEFINITIONS } from \"@/components/mirai-calculator/calculator-extension-definitions\"\nimport { CalculatorSettingsDialog } from \"@/components/mirai-calculator/calculator-settings-dialog\"\nimport { CalculatorViewportPortal } from \"@/components/mirai-calculator/calculator-viewport-portal\"\nimport {\n  resolveCalculatorTheme,\n  useSystemDarkMode,\n} from \"@/components/mirai-calculator/use-system-dark-mode\"\nimport { Button, buttonVariants } from \"@/components/ui/button\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\"\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@/components/ui/tooltip\"\nimport {\n  CalculatorExtension,\n  DEFAULT_DISPLAY_SETTINGS,\n  DEFAULT_GRAPH_VIEW,\n  EMPTY_CALCULATOR_DEFINITIONS,\n  calculatorNumberFormatOptions,\n  collectSliderVariables,\n  normalizeCalculatorExtensions,\n} from \"@openmirai/calculator-core/configuration\"\nimport type {\n  CalculatorDisplaySettings,\n  CalculatorMode,\n  CalculatorTheme,\n} from \"@openmirai/calculator-core/configuration\"\nimport { CalculatorEngine } from \"@openmirai/calculator-core/engine\"\nimport type { AngleMode, NumberFormatOptions } from \"@openmirai/calculator-core/engine\"\nimport type { GraphingInitialData } from \"@openmirai/calculator-core/graphing-data\"\nimport type { GraphView } from \"@openmirai/calculator-core/graphing-view\"\nimport type { StatisticsInitialData } from \"@openmirai/calculator-core/statistics-data\"\nimport type { ToolsInitialData as ToolsInitialDataValue } from \"@openmirai/calculator-core/tools\"\nimport { cn } from \"@/lib/utils\"\n\nexport interface MiraiCalculatorProps {\n  className?: string\n  extensions?: ReadonlyArray<CalculatorExtension>\n  mode?: CalculatorMode\n  defaultMode?: CalculatorMode\n  onModeChange?: (mode: CalculatorMode) => void\n  angleMode?: AngleMode\n  defaultAngleMode?: AngleMode\n  onAngleModeChange?: (mode: AngleMode) => void\n  theme?: CalculatorTheme\n  defaultTheme?: CalculatorTheme\n  onThemeChange?: (theme: CalculatorTheme) => void\n  hidden?: boolean\n  defaultHidden?: boolean\n  onHiddenChange?: (hidden: boolean) => void\n  startFullscreen?: boolean\n  title?: string\n  onClose?: () => void\n  defaultDefinitions?: ReadonlyArray<string>\n  defaultGraphingData?: GraphingInitialData\n  defaultStatisticsData?: StatisticsInitialData\n  defaultToolsData?: ToolsInitialDataValue\n}\n\n/** Composes enabled calculator extensions into a configurable, container-responsive panel. */\nexport function MiraiCalculator({\n  className,\n  extensions,\n  mode,\n  defaultMode = CalculatorExtension.SCIENTIFIC,\n  onModeChange,\n  angleMode,\n  defaultAngleMode = \"degrees\",\n  onAngleModeChange,\n  theme,\n  defaultTheme = \"light\",\n  onThemeChange,\n  hidden,\n  defaultHidden = false,\n  onHiddenChange,\n  startFullscreen = false,\n  title = \"Calculator\",\n  onClose,\n  defaultDefinitions = EMPTY_CALCULATOR_DEFINITIONS,\n  defaultGraphingData,\n  defaultStatisticsData,\n  defaultToolsData,\n}: MiraiCalculatorProps) {\n  const [internalMode, setInternalMode] = useState(defaultMode)\n  const [internalAngleMode, setInternalAngleMode] = useState(defaultAngleMode)\n  const [internalTheme, setInternalTheme] = useState(defaultTheme)\n  const [internalHidden, setInternalHidden] = useState(defaultHidden)\n  const systemDark = useSystemDarkMode()\n  const [fullscreen, setFullscreen] = useState(startFullscreen)\n  const [settingsOpen, setSettingsOpen] = useState(false)\n  const [displaySettings, setDisplaySettings] = useState<CalculatorDisplaySettings>(() => ({\n    ...DEFAULT_DISPLAY_SETTINGS,\n  }))\n  const [graphView, setGraphView] = useState<GraphView>(() => ({ ...DEFAULT_GRAPH_VIEW }))\n  const [graphGridVisible, setGraphGridVisible] = useState(true)\n  const [definitions, setDefinitions] = useState(() => [...defaultDefinitions])\n  const [variables, setVariables] = useState<Record<string, number>>(() =>\n    collectSliderVariables(\n      (defaultGraphingData?.expressions ?? []).map((expression) => ({\n        expression: expression.value,\n        value: expression.slider?.value,\n      }))\n    )\n  )\n  const [ans, setAns] = useState(0)\n  const [resetSignal, setResetSignal] = useState(0)\n  const calculatorRef = useRef<HTMLDivElement>(null)\n\n  const enabledExtensions = useMemo(() => normalizeCalculatorExtensions(extensions), [extensions])\n  const requestedMode = mode ?? internalMode\n  const activeMode = enabledExtensions.includes(requestedMode)\n    ? requestedMode\n    : enabledExtensions[0]\n  const activeAngleMode = angleMode ?? internalAngleMode\n  const activeTheme = theme ?? internalTheme\n  const activeHidden = hidden ?? internalHidden\n  const resolvedTheme = resolveCalculatorTheme(activeTheme, systemDark)\n  const showsAngleMode = enabledExtensions.some(\n    (extension) => CALCULATOR_EXTENSION_DEFINITIONS[extension].usesAngleMode\n  )\n  const showsGraphSettings = enabledExtensions.some(\n    (extension) => CALCULATOR_EXTENSION_DEFINITIONS[extension].hasGraphSettings\n  )\n\n  useEffect(() => {\n    if (!fullscreen) {\n      return\n    }\n    const previousOverflow = document.documentElement.style.overflow\n    document.documentElement.style.overflow = \"hidden\"\n    return () => {\n      document.documentElement.style.overflow = previousOverflow\n    }\n  }, [fullscreen])\n\n  const setMode = (nextMode: CalculatorMode) => {\n    if (mode === undefined) {\n      setInternalMode(nextMode)\n    }\n    onModeChange?.(nextMode)\n  }\n\n  const setAngleMode = (nextMode: AngleMode) => {\n    if (angleMode === undefined) {\n      setInternalAngleMode(nextMode)\n    }\n    onAngleModeChange?.(nextMode)\n  }\n\n  const setTheme = (nextTheme: CalculatorTheme) => {\n    if (theme === undefined) {\n      setInternalTheme(nextTheme)\n    }\n    onThemeChange?.(nextTheme)\n  }\n\n  const setHidden = (nextHidden: boolean) => {\n    if (hidden === undefined) {\n      setInternalHidden(nextHidden)\n    }\n    onHiddenChange?.(nextHidden)\n    if (nextHidden) {\n      setFullscreen(false)\n      setSettingsOpen(false)\n    }\n  }\n\n  const formatOptions = useMemo<NumberFormatOptions>(\n    () => calculatorNumberFormatOptions(displaySettings),\n    [displaySettings]\n  )\n\n  const engine = useMemo(\n    () =>\n      new CalculatorEngine({\n        angleMode: activeAngleMode,\n        ans,\n        definitions,\n        variables,\n      }),\n    [activeAngleMode, ans, definitions, variables]\n  )\n\n  const formatNumber = useCallback(\n    (value: number) => engine.format(value, formatOptions),\n    [engine, formatOptions]\n  )\n\n  const reset = () => {\n    setDisplaySettings({ ...DEFAULT_DISPLAY_SETTINGS })\n    setGraphView({ ...DEFAULT_GRAPH_VIEW })\n    setGraphGridVisible(true)\n    setDefinitions([...defaultDefinitions])\n    setVariables({})\n    setAns(0)\n    setResetSignal((signal) => signal + 1)\n  }\n\n  const updateGraphBoundary = (key: keyof GraphView, source: string) => {\n    const value = Number(source)\n    if (!Number.isFinite(value)) {\n      return\n    }\n    setGraphView((current) => {\n      const next = { ...current, [key]: value }\n      if (next.xmin >= next.xmax || next.ymin >= next.ymax) {\n        return current\n      }\n      return next\n    })\n  }\n\n  return (\n    <TooltipProvider>\n      <div className=\"mirai-calculator-package\" style={{ display: \"contents\" }}>\n        <div className=\"relative isolate h-full min-h-0 w-full\">\n          {activeHidden && (\n            <Tooltip>\n              <TooltipTrigger\n                type=\"button\"\n                aria-label=\"Show calculator\"\n                onClick={() => setHidden(false)}\n                className={cn(\n                  buttonVariants({ variant: \"default\", size: \"icon\" }),\n                  \"mirai-calculator size-12 rounded-xl shadow-xl\",\n                  resolvedTheme\n                )}\n                data-calculator-hidden-launcher=\"\"\n                data-theme={resolvedTheme}\n              >\n                <CalculatorIcon className=\"size-5\" />\n              </TooltipTrigger>\n              <TooltipContent\n                className={cn(\n                  \"mirai-calculator-package mirai-calculator-portal z-2147483002\",\n                  resolvedTheme\n                )}\n                data-theme={resolvedTheme}\n              >\n                Show calculator\n              </TooltipContent>\n            </Tooltip>\n          )}\n          <CalculatorViewportPortal fullscreen={fullscreen}>\n            <div\n              ref={calculatorRef}\n              hidden={activeHidden}\n              className={cn(\n                \"mirai-calculator @container relative flex h-full min-h-0 w-full min-w-0 max-w-full flex-col overflow-hidden rounded-xl border bg-background text-sm text-foreground tabular-nums shadow-xl [&_canvas]:block\",\n                resolvedTheme,\n                fullscreen &&\n                  \"fixed inset-3 z-2147483000 h-[calc(100dvh-1.5rem)] min-h-0 w-auto max-w-none rounded-xl\",\n                className\n              )}\n              data-theme={resolvedTheme}\n            >\n              <header\n                data-calculator-drag-handle=\"\"\n                className=\"mirai-calculator-header flex min-h-13 shrink-0 touch-none flex-nowrap items-center gap-3 overflow-hidden border-b bg-card px-3 py-2 select-none @max-[699px]:gap-1\"\n              >\n                <span className=\"sr-only\">{title}</span>\n                <span\n                  aria-hidden=\"true\"\n                  className=\"grid shrink-0 grid-cols-2 gap-0.75 p-0.5 @max-[359px]:hidden\"\n                >\n                  {Array.from({ length: 6 }, (_, index) => (\n                    <span key={index} className=\"size-0.75 rounded-full bg-muted-foreground/35\" />\n                  ))}\n                </span>\n                {enabledExtensions.length === 1 && (\n                  <span\n                    data-calculator-mode-label\n                    className=\"flex h-8 min-w-0 shrink-0 items-center text-sm font-medium\"\n                  >\n                    {CALCULATOR_EXTENSION_DEFINITIONS[enabledExtensions[0]].label}\n                  </span>\n                )}\n\n                {enabledExtensions.length > 1 && (\n                  <>\n                    <div className=\"mirai-mode-select hidden min-w-0 flex-1 @max-[699px]:block\">\n                      <Select\n                        value={activeMode}\n                        onValueChange={(value: string | null) => {\n                          if (\n                            value !== null &&\n                            enabledExtensions.includes(value as CalculatorExtension)\n                          ) {\n                            setMode(value as CalculatorExtension)\n                          }\n                        }}\n                      >\n                        <SelectTrigger\n                          aria-label=\"Calculator mode\"\n                          size=\"sm\"\n                          className=\"h-8 w-full min-w-0 rounded-lg border-transparent bg-muted shadow-none\"\n                        >\n                          <SelectValue>\n                            {CALCULATOR_EXTENSION_DEFINITIONS[activeMode].label}\n                          </SelectValue>\n                        </SelectTrigger>\n                        <SelectContent\n                          className={cn(\n                            \"mirai-calculator-package mirai-calculator-portal z-2147483002\",\n                            resolvedTheme\n                          )}\n                          data-theme={resolvedTheme}\n                        >\n                          {enabledExtensions.map((extension) => (\n                            <SelectItem key={extension} value={extension}>\n                              {CALCULATOR_EXTENSION_DEFINITIONS[extension].label}\n                            </SelectItem>\n                          ))}\n                        </SelectContent>\n                      </Select>\n                    </div>\n                    <nav\n                      aria-label=\"Calculator modes\"\n                      className=\"mirai-mode-switcher relative grid min-w-0 max-w-full flex-[1_1_364px] gap-0.5 overflow-hidden rounded-lg bg-muted p-0.75 @max-[699px]:hidden\"\n                      style={{\n                        gridTemplateColumns: `repeat(${enabledExtensions.length}, minmax(0, 1fr))`,\n                      }}\n                    >\n                      {enabledExtensions.map((extension) => (\n                        <Button\n                          key={extension}\n                          aria-label={CALCULATOR_EXTENSION_DEFINITIONS[extension].label}\n                          variant=\"ghost\"\n                          size=\"sm\"\n                          onClick={() => setMode(extension)}\n                          aria-current={activeMode === extension ? \"page\" : undefined}\n                          data-active={activeMode === extension ? \"\" : undefined}\n                          className=\"h-7 min-w-0 w-full rounded-md bg-transparent px-1 text-sm font-medium shadow-none transition-[background-color,box-shadow,color] hover:bg-foreground/8! hover:text-foreground! focus-visible:border-transparent focus-visible:ring-0 focus-visible:outline-none data-active:bg-background data-active:text-foreground data-active:shadow-sm data-active:hover:bg-background!\"\n                        >\n                          <span\n                            aria-hidden=\"true\"\n                            className=\"mirai-mode-label @max-[699px]:text-xs\"\n                            data-compact-label={\n                              CALCULATOR_EXTENSION_DEFINITIONS[extension].compactLabel\n                            }\n                          >\n                            {CALCULATOR_EXTENSION_DEFINITIONS[extension].label}\n                          </span>\n                        </Button>\n                      ))}\n                    </nav>\n                  </>\n                )}\n\n                <div className=\"mirai-header-actions ml-auto flex shrink-0 items-center gap-2 @max-[699px]:gap-1\">\n                  <CalculatorHeaderIconButton\n                    label={`Use ${resolvedTheme === \"dark\" ? \"light\" : \"dark\"} mode`}\n                    onClick={() => setTheme(resolvedTheme === \"dark\" ? \"light\" : \"dark\")}\n                    portalTheme={resolvedTheme}\n                  >\n                    {resolvedTheme === \"dark\" ? <Sun /> : <Moon />}\n                  </CalculatorHeaderIconButton>\n                  <Button\n                    type=\"button\"\n                    aria-label=\"Calculator settings\"\n                    variant=\"outline\"\n                    size=\"icon\"\n                    onClick={() => setSettingsOpen(true)}\n                    className={cn(\n                      \"size-7.5 rounded-md\",\n                      settingsOpen && \"bg-muted text-foreground\"\n                    )}\n                  >\n                    <Settings2 />\n                  </Button>\n                  <CalculatorSettingsDialog\n                    settingsOpen={settingsOpen}\n                    calculatorContainer={calculatorRef.current}\n                    setSettingsOpen={setSettingsOpen}\n                    resolvedTheme={resolvedTheme}\n                    showsAngleMode={showsAngleMode}\n                    activeAngleMode={activeAngleMode}\n                    setAngleMode={setAngleMode}\n                    displaySettings={displaySettings}\n                    setDisplaySettings={setDisplaySettings}\n                    showsGraphSettings={showsGraphSettings}\n                    graphGridVisible={graphGridVisible}\n                    setGraphGridVisible={setGraphGridVisible}\n                    graphView={graphView}\n                    updateGraphBoundary={updateGraphBoundary}\n                    activeTheme={activeTheme}\n                    setTheme={setTheme}\n                  />\n                  <CalculatorHeaderIconButton\n                    label=\"Hide calculator\"\n                    onClick={() => setHidden(true)}\n                    portalTheme={resolvedTheme}\n                  >\n                    <EyeOff />\n                  </CalculatorHeaderIconButton>\n                  <Button\n                    aria-label=\"Reset\"\n                    variant=\"outline\"\n                    size=\"sm\"\n                    onClick={reset}\n                    className=\"mirai-header-reset h-7.5 min-w-7.5 rounded-md px-2.5 text-xs text-muted-foreground @max-[699px]:w-7.5 @max-[699px]:px-0\"\n                  >\n                    <RotateCcw\n                      aria-hidden=\"true\"\n                      className=\"mirai-header-reset-icon hidden size-4 @max-[699px]:block\"\n                    />\n                    <span className=\"mirai-header-reset-label @max-[699px]:hidden\">Reset</span>\n                  </Button>\n                  <CalculatorHeaderIconButton\n                    label={fullscreen ? \"Exit full screen\" : \"Enter full screen\"}\n                    onClick={() => setFullscreen((value) => !value)}\n                    portalTheme={resolvedTheme}\n                  >\n                    {fullscreen ? <Minimize2 /> : <Expand />}\n                  </CalculatorHeaderIconButton>\n                  {onClose && (\n                    <CalculatorHeaderIconButton\n                      label=\"Close calculator\"\n                      onClick={onClose}\n                      portalTheme={resolvedTheme}\n                    >\n                      <X />\n                    </CalculatorHeaderIconButton>\n                  )}\n                </div>\n              </header>\n\n              <main className=\"flex min-h-0 flex-1 flex-col overflow-hidden\">\n                {activeMode ? (\n                  CALCULATOR_EXTENSION_DEFINITIONS[activeMode].render({\n                    resetSignal,\n                    engine,\n                    activeAngleMode,\n                    ans,\n                    definitions,\n                    setDefinitions,\n                    setAns,\n                    formatOptions,\n                    formatNumber,\n                    setVariables,\n                    graphView,\n                    setGraphView,\n                    graphGridVisible,\n                    setGraphGridVisible,\n                    resolvedTheme,\n                    defaultGraphingData,\n                    defaultStatisticsData,\n                    defaultToolsData,\n                  })\n                ) : (\n                  <div\n                    role=\"status\"\n                    className=\"flex min-h-48 flex-1 items-center justify-center p-8 text-center text-sm text-muted-foreground\"\n                  >\n                    No calculator extensions enabled\n                  </div>\n                )}\n              </main>\n            </div>\n          </CalculatorViewportPortal>\n        </div>\n      </div>\n    </TooltipProvider>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/mirai-calculator.tsx"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/modes/graphing-canvas-viewport.tsx",
      "content": "import { Grid3X3, Home, LocateFixed, X, ZoomIn, ZoomOut } from \"lucide-react\"\n\nimport { CALCULATOR_CHART_TOKENS } from \"@/components/mirai-calculator/calculator-ui-config\"\nimport type {\n  GraphMarkCluster,\n  GraphTrace,\n} from \"@/components/mirai-calculator/modes/graphing-state\"\nimport { Button } from \"@/components/ui/button\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { DEFAULT_GRAPH_VIEW } from \"@openmirai/calculator-core/configuration\"\nimport type { GraphView } from \"@openmirai/calculator-core/graphing-view\"\nimport { cn } from \"@/lib/utils\"\n\ninterface GraphTracePosition {\n  x: number\n  y: number\n  labelOnLeft: boolean\n  labelBelow: boolean\n}\n\ninterface GraphingCanvasViewportProps {\n  graphHostRef: React.RefObject<HTMLDivElement | null>\n  canvasRef: React.RefObject<HTMLCanvasElement | null>\n  trace: GraphTrace | null\n  tracePosition: GraphTracePosition | null\n  handlePointerDown: React.PointerEventHandler<HTMLCanvasElement>\n  handlePointerMove: React.PointerEventHandler<HTMLCanvasElement>\n  handlePointerUp: React.PointerEventHandler<HTMLCanvasElement>\n  formatNumber: (value: number) => string\n  zoom: (factor: number) => void\n  setView: (view: GraphView) => void\n  gridVisible: boolean\n  onGridVisibleChange: (visible: boolean) => void\n  analysisOpen: boolean\n  setAnalysisOpen: (open: boolean) => void\n  marks: Array<GraphMarkCluster>\n  setTrace: (trace: GraphTrace | null) => void\n}\n\n/** Renders the interactive graph canvas, controls, trace label, and analysis overlay. */\nexport function GraphingCanvasViewport({\n  graphHostRef,\n  canvasRef,\n  trace,\n  tracePosition,\n  handlePointerDown,\n  handlePointerMove,\n  handlePointerUp,\n  formatNumber,\n  zoom,\n  setView,\n  gridVisible,\n  onGridVisibleChange,\n  analysisOpen,\n  setAnalysisOpen,\n  marks,\n  setTrace,\n}: GraphingCanvasViewportProps) {\n  return (\n    <section className=\"mirai-graphing-canvas relative min-h-0 overflow-hidden overscroll-contain bg-background @max-[699px]:order-1 @max-[699px]:min-h-80\">\n      <div ref={graphHostRef} className=\"absolute inset-0\" aria-label=\"Interactive graph\">\n        <canvas\n          ref={canvasRef}\n          className=\"size-full touch-none cursor-crosshair\"\n          onPointerDown={handlePointerDown}\n          onPointerMove={handlePointerMove}\n          onPointerUp={handlePointerUp}\n          onPointerCancel={handlePointerUp}\n        />\n        {trace && tracePosition && (\n          <div\n            data-graph-trace-coordinate=\"\"\n            className=\"pointer-events-none absolute z-20\"\n            style={{ left: tracePosition.x, top: tracePosition.y }}\n          >\n            <span\n              aria-hidden=\"true\"\n              className=\"absolute size-3 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-background shadow-sm\"\n              style={{ backgroundColor: trace.color }}\n            />\n            <output\n              aria-live=\"polite\"\n              className={cn(\n                \"absolute w-max max-w-48 rounded-md border bg-background/95 px-2 py-1 font-mono text-xs text-foreground shadow-md backdrop-blur\",\n                tracePosition.labelOnLeft ? \"right-3\" : \"left-3\",\n                tracePosition.labelBelow ? \"top-3\" : \"bottom-3\"\n              )}\n            >\n              ({formatNumber(trace.x)}, {formatNumber(trace.y)})\n            </output>\n          </div>\n        )}\n      </div>\n\n      <div className=\"absolute top-3 left-3 flex rounded-lg border bg-background/95 p-1 shadow-sm backdrop-blur\">\n        <Button variant=\"ghost\" size=\"icon-sm\" onClick={() => zoom(0.7)} aria-label=\"Zoom in\">\n          <ZoomIn />\n        </Button>\n        <Button variant=\"ghost\" size=\"icon-sm\" onClick={() => zoom(1 / 0.7)} aria-label=\"Zoom out\">\n          <ZoomOut />\n        </Button>\n        <Button\n          variant=\"ghost\"\n          size=\"icon-sm\"\n          onClick={() => setView({ ...DEFAULT_GRAPH_VIEW })}\n          aria-label=\"Reset graph view\"\n        >\n          <Home />\n        </Button>\n        <Button\n          variant={gridVisible ? \"secondary\" : \"ghost\"}\n          size=\"icon-sm\"\n          onClick={() => onGridVisibleChange(!gridVisible)}\n          aria-label=\"Toggle grid\"\n        >\n          <Grid3X3 />\n        </Button>\n      </div>\n\n      {analysisOpen ? (\n        <div\n          data-graph-analysis\n          className=\"absolute right-3 bottom-3 max-h-[48%] w-[min(300px,calc(100%-1.5rem))] overflow-auto rounded-lg border bg-background/95 p-3 shadow-sm backdrop-blur\"\n        >\n          <div className=\"mb-2 flex items-center justify-between gap-2\">\n            <h3 className=\"flex items-center gap-2 text-xs font-semibold uppercase tracking-wide\">\n              <LocateFixed className=\"size-3.5 text-primary\" />\n              Analysis\n            </h3>\n            <div className=\"flex items-center\">\n              {trace && (\n                <Button variant=\"ghost\" size=\"sm\" onClick={() => setTrace(null)}>\n                  Clear trace\n                </Button>\n              )}\n              <Button\n                variant=\"ghost\"\n                size=\"icon-sm\"\n                onClick={() => setAnalysisOpen(false)}\n                aria-label=\"Hide analysis\"\n              >\n                <X />\n              </Button>\n            </div>\n          </div>\n          {trace && (\n            <>\n              <div className=\"rounded-md bg-muted px-3 py-2 font-mono text-xs\">\n                ({formatNumber(trace.x)}, {formatNumber(trace.y)})\n              </div>\n              <Separator className=\"my-2\" />\n            </>\n          )}\n          <div className=\"space-y-1\">\n            {marks.slice(0, 12).map((mark, index) => (\n              <Button\n                variant=\"ghost\"\n                key={`${mark.labels.join(\"-\")}-${mark.x}-${index}`}\n                onClick={() =>\n                  setTrace({\n                    x: mark.x,\n                    y: mark.y,\n                    color: mark.colors.at(-1) ?? CALCULATOR_CHART_TOKENS[4],\n                  })\n                }\n                className=\"h-auto w-full justify-between gap-2 px-2 py-1.5 text-xs\"\n              >\n                <span className=\"truncate capitalize\">\n                  {mark.labels.join(\" · \")}\n                  {mark.count > 1 ? ` ×${String(mark.count)}` : \"\"}\n                </span>\n                <span className=\"shrink-0 font-mono text-muted-foreground\">\n                  ({formatNumber(mark.x)}, {formatNumber(mark.y)})\n                </span>\n              </Button>\n            ))}\n            {marks.length === 0 && !trace && (\n              <p className=\"py-2 text-xs text-muted-foreground\">\n                Touch a curve to trace and drag it. Zeros, extrema, and intersections appear here.\n              </p>\n            )}\n          </div>\n        </div>\n      ) : (\n        <Button\n          variant=\"outline\"\n          size=\"sm\"\n          onClick={() => setAnalysisOpen(true)}\n          className=\"absolute right-3 bottom-3 bg-background/95 shadow-sm backdrop-blur\"\n        >\n          <LocateFixed />\n          Analysis\n        </Button>\n      )}\n    </section>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/modes/graphing-canvas-viewport.tsx"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/modes/graphing-sidebar.tsx",
      "content": "import { Play, Plus, Square, X } from \"lucide-react\"\n\nimport { Badge } from \"@/components/ui/badge\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { ScrollArea } from \"@/components/ui/scroll-area\"\nimport { CalculatorEmptyStateAction } from \"@/components/mirai-calculator/calculator-empty-state-action\"\nimport type {\n  GraphExpressionRow,\n  GraphTableColumn,\n  GraphTableRow,\n  TableGraphPoint,\n} from \"@/components/mirai-calculator/modes/graphing-state\"\nimport type { CompiledGraphExpression } from \"@openmirai/calculator-core/graphing\"\nimport type { RegressionResult } from \"@openmirai/calculator-core/statistics\"\nimport { cn } from \"@/lib/utils\"\n\ninterface GraphingSidebarProps {\n  compiled: Array<{ row: GraphExpressionRow; expression: CompiledGraphExpression }>\n  rows: Array<GraphExpressionRow>\n  tableColumns: Array<GraphTableColumn>\n  table: Array<GraphTableRow>\n  tableMinimumWidth: number\n  tableSeries: Array<{\n    column: GraphTableColumn & { color: string }\n    points: Array<TableGraphPoint>\n    regression: RegressionResult\n  }>\n  formatNumber: (value: number) => string\n  updateRow: (id: number, patch: Partial<GraphExpressionRow>) => void\n  commitRows: React.Dispatch<React.SetStateAction<Array<GraphExpressionRow>>>\n  addTableVariable: () => void\n  addTableRow: () => void\n  setTable: React.Dispatch<React.SetStateAction<Array<GraphTableRow>>>\n  setTableColumns: React.Dispatch<React.SetStateAction<Array<GraphTableColumn>>>\n  removeTableVariable: (columnId: number) => void\n  updateCell: (rowId: number, columnId: number, value: string) => void\n  addExpression: () => void\n}\n\nfunction describeGraphExpression(\n  expression: CompiledGraphExpression,\n  formatNumber: (value: number) => string\n): string {\n  switch (expression.kind) {\n    case \"invalid\":\n      return expression.message\n    case \"explicit\":\n      return \"Plotted\"\n    case \"implicit\":\n      return \"Implicit relation\"\n    case \"points\":\n      return `${expression.points.length} plotted points`\n    case \"variable\":\n      return `${expression.name} = ${formatNumber(expression.value)}`\n  }\n}\n\n/** Renders graph expressions, editable table data, and regression summaries. */\nexport function GraphingSidebar({\n  compiled,\n  rows,\n  tableColumns,\n  table,\n  tableMinimumWidth,\n  tableSeries,\n  formatNumber,\n  updateRow,\n  commitRows,\n  addTableVariable,\n  addTableRow,\n  setTable,\n  setTableColumns,\n  removeTableVariable,\n  updateCell,\n  addExpression,\n}: GraphingSidebarProps) {\n  return (\n    <aside className=\"mirai-graphing-sidebar flex min-h-0 flex-col border-r bg-card @max-[699px]:order-2 @max-[699px]:min-h-75 @max-[699px]:max-h-[50%] @max-[699px]:border-r-0 @max-[699px]:border-b\">\n      <ScrollArea className=\"min-h-0 flex-1\">\n        <div className=\"divide-y\">\n          {compiled.map(({ row, expression }) => {\n            const slider = row.slider\n            return (\n              <div key={row.id} className=\"flex gap-2.5 px-3 py-3\">\n                <Button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  onClick={() => updateRow(row.id, { visible: !row.visible })}\n                  aria-label={row.visible ? \"Hide expression\" : \"Show expression\"}\n                  className=\"mt-0.5 size-5.5 shrink-0 rounded-full border-2 p-0\"\n                  style={{\n                    borderColor: row.color,\n                    backgroundColor: row.visible ? row.color : \"transparent\",\n                  }}\n                >\n                  <span className=\"sr-only\">{row.visible ? \"Visible\" : \"Hidden\"}</span>\n                </Button>\n                <div className=\"min-w-0 flex-1 space-y-2\">\n                  <div className=\"flex items-center gap-1.5\">\n                    <Input\n                      value={row.text}\n                      onChange={(event) => updateRow(row.id, { text: event.target.value })}\n                      aria-label={`Graph expression ${row.id}`}\n                      placeholder=\"y = 2x + 5\"\n                      className=\"h-7 min-w-0 rounded-none border-0 border-b border-transparent bg-transparent px-0 font-mono text-base font-medium shadow-none focus-visible:border-primary focus-visible:ring-0 dark:bg-transparent\"\n                    />\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon-sm\"\n                      onClick={() =>\n                        commitRows((current) => current.filter((item) => item.id !== row.id))\n                      }\n                      aria-label={`Delete expression ${row.id}`}\n                      className=\"text-muted-foreground hover:text-destructive\"\n                    >\n                      <X />\n                    </Button>\n                  </div>\n                  <p\n                    className={cn(\n                      \"text-xs text-muted-foreground\",\n                      expression.kind === \"invalid\" && \"text-destructive\"\n                    )}\n                  >\n                    {describeGraphExpression(expression, formatNumber)}\n                  </p>\n                  {slider && (\n                    <div className=\"flex items-center gap-2\">\n                      <span className=\"text-xs text-muted-foreground\">{slider.min}</span>\n                      <Input\n                        type=\"range\"\n                        min={slider.min}\n                        max={slider.max}\n                        step={slider.step}\n                        value={slider.value}\n                        onChange={(event) =>\n                          updateRow(row.id, {\n                            slider: {\n                              ...slider,\n                              value: Number(event.target.value),\n                            },\n                          })\n                        }\n                        aria-label={`Slider for expression ${row.id}`}\n                        className=\"h-5 flex-1 border-0 p-0 shadow-none\"\n                      />\n                      <span className=\"text-xs text-muted-foreground\">{slider.max}</span>\n                      <Button\n                        variant=\"outline\"\n                        size=\"icon-sm\"\n                        onClick={() =>\n                          updateRow(row.id, {\n                            slider: {\n                              ...slider,\n                              playing: !slider.playing,\n                            },\n                          })\n                        }\n                        aria-label={slider.playing ? \"Stop slider\" : \"Play slider\"}\n                        className=\"rounded-full text-primary\"\n                      >\n                        {slider.playing ? <Square /> : <Play />}\n                      </Button>\n                    </div>\n                  )}\n                </div>\n              </div>\n            )\n          })}\n\n          {rows.length === 0 && (\n            <div className=\"px-4 py-6 text-center\">\n              <p className=\"text-sm font-semibold\">No expressions yet</p>\n              <p className=\"mt-1 text-xs text-muted-foreground\">\n                <CalculatorEmptyStateAction label=\"Add expression\" onClick={addExpression} /> one to\n                start graphing.\n              </p>\n            </div>\n          )}\n\n          <section className=\"p-3\">\n            <div className=\"mb-3 flex flex-wrap items-center justify-end gap-2\">\n              <h3 className=\"mr-auto text-sm font-semibold\">Table 1</h3>\n              <Button\n                variant=\"outline\"\n                size=\"sm\"\n                onClick={addTableVariable}\n                className=\"h-7 rounded-md text-xs\"\n              >\n                <Plus />\n                Variable\n              </Button>\n              <Button\n                variant=\"outline\"\n                size=\"sm\"\n                onClick={addTableRow}\n                className=\"h-7 rounded-md text-xs\"\n              >\n                <Plus />\n                Add row\n              </Button>\n              <Button\n                variant=\"outline\"\n                size=\"sm\"\n                onClick={() => setTable([])}\n                className=\"h-7 rounded-md text-xs\"\n              >\n                Clear\n              </Button>\n            </div>\n            <div className=\"mirai-graphing-table-scroll min-w-0 max-w-full overflow-x-auto overscroll-x-contain border-y contain-[inline-size]\">\n              <table\n                className=\"w-full table-fixed border-collapse text-sm\"\n                style={{ minWidth: tableMinimumWidth }}\n              >\n                <colgroup>\n                  <col className=\"w-9\" />\n                  {tableColumns.map((column) => (\n                    <col className=\"w-28\" key={column.id} />\n                  ))}\n                  <col className=\"w-8\" />\n                </colgroup>\n                <thead className=\"bg-muted/35\">\n                  <tr>\n                    <th\n                      scope=\"col\"\n                      className=\"w-9 border-r px-2 py-1 text-center font-mono text-xs text-muted-foreground\"\n                    >\n                      #\n                    </th>\n                    {tableColumns.map((column) => (\n                      <th key={column.id} scope=\"col\" className=\"border-r p-0 font-normal\">\n                        <div className=\"flex items-center\">\n                          {column.color && (\n                            <span\n                              aria-hidden=\"true\"\n                              className=\"ml-2 size-2.5 shrink-0 rounded-full\"\n                              style={{ backgroundColor: column.color }}\n                            />\n                          )}\n                          <Input\n                            value={column.label}\n                            onChange={(event) =>\n                              setTableColumns((current) =>\n                                current.map((item) =>\n                                  item.id === column.id\n                                    ? { ...item, label: event.target.value }\n                                    : item\n                                )\n                              )\n                            }\n                            aria-label={`Table variable ${column.label}`}\n                            className=\"h-8 w-full min-w-0 rounded-none border-0 bg-transparent px-2 font-mono text-sm font-semibold shadow-none focus-visible:ring-0 dark:bg-transparent\"\n                          />\n                          {column.role === \"y\" && (\n                            <Button\n                              variant=\"ghost\"\n                              size=\"icon-sm\"\n                              onClick={() => removeTableVariable(column.id)}\n                              aria-label={`Remove variable ${column.label}`}\n                              className=\"rounded-none text-muted-foreground hover:text-destructive\"\n                            >\n                              <X />\n                            </Button>\n                          )}\n                        </div>\n                      </th>\n                    ))}\n                    <th scope=\"col\" className=\"w-8\">\n                      <span className=\"sr-only\">Row actions</span>\n                    </th>\n                  </tr>\n                </thead>\n                <tbody>\n                  {table.map((row, rowIndex) => (\n                    <tr key={row.id} className=\"border-t\">\n                      <th\n                        scope=\"row\"\n                        className=\"border-r px-2 text-center font-mono text-xs font-normal text-muted-foreground\"\n                      >\n                        {rowIndex + 1}\n                      </th>\n                      {tableColumns.map((column) => (\n                        <td key={column.id} className=\"border-r p-0\">\n                          <Input\n                            value={row.values[column.id] ?? \"\"}\n                            onChange={(event) => updateCell(row.id, column.id, event.target.value)}\n                            aria-label={`${column.label} row ${rowIndex + 1}`}\n                            className=\"h-8 w-full min-w-0 rounded-none border-0 bg-transparent px-2 font-mono text-sm shadow-none focus-visible:ring-1 dark:bg-transparent\"\n                          />\n                        </td>\n                      ))}\n                      <td className=\"p-0\">\n                        <Button\n                          variant=\"ghost\"\n                          size=\"icon-sm\"\n                          onClick={() =>\n                            setTable((current) => current.filter((item) => item.id !== row.id))\n                          }\n                          aria-label={`Remove table row ${rowIndex + 1}`}\n                          className=\"rounded-none text-muted-foreground hover:text-destructive\"\n                        >\n                          <X />\n                        </Button>\n                      </td>\n                    </tr>\n                  ))}\n                  {table.length === 0 && (\n                    <tr>\n                      <td\n                        colSpan={tableColumns.length + 2}\n                        className=\"h-12 px-3 text-center text-xs text-muted-foreground\"\n                      >\n                        <CalculatorEmptyStateAction label=\"Add table row\" onClick={addTableRow} /> a\n                        row to plot table data.\n                      </td>\n                    </tr>\n                  )}\n                </tbody>\n              </table>\n            </div>\n          </section>\n\n          <section className=\"p-3\">\n            <h3 className=\"mb-3 text-sm font-semibold\">Regression</h3>\n            <div className=\"space-y-3\">\n              {tableSeries.map(({ column, regression }) => (\n                <div key={column.id}>\n                  <Badge variant=\"secondary\" className=\"mb-2 font-mono text-xs\">\n                    {column.label} ~ m{tableColumns[0]?.label ?? \"x\"} + b\n                  </Badge>\n                  <div className=\"grid grid-cols-3 gap-2\">\n                    {[\n                      [\"m\", regression.params[1]?.value],\n                      [\"b\", regression.params[0]?.value],\n                      [\"R²\", regression.r2],\n                    ].map(([label, value]) => (\n                      <div key={String(label)} className=\"rounded-lg border p-2\">\n                        <div className=\"text-xs text-muted-foreground\">{label}</div>\n                        <div\n                          className={cn(\n                            \"font-mono text-sm font-semibold\",\n                            label === \"R²\" && \"text-primary\"\n                          )}\n                        >\n                          {typeof value === \"number\" && Number.isFinite(value)\n                            ? formatNumber(value)\n                            : \"—\"}\n                        </div>\n                      </div>\n                    ))}\n                  </div>\n                </div>\n              ))}\n              {tableSeries.length === 0 && (\n                <p className=\"text-xs text-muted-foreground\">\n                  <CalculatorEmptyStateAction label=\"Add y variable\" onClick={addTableVariable} /> a\n                  y variable to fit data.\n                </p>\n              )}\n            </div>\n          </section>\n        </div>\n      </ScrollArea>\n\n      <div className=\"flex shrink-0 gap-2 border-t bg-muted/35 p-3\">\n        <Button variant=\"outline\" size=\"sm\" onClick={addExpression} className=\"flex-1 rounded-md\">\n          <Plus />\n          Expression\n        </Button>\n        <Button\n          variant=\"outline\"\n          size=\"sm\"\n          onClick={() => commitRows([])}\n          className=\"rounded-md text-muted-foreground hover:text-destructive\"\n        >\n          Clear all\n        </Button>\n      </div>\n    </aside>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/modes/graphing-sidebar.tsx"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/modes/graphing-state.ts",
      "content": "import { CALCULATOR_CHART_TOKENS } from \"@/components/mirai-calculator/calculator-ui-config\"\nimport type { GraphingInitialData } from \"@openmirai/calculator-core/graphing-data\"\nimport type { GraphPoint } from \"@openmirai/calculator-core/graphing-view\"\n\n/** Editable graph expression state owned by the graphing interface. */\nexport interface GraphExpressionRow {\n  id: number\n  text: string\n  color: string\n  visible: boolean\n  slider?: {\n    min: number\n    max: number\n    step: number\n    value: number\n    playing: boolean\n  }\n}\n\n/** Editable table row keyed by graph table column identifiers. */\nexport interface GraphTableRow {\n  id: number\n  values: Record<number, string>\n}\n\n/** Editable graph table column. */\nexport interface GraphTableColumn {\n  id: number\n  label: string\n  color?: string\n  role: \"x\" | \"y\"\n}\n\n/** Clustered graph-analysis point with presentation metadata. */\nexport interface GraphMarkCluster extends GraphPoint {\n  labels: Array<string>\n  colors: Array<string>\n  count: number\n}\n\n/** Plottable table point connected to its editable source cells. */\nexport interface TableGraphPoint extends GraphPoint {\n  rowId: number\n  columnId: number\n  color: string\n}\n\n/** Active trace point and its optional editable source. */\nexport interface GraphTrace extends GraphPoint {\n  color: string\n  source?:\n    | { kind: \"expression\"; rowId: number }\n    | { kind: \"table\"; rowId: number; columnId: number }\n}\n\n/** Maximum pointer distance in viewport pixels for selecting a graph point. */\nexport const GRAPH_POINT_HIT_RADIUS = 14\n\n/** Maximum number of implicit-render cache entries retained by the graph canvas. */\nexport const IMPLICIT_CACHE_LIMIT = 24\n\n/** Fixed graph-table index column width in pixels for canvas-independent table sizing. */\nexport const TABLE_INDEX_COLUMN_WIDTH = 36\n\n/** Fixed editable graph-table value column width. */\nexport const TABLE_VALUE_COLUMN_WIDTH = 112\n\n/** Fixed graph-table row-action column width. */\nexport const TABLE_ACTION_COLUMN_WIDTH = 32\n\n/** Converts consumer graphing defaults into editable expression rows. */\nexport function createInitialGraphExpressionRows(\n  data: GraphingInitialData | undefined\n): Array<GraphExpressionRow> {\n  return (data?.expressions ?? []).map((expression, index) => ({\n    id: index + 1,\n    text: expression.value,\n    color: expression.color ?? CALCULATOR_CHART_TOKENS[index % CALCULATOR_CHART_TOKENS.length],\n    visible: expression.visible ?? true,\n    slider: expression.slider\n      ? { ...expression.slider, playing: expression.slider.playing ?? false }\n      : undefined,\n  }))\n}\n\n/** Converts consumer graphing defaults into editable multi-variable table state. */\nexport function createInitialGraphTableState(data: GraphingInitialData | undefined): {\n  columns: Array<GraphTableColumn>\n  rows: Array<GraphTableRow>\n} {\n  const table = data?.table\n  const series = table?.series?.length\n    ? table.series\n    : [{ label: \"y₁\", color: CALCULATOR_CHART_TOKENS[2], values: [] }]\n  const columns: Array<GraphTableColumn> = [\n    { id: 1, label: table?.xLabel ?? \"x₁\", role: \"x\" },\n    ...series.map((item, index) => ({\n      id: index + 2,\n      label: item.label ?? `y${String(index + 1)}`,\n      color: item.color ?? CALCULATOR_CHART_TOKENS[(index + 2) % CALCULATOR_CHART_TOKENS.length],\n      role: \"y\" as const,\n    })),\n  ]\n  const rowCount = Math.max(\n    table?.xValues?.length ?? 0,\n    ...series.map((item) => item.values.length)\n  )\n  const rows = Array.from({ length: rowCount }, (_, rowIndex) => ({\n    id: rowIndex + 1,\n    values: Object.fromEntries([\n      [1, String(table?.xValues?.[rowIndex] ?? \"\")],\n      ...series.map((item, seriesIndex) => [seriesIndex + 2, String(item.values[rowIndex] ?? \"\")]),\n    ]),\n  }))\n  return { columns, rows }\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/modes/graphing-state.ts"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/modes/graphing-mode.tsx",
      "content": "import { useEffect, useCallback, useMemo, useRef, useState } from \"react\"\nimport { readCanvasColor, resolveCanvasColor } from \"@/components/mirai-calculator/canvas-theme\"\nimport { CALCULATOR_CHART_TOKENS } from \"@/components/mirai-calculator/calculator-ui-config\"\nimport { GraphingCanvasViewport } from \"@/components/mirai-calculator/modes/graphing-canvas-viewport\"\nimport { GraphingSidebar } from \"@/components/mirai-calculator/modes/graphing-sidebar\"\nimport {\n  GRAPH_POINT_HIT_RADIUS,\n  IMPLICIT_CACHE_LIMIT,\n  TABLE_ACTION_COLUMN_WIDTH,\n  TABLE_INDEX_COLUMN_WIDTH,\n  TABLE_VALUE_COLUMN_WIDTH,\n  createInitialGraphExpressionRows,\n  createInitialGraphTableState,\n} from \"@/components/mirai-calculator/modes/graphing-state\"\nimport type {\n  GraphExpressionRow,\n  GraphMarkCluster,\n  GraphTableColumn,\n  GraphTableRow,\n  GraphTrace,\n  TableGraphPoint,\n} from \"@/components/mirai-calculator/modes/graphing-state\"\nimport { collectSliderVariables } from \"@openmirai/calculator-core/configuration\"\nimport { CalculatorEngine } from \"@openmirai/calculator-core/engine\"\nimport type { AngleMode } from \"@openmirai/calculator-core/engine\"\nimport {\n  clusterGraphPoints,\n  compileGraphExpression,\n  findExtrema,\n  findIntersections,\n  findRoots,\n  graphGridStep,\n  sampleExplicitGraphSegments,\n  sampleImplicitContourSegments,\n} from \"@openmirai/calculator-core/graphing\"\nimport type { CompiledGraphExpression } from \"@openmirai/calculator-core/graphing\"\nimport type { GraphingInitialData } from \"@openmirai/calculator-core/graphing-data\"\nimport {\n  fitGraphViewToAspect,\n  graphPointToViewport,\n  panGraphView,\n  projectPointToGraphSegments,\n  viewportPointToGraph,\n  zoomGraphView,\n} from \"@openmirai/calculator-core/graphing-view\"\nimport type { GraphPoint, GraphSegment, GraphView } from \"@openmirai/calculator-core/graphing-view\"\nimport { fitRegression } from \"@openmirai/calculator-core/statistics\"\n\ntype ExplicitYExpression = Extract<CompiledGraphExpression, { kind: \"explicit\" }> & { axis: \"y\" }\n\nexport interface GraphingModeProps {\n  angleMode: AngleMode\n  ans: number\n  definitions: Array<string>\n  formatNumber: (value: number) => string\n  onVariablesChange: (variables: Record<string, number>) => void\n  view: GraphView\n  onViewChange: (view: GraphView) => void\n  gridVisible: boolean\n  onGridVisibleChange: (visible: boolean) => void\n  colorScheme?: \"light\" | \"dark\"\n  defaultData?: GraphingInitialData\n}\n\n/** Renders the interactive graph workspace with expressions, tables, tracing, and analysis. */\nexport function GraphingMode({\n  angleMode,\n  ans,\n  definitions,\n  formatNumber: formatValue,\n  onVariablesChange,\n  view,\n  onViewChange,\n  gridVisible,\n  onGridVisibleChange,\n  colorScheme = \"light\",\n  defaultData,\n}: GraphingModeProps) {\n  const [initialTable] = useState(() => createInitialGraphTableState(defaultData))\n  const [rows, setRows] = useState<Array<GraphExpressionRow>>(() =>\n    createInitialGraphExpressionRows(defaultData)\n  )\n  const rowsRef = useRef(rows)\n  const [tableColumns, setTableColumns] = useState<Array<GraphTableColumn>>(initialTable.columns)\n  const [table, setTable] = useState<Array<GraphTableRow>>(initialTable.rows)\n  const tableMinimumWidth =\n    TABLE_INDEX_COLUMN_WIDTH +\n    tableColumns.length * TABLE_VALUE_COLUMN_WIDTH +\n    TABLE_ACTION_COLUMN_WIDTH\n  const [trace, setTrace] = useState<GraphTrace | null>(null)\n  const [analysisOpen, setAnalysisOpen] = useState(false)\n  const [interacting, setInteracting] = useState(false)\n  const [graphSize, setGraphSize] = useState({ width: 0, height: 0 })\n  const nextRowId = useRef(rows.length + 1)\n  const nextTableRowId = useRef(table.length + 1)\n  const nextTableColumnId = useRef(tableColumns.length + 1)\n  const canvasRef = useRef<HTMLCanvasElement>(null)\n  const graphHostRef = useRef<HTMLDivElement>(null)\n  const [renderedSegmentsByRow] = useState(() => new Map<number, Array<GraphSegment>>())\n  const [implicitCache] = useState(\n    () =>\n      new Map<\n        number,\n        {\n          residual: (x: number, y: number) => number\n          key: string\n          segments: ReturnType<typeof sampleImplicitContourSegments>\n        }\n      >()\n  )\n  const interactionTimerRef = useRef<number | null>(null)\n  const dragRef = useRef<\n    | {\n        kind: \"view\"\n        pointerId: number\n        startX: number\n        startY: number\n        view: GraphView\n        renderedView: GraphView\n        moved: boolean\n      }\n    | { kind: \"trace\"; pointerId: number; rowId: number; color: string }\n    | { kind: \"table\"; pointerId: number; rowId: number; columnId: number; color: string }\n    | null\n  >(null)\n  const formatNumber = useCallback(\n    (value: number) => formatValue(Number.isFinite(value) ? Number(value.toPrecision(7)) : value),\n    [formatValue]\n  )\n  const commitRows = useCallback(\n    (\n      update:\n        | Array<GraphExpressionRow>\n        | ((current: Array<GraphExpressionRow>) => Array<GraphExpressionRow>)\n    ) => {\n      const nextRows = typeof update === \"function\" ? update(rowsRef.current) : update\n      rowsRef.current = nextRows\n      setRows(nextRows)\n      onVariablesChange(\n        collectSliderVariables(\n          nextRows.map((row) => ({ expression: row.text, value: row.slider?.value }))\n        )\n      )\n    },\n    [onVariablesChange]\n  )\n  const tracePosition = useMemo(() => {\n    if (!trace || graphSize.width <= 0 || graphSize.height <= 0) {\n      return null\n    }\n    const renderedView = fitGraphViewToAspect(view, graphSize.width, graphSize.height)\n    const point = graphPointToViewport(trace, renderedView, graphSize.width, graphSize.height)\n    if (!point) {\n      return null\n    }\n    return {\n      ...point,\n      labelOnLeft: trace.x > (renderedView.xmin + renderedView.xmax) / 2,\n      labelBelow: trace.y > renderedView.ymax - (renderedView.ymax - renderedView.ymin) * 0.12,\n    }\n  }, [graphSize.height, graphSize.width, trace, view])\n\n  const setView = useCallback(\n    (next: GraphView | ((current: GraphView) => GraphView)) => {\n      onViewChange(typeof next === \"function\" ? next(view) : next)\n    },\n    [onViewChange, view]\n  )\n\n  const variables = useMemo(\n    () =>\n      collectSliderVariables(\n        rows.map((row) => ({ expression: row.text, value: row.slider?.value }))\n      ),\n    [rows]\n  )\n\n  const engine = useMemo(\n    () =>\n      new CalculatorEngine({\n        angleMode,\n        ans,\n        definitions,\n        variables,\n      }),\n    [angleMode, ans, definitions, variables]\n  )\n\n  const compiled = useMemo(\n    () =>\n      rows.map((row) => ({\n        row,\n        expression: compileGraphExpression(row.text, engine),\n      })),\n    [engine, rows]\n  )\n\n  const tableSeries = useMemo(() => {\n    const xColumn = tableColumns.find((column) => column.role === \"x\")\n    if (!xColumn) {\n      return []\n    }\n    return tableColumns\n      .filter(\n        (column): column is GraphTableColumn & { color: string } =>\n          column.role === \"y\" && !!column.color\n      )\n      .map((column) => {\n        const points = table.flatMap<TableGraphPoint>((row) => {\n          const x = Number(row.values[xColumn.id])\n          const y = Number(row.values[column.id])\n          return Number.isFinite(x) && Number.isFinite(y)\n            ? [{ x, y, rowId: row.id, columnId: column.id, color: column.color }]\n            : []\n        })\n        return {\n          column,\n          points,\n          regression: fitRegression(\n            points.map((point) => point.x),\n            points.map((point) => point.y),\n            \"linear\"\n          ),\n        }\n      })\n  }, [table, tableColumns])\n  const tablePoints = useMemo(() => tableSeries.flatMap((series) => series.points), [tableSeries])\n\n  const marks = useMemo<Array<GraphMarkCluster>>(() => {\n    if (!analysisOpen) {\n      return []\n    }\n    const visibleFunctions = compiled.filter(\n      (\n        item\n      ): item is {\n        row: GraphExpressionRow\n        expression: ExplicitYExpression\n      } => item.row.visible && item.expression.kind === \"explicit\" && item.expression.axis === \"y\"\n    )\n    const result: Array<GraphPoint & { label: string; color: string }> = []\n\n    for (const item of visibleFunctions) {\n      try {\n        for (const x of findRoots(item.expression.evaluate, view.xmin, view.xmax)) {\n          result.push({ x, y: 0, label: \"zero\", color: item.row.color })\n        }\n        for (const point of findExtrema(item.expression.evaluate, view.xmin, view.xmax)) {\n          result.push({\n            x: point.x,\n            y: point.y,\n            label: point.kind,\n            color: item.row.color,\n          })\n        }\n      } catch {\n        // A partially defined function is still allowed to render where possible.\n      }\n    }\n\n    for (let left = 0; left < visibleFunctions.length; left += 1) {\n      for (let right = left + 1; right < visibleFunctions.length; right += 1) {\n        try {\n          for (const point of findIntersections(\n            visibleFunctions[left].expression.evaluate,\n            visibleFunctions[right].expression.evaluate,\n            view.xmin,\n            view.xmax\n          )) {\n            result.push({\n              ...point,\n              label: \"intersection\",\n              color: CALCULATOR_CHART_TOKENS[4],\n            })\n          }\n        } catch {\n          // Ignore an invalid pair while keeping the remaining graph usable.\n        }\n      }\n    }\n    const tolerance = Math.max(view.xmax - view.xmin, view.ymax - view.ymin) * 1e-6\n    return clusterGraphPoints(result.slice(0, 50), tolerance).map(({ point, indexes }) => ({\n      ...point,\n      labels: [...new Set(indexes.map((index) => result[index].label))],\n      colors: [...new Set(indexes.map((index) => result[index].color))],\n      count: indexes.length,\n    }))\n  }, [analysisOpen, compiled, view.xmax, view.xmin, view.ymax, view.ymin])\n\n  useEffect(() => {\n    const canvas = canvasRef.current\n    const host = graphHostRef.current\n    if (!canvas || !host) {\n      return\n    }\n\n    const draw = () => {\n      const bounds = host.getBoundingClientRect()\n      const width = Math.max(1, Math.floor(bounds.width))\n      const height = Math.max(1, Math.floor(bounds.height))\n      setGraphSize((current) =>\n        current.width === width && current.height === height ? current : { width, height }\n      )\n      const ratio = Math.min(window.devicePixelRatio || 1, 2)\n      canvas.width = width * ratio\n      canvas.height = height * ratio\n      canvas.style.width = `${width}px`\n      canvas.style.height = `${height}px`\n      const context = canvas.getContext(\"2d\")\n      if (!context) {\n        return\n      }\n      context.setTransform(ratio, 0, 0, ratio, 0, 0)\n\n      const background = readCanvasColor(canvas, \"--background\")\n      const border = readCanvasColor(canvas, \"--border\")\n      const muted = readCanvasColor(canvas, \"--muted-foreground\")\n      const renderedView = fitGraphViewToAspect(view, width, height)\n      const xToCanvas = (x: number) =>\n        ((x - renderedView.xmin) / (renderedView.xmax - renderedView.xmin)) * width\n      const yToCanvas = (y: number) =>\n        height - ((y - renderedView.ymin) / (renderedView.ymax - renderedView.ymin)) * height\n      const renderedSegments = new Map<number, Array<GraphSegment>>()\n\n      context.clearRect(0, 0, width, height)\n      context.fillStyle = background\n      context.fillRect(0, 0, width, height)\n\n      if (gridVisible) {\n        const xStep = graphGridStep(renderedView.xmax - renderedView.xmin)\n        const yStep = graphGridStep(renderedView.ymax - renderedView.ymin)\n        context.strokeStyle = border\n        context.lineWidth = 1\n        context.font = \"10px system-ui, -apple-system, sans-serif\"\n        context.fillStyle = muted\n\n        for (\n          let x = Math.ceil(renderedView.xmin / xStep) * xStep;\n          x <= renderedView.xmax;\n          x += xStep\n        ) {\n          const pixel = xToCanvas(x)\n          context.beginPath()\n          context.moveTo(pixel, 0)\n          context.lineTo(pixel, height)\n          context.stroke()\n          if (Math.abs(x) > 1e-10) {\n            context.fillText(formatNumber(x), pixel + 4, yToCanvas(0) + 12)\n          }\n        }\n        for (\n          let y = Math.ceil(renderedView.ymin / yStep) * yStep;\n          y <= renderedView.ymax;\n          y += yStep\n        ) {\n          const pixel = yToCanvas(y)\n          context.beginPath()\n          context.moveTo(0, pixel)\n          context.lineTo(width, pixel)\n          context.stroke()\n          if (Math.abs(y) > 1e-10) {\n            context.fillText(formatNumber(y), xToCanvas(0) + 5, pixel - 4)\n          }\n        }\n      }\n\n      context.strokeStyle = muted\n      context.lineWidth = 1.3\n      if (renderedView.ymin <= 0 && renderedView.ymax >= 0) {\n        context.beginPath()\n        context.moveTo(0, yToCanvas(0))\n        context.lineTo(width, yToCanvas(0))\n        context.stroke()\n      }\n      if (renderedView.xmin <= 0 && renderedView.xmax >= 0) {\n        context.beginPath()\n        context.moveTo(xToCanvas(0), 0)\n        context.lineTo(xToCanvas(0), height)\n        context.stroke()\n      }\n\n      for (const { row, expression } of compiled) {\n        if (!row.visible || expression.kind === \"invalid\") {\n          continue\n        }\n        const rowColor = resolveCanvasColor(canvas, row.color)\n        context.strokeStyle = rowColor\n        context.fillStyle = rowColor\n        context.lineWidth = 2.2\n        context.lineCap = \"round\"\n        context.lineJoin = \"round\"\n\n        if (expression.kind === \"explicit\") {\n          const segments = sampleExplicitGraphSegments(\n            expression.evaluate,\n            expression.axis,\n            renderedView,\n            width,\n            height\n          )\n          context.beginPath()\n          for (const segment of segments) {\n            const { from, to } = segment\n            context.moveTo(xToCanvas(from.x), yToCanvas(from.y))\n            context.lineTo(xToCanvas(to.x), yToCanvas(to.y))\n          }\n          context.stroke()\n          renderedSegments.set(row.id, segments)\n        } else if (expression.kind === \"points\") {\n          for (const point of expression.points) {\n            context.beginPath()\n            context.arc(xToCanvas(point.x), yToCanvas(point.y), 4.5, 0, Math.PI * 2)\n            context.fill()\n          }\n        } else if (expression.kind === \"implicit\") {\n          const columns = interacting\n            ? Math.max(36, Math.min(72, Math.ceil(width / 14)))\n            : Math.max(48, Math.min(120, Math.ceil(width / 9)))\n          const rowsCount = interacting\n            ? Math.max(32, Math.min(56, Math.ceil(height / 14)))\n            : Math.max(40, Math.min(96, Math.ceil(height / 9)))\n          const cacheKey = [\n            renderedView.xmin,\n            renderedView.xmax,\n            renderedView.ymin,\n            renderedView.ymax,\n            columns,\n            rowsCount,\n          ].join(\":\")\n          const cached = implicitCache.get(row.id)\n          const segments =\n            cached?.residual === expression.residual && cached.key === cacheKey\n              ? cached.segments\n              : sampleImplicitContourSegments(expression.residual, renderedView, columns, rowsCount)\n          if (segments !== cached?.segments) {\n            implicitCache.set(row.id, {\n              residual: expression.residual,\n              key: cacheKey,\n              segments,\n            })\n            if (implicitCache.size > IMPLICIT_CACHE_LIMIT) {\n              const oldestRowId = implicitCache.keys().next().value\n              if (typeof oldestRowId === \"number\") {\n                implicitCache.delete(oldestRowId)\n              }\n            }\n          }\n          const isolatedPoints: Array<GraphPoint> = []\n          context.beginPath()\n          for (const segment of segments) {\n            const fromX = xToCanvas(segment.from.x)\n            const fromY = yToCanvas(segment.from.y)\n            const toX = xToCanvas(segment.to.x)\n            const toY = yToCanvas(segment.to.y)\n            if (segment.from.x === segment.to.x && segment.from.y === segment.to.y) {\n              isolatedPoints.push({ x: fromX, y: fromY })\n              continue\n            }\n            context.moveTo(fromX, fromY)\n            context.lineTo(toX, toY)\n          }\n          context.stroke()\n          for (const point of isolatedPoints) {\n            context.beginPath()\n            context.arc(point.x, point.y, 2.2, 0, Math.PI * 2)\n            context.fill()\n          }\n          renderedSegments.set(row.id, segments)\n        }\n      }\n\n      renderedSegmentsByRow.clear()\n      for (const [rowId, segments] of renderedSegments) {\n        renderedSegmentsByRow.set(rowId, segments)\n      }\n\n      for (const point of tablePoints) {\n        context.fillStyle = resolveCanvasColor(canvas, point.color)\n        context.beginPath()\n        context.arc(xToCanvas(point.x), yToCanvas(point.y), 4.5, 0, Math.PI * 2)\n        context.fill()\n      }\n\n      for (const mark of marks) {\n        context.fillStyle = background\n        context.strokeStyle = resolveCanvasColor(\n          canvas,\n          mark.colors.at(-1) ?? CALCULATOR_CHART_TOKENS[4]\n        )\n        context.lineWidth = 2\n        context.beginPath()\n        context.arc(xToCanvas(mark.x), yToCanvas(mark.y), 5, 0, Math.PI * 2)\n        context.fill()\n        context.stroke()\n      }\n    }\n\n    let frame = window.requestAnimationFrame(draw)\n    const scheduleDraw = () => {\n      window.cancelAnimationFrame(frame)\n      frame = window.requestAnimationFrame(draw)\n    }\n    const observer = new ResizeObserver(scheduleDraw)\n    observer.observe(host)\n    return () => {\n      window.cancelAnimationFrame(frame)\n      observer.disconnect()\n    }\n  }, [\n    colorScheme,\n    compiled,\n    formatNumber,\n    gridVisible,\n    implicitCache,\n    interacting,\n    marks,\n    renderedSegmentsByRow,\n    tablePoints,\n    view,\n  ])\n\n  const hasPlayingSliders = rows.some((row) => row.slider?.playing)\n\n  useEffect(() => {\n    if (!hasPlayingSliders) {\n      return\n    }\n    const timer = window.setInterval(() => {\n      commitRows((currentRows) =>\n        currentRows.map((current) => {\n          if (!current.slider?.playing) {\n            return current\n          }\n          const next = current.slider.value + current.slider.step\n          return {\n            ...current,\n            slider: {\n              ...current.slider,\n              value: next > current.slider.max ? current.slider.min : next,\n            },\n          }\n        })\n      )\n    }, 100)\n    return () => window.clearInterval(timer)\n  }, [commitRows, hasPlayingSliders])\n\n  const zoom = useCallback(\n    (factor: number) => {\n      setView((current) => zoomGraphView(current, factor))\n    },\n    [setView]\n  )\n\n  useEffect(() => {\n    const canvas = canvasRef.current\n    if (!canvas) {\n      return\n    }\n\n    const handleWheel = (event: WheelEvent) => {\n      event.preventDefault()\n      event.stopPropagation()\n      setInteracting(true)\n      if (interactionTimerRef.current !== null) {\n        window.clearTimeout(interactionTimerRef.current)\n      }\n      interactionTimerRef.current = window.setTimeout(() => {\n        setInteracting(false)\n        interactionTimerRef.current = null\n      }, 140)\n      zoom(event.deltaY > 0 ? 1.12 : 1 / 1.12)\n    }\n\n    canvas.addEventListener(\"wheel\", handleWheel, { passive: false })\n    return () => {\n      canvas.removeEventListener(\"wheel\", handleWheel)\n      if (interactionTimerRef.current !== null) {\n        window.clearTimeout(interactionTimerRef.current)\n      }\n    }\n  }, [zoom])\n\n  const graphCoordinates = (clientX: number, clientY: number): GraphPoint => {\n    const canvas = canvasRef.current\n    if (!canvas) {\n      return { x: view.xmin, y: view.ymax }\n    }\n    const bounds = canvas.getBoundingClientRect()\n    const renderedView = fitGraphViewToAspect(view, bounds.width, bounds.height)\n    return (\n      viewportPointToGraph(\n        { x: clientX - bounds.left, y: clientY - bounds.top },\n        renderedView,\n        bounds.width,\n        bounds.height\n      ) ?? { x: renderedView.xmin, y: renderedView.ymax }\n    )\n  }\n\n  const handlePointerDown = (event: React.PointerEvent<HTMLCanvasElement>) => {\n    event.currentTarget.setPointerCapture(event.pointerId)\n    const bounds = event.currentTarget.getBoundingClientRect()\n    const measuredSize = {\n      width: Math.max(1, Math.floor(bounds.width)),\n      height: Math.max(1, Math.floor(bounds.height)),\n    }\n    setGraphSize((current) =>\n      current.width === measuredSize.width && current.height === measuredSize.height\n        ? current\n        : measuredSize\n    )\n    const renderedView = fitGraphViewToAspect(view, bounds.width, bounds.height)\n    const graphPoint = graphCoordinates(event.clientX, event.clientY)\n    const pointerX = event.clientX - bounds.left\n    const pointerY = event.clientY - bounds.top\n    const tablePoint = [...tablePoints].reverse().find((point) => {\n      const viewportPoint = graphPointToViewport(point, renderedView, bounds.width, bounds.height)\n      return (\n        viewportPoint !== null &&\n        Math.hypot(viewportPoint.x - pointerX, viewportPoint.y - pointerY) <= GRAPH_POINT_HIT_RADIUS\n      )\n    })\n    if (tablePoint) {\n      setTrace({\n        ...tablePoint,\n        source: { kind: \"table\", rowId: tablePoint.rowId, columnId: tablePoint.columnId },\n      })\n      dragRef.current = {\n        kind: \"table\",\n        pointerId: event.pointerId,\n        rowId: tablePoint.rowId,\n        columnId: tablePoint.columnId,\n        color: tablePoint.color,\n      }\n      setInteracting(true)\n      return\n    }\n\n    let closestCurve: { row: GraphExpressionRow; point: GraphPoint } | null = null\n    let closestDistance = GRAPH_POINT_HIT_RADIUS + 1\n    for (const item of compiled) {\n      const { expression, row } = item\n      if (!row.visible || (expression.kind !== \"explicit\" && expression.kind !== \"implicit\")) {\n        continue\n      }\n      const renderedSegments = renderedSegmentsByRow.get(row.id)\n      const fallbackSegments =\n        expression.kind === \"explicit\"\n          ? sampleExplicitGraphSegments(\n              expression.evaluate,\n              expression.axis,\n              renderedView,\n              bounds.width,\n              bounds.height\n            )\n          : []\n      if (!renderedSegments && fallbackSegments.length > 0) {\n        renderedSegmentsByRow.set(row.id, fallbackSegments)\n      }\n      const projection = projectPointToGraphSegments(\n        renderedSegments ?? fallbackSegments,\n        graphPoint,\n        renderedView,\n        bounds.width,\n        bounds.height\n      )\n      if (projection && projection.distance <= closestDistance) {\n        closestCurve = { row, point: projection.point }\n        closestDistance = projection.distance\n      }\n    }\n    if (closestCurve) {\n      setTrace({\n        ...closestCurve.point,\n        color: closestCurve.row.color,\n        source: { kind: \"expression\", rowId: closestCurve.row.id },\n      })\n      dragRef.current = {\n        kind: \"trace\",\n        pointerId: event.pointerId,\n        rowId: closestCurve.row.id,\n        color: closestCurve.row.color,\n      }\n      setInteracting(true)\n      return\n    }\n\n    dragRef.current = {\n      kind: \"view\",\n      pointerId: event.pointerId,\n      startX: event.clientX,\n      startY: event.clientY,\n      view,\n      renderedView,\n      moved: false,\n    }\n    setInteracting(true)\n  }\n\n  const handlePointerMove = (event: React.PointerEvent<HTMLCanvasElement>) => {\n    const drag = dragRef.current\n    const bounds = event.currentTarget.getBoundingClientRect()\n    if (!drag || drag.pointerId !== event.pointerId) {\n      return\n    }\n    event.preventDefault()\n    const point = graphCoordinates(event.clientX, event.clientY)\n\n    if (drag.kind === \"trace\") {\n      const renderedView = fitGraphViewToAspect(view, bounds.width, bounds.height)\n      const projection = projectPointToGraphSegments(\n        renderedSegmentsByRow.get(drag.rowId) ?? [],\n        point,\n        renderedView,\n        bounds.width,\n        bounds.height\n      )\n      if (projection) {\n        setTrace({\n          ...projection.point,\n          color: drag.color,\n          source: { kind: \"expression\", rowId: drag.rowId },\n        })\n      }\n      return\n    }\n\n    if (drag.kind === \"table\") {\n      const xColumn = tableColumns.find((column) => column.role === \"x\")\n      if (!xColumn) {\n        return\n      }\n      const xValue = String(Number(point.x.toPrecision(10)))\n      const yValue = String(Number(point.y.toPrecision(10)))\n      setTable((current) =>\n        current.map((row) =>\n          row.id === drag.rowId\n            ? {\n                ...row,\n                values: {\n                  ...row.values,\n                  [xColumn.id]: xValue,\n                  [drag.columnId]: yValue,\n                },\n              }\n            : row\n        )\n      )\n      setTrace({\n        x: point.x,\n        y: point.y,\n        color: drag.color,\n        source: { kind: \"table\", rowId: drag.rowId, columnId: drag.columnId },\n      })\n      return\n    }\n\n    const dx = event.clientX - drag.startX\n    const dy = event.clientY - drag.startY\n    if (Math.abs(dx) + Math.abs(dy) > 3) {\n      drag.moved = true\n    }\n    setView(panGraphView(drag.view, drag.renderedView, dx, dy, bounds.width, bounds.height))\n  }\n\n  const handlePointerUp = (event: React.PointerEvent<HTMLCanvasElement>) => {\n    const completedDrag = dragRef.current\n    dragRef.current = null\n    if (completedDrag?.kind === \"trace\" || completedDrag?.kind === \"table\") {\n      setTrace(null)\n    }\n    setInteracting(false)\n    if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n      event.currentTarget.releasePointerCapture(event.pointerId)\n    }\n  }\n\n  const updateRow = (id: number, patch: Partial<GraphExpressionRow>) => {\n    commitRows((current) => current.map((row) => (row.id === id ? { ...row, ...patch } : row)))\n  }\n\n  const addExpression = () => {\n    const id = nextRowId.current\n    nextRowId.current += 1\n    commitRows((current) => [\n      ...current,\n      {\n        id,\n        text: \"\",\n        color: CALCULATOR_CHART_TOKENS[(id - 1) % CALCULATOR_CHART_TOKENS.length],\n        visible: true,\n      },\n    ])\n  }\n\n  const updateCell = (rowId: number, columnId: number, value: string) => {\n    setTable((current) =>\n      current.map((row) =>\n        row.id === rowId ? { ...row, values: { ...row.values, [columnId]: value } } : row\n      )\n    )\n  }\n\n  const addTableRow = () => {\n    const id = nextTableRowId.current\n    nextTableRowId.current += 1\n    setTable((current) => [\n      ...current,\n      { id, values: Object.fromEntries(tableColumns.map((column) => [column.id, \"\"])) },\n    ])\n  }\n\n  const addTableVariable = () => {\n    const id = nextTableColumnId.current\n    nextTableColumnId.current += 1\n    const variableIndex = tableColumns.filter((column) => column.role === \"y\").length + 1\n    setTableColumns((current) => [\n      ...current,\n      {\n        id,\n        label: `y${String(variableIndex)}`,\n        color: CALCULATOR_CHART_TOKENS[(variableIndex + 1) % CALCULATOR_CHART_TOKENS.length],\n        role: \"y\",\n      },\n    ])\n  }\n\n  const removeTableVariable = (columnId: number) => {\n    setTableColumns((current) => current.filter((column) => column.id !== columnId))\n    setTable((current) =>\n      current.map((row) => {\n        const values = { ...row.values }\n        delete values[columnId]\n        return { ...row, values }\n      })\n    )\n  }\n\n  return (\n    <div className=\"mirai-graphing-layout grid min-h-0 flex-1 grid-cols-[340px_minmax(0,1fr)] overflow-hidden @max-[699px]:grid-cols-1 @max-[699px]:overflow-y-auto\">\n      <GraphingSidebar\n        compiled={compiled}\n        rows={rows}\n        tableColumns={tableColumns}\n        table={table}\n        tableMinimumWidth={tableMinimumWidth}\n        tableSeries={tableSeries}\n        formatNumber={formatNumber}\n        updateRow={updateRow}\n        commitRows={commitRows}\n        addTableVariable={addTableVariable}\n        addTableRow={addTableRow}\n        setTable={setTable}\n        setTableColumns={setTableColumns}\n        removeTableVariable={removeTableVariable}\n        updateCell={updateCell}\n        addExpression={addExpression}\n      />\n\n      <GraphingCanvasViewport\n        graphHostRef={graphHostRef}\n        canvasRef={canvasRef}\n        trace={trace}\n        tracePosition={tracePosition}\n        handlePointerDown={handlePointerDown}\n        handlePointerMove={handlePointerMove}\n        handlePointerUp={handlePointerUp}\n        formatNumber={formatNumber}\n        zoom={zoom}\n        setView={setView}\n        gridVisible={gridVisible}\n        onGridVisibleChange={onGridVisibleChange}\n        analysisOpen={analysisOpen}\n        setAnalysisOpen={setAnalysisOpen}\n        marks={marks}\n        setTrace={setTrace}\n      />\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/modes/graphing-mode.tsx"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/modes/scientific-key-label.tsx",
      "content": "import type { ScientificKeyDefinition } from \"@/components/mirai-calculator/modes/scientific-keypad-config\"\n\n/** Renders structured keypad notation with the host application's mathematical font fallback. */\nexport function ScientificKeyLabel({ definition }: { definition: ScientificKeyDefinition }) {\n  const notation = definition.notation\n  if (!notation) {\n    return definition.label\n  }\n\n  return (\n    <span\n      aria-hidden=\"true\"\n      data-math-notation=\"\"\n      className=\"inline-flex items-baseline font-[math,var(--font-sans,system-ui),sans-serif]! leading-none\"\n    >\n      {notation.kind === \"fraction\" && (\n        <span data-math-fraction=\"\">\n          {notation.numerator}⁄{notation.denominator}\n        </span>\n      )}\n      {notation.kind === \"root\" && (\n        <span>\n          <sup className=\"mr-px leading-none\">{notation.index}</sup>√{notation.radicand}\n        </span>\n      )}\n      {notation.kind === \"subscript\" && (\n        <span>\n          {notation.base}\n          <sub className=\"leading-none\">{notation.script}</sub>\n        </span>\n      )}\n      {notation.kind === \"superscript\" && (\n        <span>\n          {notation.base}\n          <sup className=\"leading-none\">{notation.script}</sup>\n        </span>\n      )}\n    </span>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/modes/scientific-key-label.tsx"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/modes/scientific-keypad.tsx",
      "content": "import { ScientificKeyLabel } from \"@/components/mirai-calculator/modes/scientific-key-label\"\nimport {\n  SCIENTIFIC_KEY_SETS,\n  getScientificKeyLabelSize,\n} from \"@/components/mirai-calculator/modes/scientific-keypad-config\"\nimport type {\n  ScientificKeyDefinition,\n  ScientificKeypadTab,\n} from \"@/components/mirai-calculator/modes/scientific-keypad-config\"\nimport { Button } from \"@/components/ui/button\"\nimport { Tabs, TabsList, TabsTrigger } from \"@/components/ui/tabs\"\nimport { cn } from \"@/lib/utils\"\n\ninterface ScientificKeypadProps {\n  tab: ScientificKeypadTab\n  onTabChange: (tab: ScientificKeypadTab) => void\n  onPressKey: (key: ScientificKeyDefinition) => void\n}\n\n/** Renders the scientific keypad tabs and their calculator keys. */\nexport function ScientificKeypad({ tab, onTabChange, onPressKey }: ScientificKeypadProps) {\n  return (\n    <Tabs\n      value={tab}\n      onValueChange={(value) => {\n        switch (value) {\n          case \"basic\":\n          case \"functions\":\n          case \"trig\":\n          case \"stats\":\n          case \"variables\":\n            onTabChange(value)\n            break\n        }\n      }}\n      className=\"min-h-0 flex-1 gap-0\"\n    >\n      <TabsList\n        variant=\"line\"\n        className=\"mirai-keypad-tabs-list h-10! w-full flex-none justify-start gap-1 overflow-x-auto overflow-y-hidden overscroll-x-contain scroll-px-4 rounded-none border-y bg-muted/45 px-4 py-0\"\n      >\n        <TabsTrigger\n          className=\"mirai-keypad-tab h-10 flex-none rounded-none bg-transparent! px-3 text-sm font-semibold text-muted-foreground shadow-none! hover:bg-transparent! data-active:bg-transparent! data-active:text-primary after:right-0 after:-bottom-px! after:left-0 after:h-0.5! after:bg-primary!\"\n          value=\"basic\"\n        >\n          Basic\n        </TabsTrigger>\n        <TabsTrigger\n          className=\"mirai-keypad-tab h-10 flex-none rounded-none bg-transparent! px-3 text-sm font-semibold text-muted-foreground shadow-none! hover:bg-transparent! data-active:bg-transparent! data-active:text-primary after:right-0 after:-bottom-px! after:left-0 after:h-0.5! after:bg-primary!\"\n          value=\"functions\"\n        >\n          Functions\n        </TabsTrigger>\n        <TabsTrigger\n          className=\"mirai-keypad-tab h-10 flex-none rounded-none bg-transparent! px-3 text-sm font-semibold text-muted-foreground shadow-none! hover:bg-transparent! data-active:bg-transparent! data-active:text-primary after:right-0 after:-bottom-px! after:left-0 after:h-0.5! after:bg-primary!\"\n          value=\"trig\"\n        >\n          Trig\n        </TabsTrigger>\n        <TabsTrigger\n          className=\"mirai-keypad-tab h-10 flex-none rounded-none bg-transparent! px-3 text-sm font-semibold text-muted-foreground shadow-none! hover:bg-transparent! data-active:bg-transparent! data-active:text-primary after:right-0 after:-bottom-px! after:left-0 after:h-0.5! after:bg-primary!\"\n          value=\"stats\"\n        >\n          Stats\n        </TabsTrigger>\n        <TabsTrigger\n          className=\"mirai-keypad-tab h-10 flex-none rounded-none bg-transparent! px-3 text-sm font-semibold text-muted-foreground shadow-none! hover:bg-transparent! data-active:bg-transparent! data-active:text-primary after:right-0 after:-bottom-px! after:left-0 after:h-0.5! after:bg-primary!\"\n          value=\"variables\"\n        >\n          Variables\n        </TabsTrigger>\n      </TabsList>\n      <div className=\"grid min-h-61.5 flex-1 grid-cols-6 auto-rows-fr gap-2 p-4\">\n        {SCIENTIFIC_KEY_SETS[tab].map((key) => {\n          const Icon = key.icon\n          return (\n            <Button\n              key={key.ariaLabel ?? key.label}\n              variant={key.tone === \"primary\" ? \"default\" : \"outline\"}\n              onClick={() => onPressKey(key)}\n              aria-label={key.ariaLabel ?? key.label}\n              data-key-label-size={key.icon ? \"single\" : getScientificKeyLabelSize(key.label)}\n              className={cn(\n                \"mirai-scientific-key h-auto min-h-11 min-w-0 overflow-hidden rounded-[6px] px-[clamp(0.125rem,1cqi,0.75rem)] font-sans text-sm leading-tight font-medium whitespace-nowrap data-[key-label-size=single]:text-lg data-[key-label-size=short]:text-base data-[key-label-size=medium]:text-sm data-[key-label-size=long]:px-px data-[key-label-size=long]:text-xs\",\n                key.tone === \"operator\" &&\n                  \"border-primary/25 bg-primary/5 text-primary hover:bg-primary/10\",\n                key.span === 2 && \"col-span-2\"\n              )}\n            >\n              {Icon ? <Icon aria-hidden=\"true\" /> : <ScientificKeyLabel definition={key} />}\n            </Button>\n          )\n        })}\n      </div>\n    </Tabs>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/modes/scientific-keypad.tsx"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/modes/scientific-keypad-config.ts",
      "content": "import { Delete } from \"lucide-react\"\nimport type { LucideIcon } from \"lucide-react\"\n\nexport interface ScientificKeyDefinition {\n  label: string\n  notation?: ScientificKeyNotation\n  icon?: LucideIcon\n  token?: string\n  action?: \"absolute\" | \"backspace\" | \"clear\" | \"evaluate\" | \"left\" | \"right\"\n  tone?: \"default\" | \"operator\" | \"primary\"\n  ariaLabel?: string\n  span?: 2\n}\n\ntype ScientificKeyNotation =\n  | { kind: \"fraction\"; numerator: string; denominator: string }\n  | { kind: \"root\"; index: string; radicand: string }\n  | { kind: \"subscript\"; base: string; script: string }\n  | { kind: \"superscript\"; base: string; script: string }\n\nconst BASIC_KEYS: Array<ScientificKeyDefinition> = [\n  { label: \"(\" },\n  { label: \")\" },\n  { label: \"%\", ariaLabel: \"Percent\" },\n  { label: \"÷\", tone: \"operator\", ariaLabel: \"Divide\" },\n  {\n    label: \"Backspace\",\n    icon: Delete,\n    action: \"backspace\",\n    ariaLabel: \"Backspace\",\n  },\n  { label: \"C\", action: \"clear\", ariaLabel: \"Clear\" },\n  { label: \"7\" },\n  { label: \"8\" },\n  { label: \"9\" },\n  { label: \"×\", tone: \"operator\", ariaLabel: \"Multiply\" },\n  { label: \"x²\", token: \"²\", notation: { kind: \"superscript\", base: \"x\", script: \"2\" } },\n  { label: \"√\", token: \"√(\" },\n  { label: \"4\" },\n  { label: \"5\" },\n  { label: \"6\" },\n  { label: \"−\", tone: \"operator\", ariaLabel: \"Subtract\" },\n  { label: \"xʸ\", token: \"^\", notation: { kind: \"superscript\", base: \"x\", script: \"y\" } },\n  { label: \"π\", token: \"π\" },\n  { label: \"1\" },\n  { label: \"2\" },\n  { label: \"3\" },\n  { label: \"+\", tone: \"operator\", ariaLabel: \"Add\" },\n  { label: \"|x|\", action: \"absolute\", ariaLabel: \"Absolute value\" },\n  { label: \"e\", token: \"e\" },\n  { label: \"±\", token: \"−\", ariaLabel: \"Toggle sign\" },\n  { label: \"0\" },\n  { label: \".\" },\n  {\n    label: \"a⁄b\",\n    token: \"÷\",\n    ariaLabel: \"Fraction\",\n    notation: { kind: \"fraction\", numerator: \"a\", denominator: \"b\" },\n  },\n  {\n    label: \"=\",\n    action: \"evaluate\",\n    tone: \"primary\",\n    ariaLabel: \"Equals\",\n    span: 2,\n  },\n]\n\nconst FUNCTION_KEYS: Array<ScientificKeyDefinition> = [\n  { label: \"x²\", token: \"²\", notation: { kind: \"superscript\", base: \"x\", script: \"2\" } },\n  { label: \"x³\", token: \"³\", notation: { kind: \"superscript\", base: \"x\", script: \"3\" } },\n  { label: \"xʸ\", token: \"^\", notation: { kind: \"superscript\", base: \"x\", script: \"y\" } },\n  { label: \"√\", token: \"√(\" },\n  { label: \"∛\", token: \"∛(\" },\n  {\n    label: \"1/x\",\n    token: \"1÷(\",\n    notation: { kind: \"fraction\", numerator: \"1\", denominator: \"x\" },\n  },\n  { label: \"|x|\", action: \"absolute\", ariaLabel: \"Absolute value\" },\n  {\n    label: \"10ˣ\",\n    token: \"10^(\",\n    notation: { kind: \"superscript\", base: \"10\", script: \"x\" },\n  },\n  { label: \"eˣ\", token: \"exp(\", notation: { kind: \"superscript\", base: \"e\", script: \"x\" } },\n  { label: \"log\", token: \"log(\" },\n  { label: \"ln\", token: \"ln(\" },\n  {\n    label: \"logᵦ\",\n    token: \"logb(\",\n    ariaLabel: \"Log base\",\n    notation: { kind: \"subscript\", base: \"log\", script: \"b\" },\n  },\n  {\n    label: \"n√x\",\n    token: \"nthroot(\",\n    ariaLabel: \"Nth root\",\n    notation: { kind: \"root\", index: \"n\", radicand: \"x\" },\n  },\n  { label: \"x!\", token: \"!\" },\n  { label: \"%\", token: \"%\" },\n  { label: \"abs\", token: \"abs(\" },\n  { label: \"floor\", token: \"floor(\" },\n  { label: \"ceil\", token: \"ceil(\" },\n  { label: \"round\", token: \"round(\" },\n  { label: \"mod\", token: \"mod(\" },\n]\n\nconst TRIG_KEYS: Array<ScientificKeyDefinition> = [\n  { label: \"sin\", token: \"sin(\" },\n  { label: \"cos\", token: \"cos(\" },\n  { label: \"tan\", token: \"tan(\" },\n  { label: \"sec\", token: \"sec(\" },\n  { label: \"csc\", token: \"csc(\" },\n  { label: \"cot\", token: \"cot(\" },\n  {\n    label: \"sin⁻¹\",\n    token: \"asin(\",\n    notation: { kind: \"superscript\", base: \"sin\", script: \"−1\" },\n  },\n  {\n    label: \"cos⁻¹\",\n    token: \"acos(\",\n    notation: { kind: \"superscript\", base: \"cos\", script: \"−1\" },\n  },\n  {\n    label: \"tan⁻¹\",\n    token: \"atan(\",\n    notation: { kind: \"superscript\", base: \"tan\", script: \"−1\" },\n  },\n  { label: \"sinh\", token: \"sinh(\" },\n  { label: \"cosh\", token: \"cosh(\" },\n  { label: \"tanh\", token: \"tanh(\" },\n  {\n    label: \"π/2\",\n    token: \"π÷2\",\n    notation: { kind: \"fraction\", numerator: \"π\", denominator: \"2\" },\n  },\n  {\n    label: \"π/3\",\n    token: \"π÷3\",\n    notation: { kind: \"fraction\", numerator: \"π\", denominator: \"3\" },\n  },\n  {\n    label: \"π/4\",\n    token: \"π÷4\",\n    notation: { kind: \"fraction\", numerator: \"π\", denominator: \"4\" },\n  },\n  {\n    label: \"π/6\",\n    token: \"π÷6\",\n    notation: { kind: \"fraction\", numerator: \"π\", denominator: \"6\" },\n  },\n  { label: \"deg→rad\", token: \"×π÷180\" },\n  { label: \"rad→deg\", token: \"×180÷π\" },\n  { label: \"(\", token: \"(\" },\n  { label: \")\", token: \")\" },\n]\n\nconst STATS_KEYS: Array<ScientificKeyDefinition> = [\n  { label: \"[ ]\", token: \"[\" },\n  { label: \",\", token: \",\" },\n  { label: \"mean\", token: \"mean([\" },\n  { label: \"median\", token: \"median([\" },\n  { label: \"mode\", token: \"mode([\" },\n  { label: \"Σ\", token: \"sum([\" },\n  { label: \"min\", token: \"min([\" },\n  { label: \"max\", token: \"max([\" },\n  { label: \"range\", token: \"range([\" },\n  {\n    label: \"Q₁\",\n    token: \"quartile([\",\n    notation: { kind: \"subscript\", base: \"Q\", script: \"1\" },\n  },\n  { label: \"σ\", token: \"stdevp([\" },\n  { label: \"s\", token: \"stdev([\" },\n  { label: \"variance\", token: \"var([\" },\n  { label: \"IQR\", token: \"iqr([\" },\n  { label: \"sort\", token: \"sort([\" },\n  { label: \"nCr\", token: \"ncr(\" },\n  { label: \"nPr\", token: \"npr(\" },\n  { label: \"gcd\", token: \"gcd(\" },\n  { label: \"lcm\", token: \"lcm(\" },\n  { label: \"corr\", token: \"corr([\" },\n]\n\nconst VARIABLE_KEYS: Array<ScientificKeyDefinition> = [\n  { label: \"a\" },\n  { label: \"b\" },\n  { label: \"c\" },\n  { label: \"d\" },\n  { label: \"x\" },\n  { label: \"y\" },\n  { label: \"n\" },\n  { label: \"r\" },\n  { label: \"t\" },\n  { label: \"Ans\", token: \"Ans\" },\n  { label: \"π\", token: \"π\" },\n  { label: \"e\", token: \"e\" },\n  { label: \"=\", token: \"=\" },\n  { label: \"<\", token: \"<\" },\n  { label: \">\", token: \">\" },\n  { label: \"{ }\", token: \"{\" },\n  { label: \":\", token: \":\" },\n  { label: \"[ ]\", token: \"[\" },\n  { label: \"(\", token: \"(\" },\n  { label: \")\", token: \")\" },\n]\n\nexport const SCIENTIFIC_KEY_SETS = {\n  basic: BASIC_KEYS,\n  functions: FUNCTION_KEYS,\n  trig: TRIG_KEYS,\n  stats: STATS_KEYS,\n  variables: VARIABLE_KEYS,\n}\n\n/** Available scientific keypad categories. */\nexport type ScientificKeypadTab = keyof typeof SCIENTIFIC_KEY_SETS\n\n/** Returns a label-length bucket used to fit keypad text within compact buttons. */\nexport function getScientificKeyLabelSize(label: string) {\n  const length = Array.from(label).length\n  if (length === 1) {\n    return \"single\"\n  }\n  if (length <= 3) {\n    return \"short\"\n  }\n  if (length <= 5) {\n    return \"medium\"\n  }\n  return \"long\"\n}\n\nexport const SCIENTIFIC_EXAMPLES = [\n  { expression: \"(2÷3)+(1÷6)\", label: \"Add fractions\" },\n  { expression: \"sin(30)\", label: \"sin(30°)\" },\n  { expression: \"10ncr3\", label: \"10 choose 3\" },\n  { expression: \"logb(2,32)\", label: \"log₂ 32\" },\n  { expression: \"−3^2\", label: \"Order of operations\" },\n  { expression: \"f(4)\", label: \"Use f(x)\" },\n]\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/modes/scientific-keypad-config.ts"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/modes/scientific-mode.tsx",
      "content": "import { useMemo, useRef, useState } from \"react\"\nimport { Redo2, Undo2 } from \"lucide-react\"\n\nimport { ScientificKeypad } from \"@/components/mirai-calculator/modes/scientific-keypad\"\nimport type {\n  ScientificKeyDefinition,\n  ScientificKeypadTab,\n} from \"@/components/mirai-calculator/modes/scientific-keypad-config\"\nimport { ScientificSidebar } from \"@/components/mirai-calculator/modes/scientific-sidebar\"\nimport type { ScientificHistoryEntry } from \"@/components/mirai-calculator/modes/scientific-sidebar\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { completeExpression, formatExpressionInput } from \"@openmirai/calculator-core/engine\"\nimport type { CalculatorEngine, NumberFormatOptions } from \"@openmirai/calculator-core/engine\"\nimport { cn } from \"@/lib/utils\"\n\nexport interface ScientificModeProps {\n  engine: CalculatorEngine\n  definitions: Array<string>\n  onDefinitionsChange: (definitions: Array<string>) => void\n  onAnsChange: (answer: number) => void\n  formatOptions: NumberFormatOptions\n}\n\n/** Renders the scientific expression workspace and calculator keypad. */\nexport function ScientificMode({\n  engine,\n  definitions,\n  onDefinitionsChange,\n  onAnsChange,\n  formatOptions,\n}: ScientificModeProps) {\n  const [expression, setExpression] = useState(\"\")\n  const [tab, setTab] = useState<ScientificKeypadTab>(\"basic\")\n  const [history, setHistory] = useState<Array<ScientificHistoryEntry>>([])\n  const [fractionView, setFractionView] = useState(false)\n  const [undoStack, setUndoStack] = useState<Array<string>>([])\n  const [redoStack, setRedoStack] = useState<Array<string>>([])\n  const inputRef = useRef<HTMLInputElement>(null)\n  const nextHistoryIdRef = useRef(0)\n  const liveResult = useMemo(() => {\n    if (!expression.trim()) {\n      return null\n    }\n    try {\n      const value = engine.evaluate(completeExpression(expression))\n      const fraction = fractionView && typeof value === \"number\" ? engine.toFraction(value) : null\n      return {\n        value,\n        text: fraction ?? formatExpressionInput(engine.format(value, formatOptions)),\n        note: fraction ? \"exact fraction\" : \"=\",\n        error: false,\n      }\n    } catch (error) {\n      return {\n        value: undefined,\n        text: error instanceof Error ? error.message : \"Invalid expression\",\n        note: \"\",\n        error: true,\n      }\n    }\n  }, [engine, expression, formatOptions, fractionView])\n\n  const updateExpression = (next: string) => {\n    setUndoStack((stack) => [expression, ...stack].slice(0, 50))\n    setRedoStack([])\n    setExpression(next)\n  }\n\n  const updateTypedExpression = (next: string, selectionStart: number, selectionEnd: number) => {\n    const formatted = formatExpressionInput(next)\n    const formattedStart = formatExpressionInput(next.slice(0, selectionStart)).length\n    const formattedEnd = formatExpressionInput(next.slice(0, selectionEnd)).length\n    updateExpression(formatted)\n    requestAnimationFrame(() => {\n      inputRef.current?.setSelectionRange(formattedStart, formattedEnd)\n    })\n  }\n\n  const insert = (token: string) => {\n    const input = inputRef.current\n    const start = input?.selectionStart ?? expression.length\n    const end = input?.selectionEnd ?? start\n    const next = expression.slice(0, start) + token + expression.slice(end)\n    updateExpression(next)\n    requestAnimationFrame(() => {\n      inputRef.current?.focus()\n      inputRef.current?.setSelectionRange(start + token.length, start + token.length)\n    })\n  }\n\n  const removePreviousCharacter = () => {\n    const input = inputRef.current\n    const start = input?.selectionStart ?? expression.length\n    const end = input?.selectionEnd ?? start\n    if (start === 0 && end === 0) {\n      return\n    }\n    const next =\n      start === end\n        ? expression.slice(0, start - 1) + expression.slice(end)\n        : expression.slice(0, start) + expression.slice(end)\n    updateExpression(next)\n    const cursor = start === end ? start - 1 : start\n    requestAnimationFrame(() => inputRef.current?.setSelectionRange(cursor, cursor))\n  }\n\n  const insertAbsoluteValue = () => {\n    const input = inputRef.current\n    const start = input?.selectionStart ?? expression.length\n    const end = input?.selectionEnd ?? start\n    const selected = expression.slice(start, end)\n    const wrapped = `|${selected}|`\n    const next = expression.slice(0, start) + wrapped + expression.slice(end)\n    updateExpression(next)\n    const cursor = selected ? start + wrapped.length : start + 1\n    requestAnimationFrame(() => {\n      inputRef.current?.focus()\n      inputRef.current?.setSelectionRange(cursor, cursor)\n    })\n  }\n\n  const commit = () => {\n    if (!expression.trim()) {\n      return\n    }\n    const completedExpression = completeExpression(expression)\n    if (completedExpression !== expression) {\n      setExpression(completedExpression)\n    }\n    try {\n      const value = engine.evaluate(completedExpression)\n      const result = formatExpressionInput(engine.format(value, formatOptions))\n      if (typeof value === \"number\") {\n        onAnsChange(value)\n      }\n      nextHistoryIdRef.current += 1\n      const id = nextHistoryIdRef.current\n      setHistory((entries) => {\n        const current = entries[0]\n        if (\n          current?.expression === completedExpression &&\n          current.result === result &&\n          !current.error\n        ) {\n          return entries\n        }\n\n        return [\n          {\n            id,\n            expression: completedExpression,\n            result,\n            value: typeof value === \"number\" ? value : undefined,\n          },\n          ...entries,\n        ].slice(0, 40)\n      })\n    } catch (error) {\n      const result = error instanceof Error ? error.message : \"Invalid expression\"\n      nextHistoryIdRef.current += 1\n      const id = nextHistoryIdRef.current\n      setHistory((entries) => {\n        const current = entries[0]\n        if (\n          current?.expression === completedExpression &&\n          current.result === result &&\n          current.error\n        ) {\n          return entries\n        }\n\n        return [\n          {\n            id,\n            expression: completedExpression,\n            result,\n            error: true,\n          },\n          ...entries,\n        ].slice(0, 40)\n      })\n    }\n  }\n\n  const pressKey = (key: ScientificKeyDefinition) => {\n    if (key.action === \"absolute\") {\n      return insertAbsoluteValue()\n    }\n    if (key.action === \"backspace\") {\n      return removePreviousCharacter()\n    }\n    if (key.action === \"clear\") {\n      return updateExpression(\"\")\n    }\n    if (key.action === \"evaluate\") {\n      return commit()\n    }\n    insert(key.token ?? key.label)\n  }\n\n  const undo = () => {\n    const previous = undoStack[0]\n    if (previous === undefined) {\n      return\n    }\n    setRedoStack((stack) => [expression, ...stack])\n    setUndoStack((stack) => stack.slice(1))\n    setExpression(previous)\n  }\n\n  const redo = () => {\n    const next = redoStack[0]\n    if (next === undefined) {\n      return\n    }\n    setUndoStack((stack) => [expression, ...stack])\n    setRedoStack((stack) => stack.slice(1))\n    setExpression(next)\n  }\n\n  return (\n    <div className=\"mirai-scientific-layout grid min-h-0 flex-1 grid-cols-[minmax(0,1.65fr)_minmax(300px,0.85fr)] overflow-hidden @max-[699px]:grid-cols-1 @max-[699px]:overflow-y-auto\">\n      <section className=\"mirai-scientific-primary flex min-h-0 min-w-0 flex-col overflow-hidden border-r @max-[699px]:min-h-112.5 @max-[699px]:border-r-0 @max-[699px]:border-b\">\n        <div className=\"flex min-h-33 shrink-0 flex-col justify-center gap-2 px-6 py-4\">\n          <label className=\"sr-only\" htmlFor=\"mirai-expression\">\n            Calculator expression\n          </label>\n          <Input\n            ref={inputRef}\n            id=\"mirai-expression\"\n            value={expression}\n            onChange={(event) =>\n              updateTypedExpression(\n                event.target.value,\n                event.target.selectionStart ?? event.target.value.length,\n                event.target.selectionEnd ?? event.target.value.length\n              )\n            }\n            onKeyDown={(event) => {\n              if (event.key === \"Enter\") {\n                event.preventDefault()\n                commit()\n              }\n            }}\n            placeholder=\"Type or tap to start\"\n            autoComplete=\"off\"\n            spellCheck={false}\n            className=\"h-auto border-0 bg-transparent px-0 text-right font-[math,var(--font-sans,system-ui),sans-serif]! text-3xl! font-semibold tracking-[-0.02em] shadow-none placeholder:font-sans focus-visible:ring-0 dark:bg-transparent sm:text-4xl!\"\n          />\n          <div\n            aria-live=\"polite\"\n            className={cn(\n              \"flex min-h-9 items-center justify-end gap-2 text-right\",\n              liveResult?.error && \"text-destructive\"\n            )}\n          >\n            {liveResult?.note && (\n              <span className=\"text-xs text-muted-foreground\">{liveResult.note}</span>\n            )}\n            <span\n              className={cn(\n                \"font-[math,var(--font-sans,system-ui),sans-serif]! text-2xl font-semibold tracking-[-0.015em] text-primary sm:text-3xl\",\n                liveResult?.error && \"max-w-full text-lg leading-tight text-destructive sm:text-xl\"\n              )}\n            >\n              {liveResult?.text ?? \" \"}\n            </span>\n          </div>\n          <div className=\"flex flex-wrap justify-end gap-2\">\n            <Button\n              variant={fractionView ? \"secondary\" : \"outline\"}\n              size=\"sm\"\n              onClick={() => setFractionView((visible) => !visible)}\n            >\n              {fractionView ? \"Decimal\" : \"Fraction\"}\n            </Button>\n            <Button variant=\"outline\" size=\"sm\" onClick={() => insert(\"Ans\")}>\n              Use Ans\n            </Button>\n            <Button\n              variant=\"outline\"\n              size=\"icon-sm\"\n              onClick={undo}\n              disabled={undoStack.length === 0}\n              aria-label=\"Undo expression edit\"\n            >\n              <Undo2 />\n            </Button>\n            <Button\n              variant=\"outline\"\n              size=\"icon-sm\"\n              onClick={redo}\n              disabled={redoStack.length === 0}\n              aria-label=\"Redo expression edit\"\n            >\n              <Redo2 />\n            </Button>\n          </div>\n        </div>\n\n        <ScientificKeypad tab={tab} onTabChange={setTab} onPressKey={pressKey} />\n      </section>\n\n      <ScientificSidebar\n        definitions={definitions}\n        onDefinitionsChange={onDefinitionsChange}\n        history={history}\n        onHistoryClear={() => setHistory([])}\n        onExpressionReuse={updateExpression}\n      />\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/modes/scientific-mode.tsx"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/modes/scientific-sidebar.tsx",
      "content": "import { useRef } from \"react\"\nimport { X } from \"lucide-react\"\n\nimport { CalculatorEmptyStateAction } from \"@/components/mirai-calculator/calculator-empty-state-action\"\nimport { SCIENTIFIC_EXAMPLES } from \"@/components/mirai-calculator/modes/scientific-keypad-config\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { ScrollArea } from \"@/components/ui/scroll-area\"\nimport { formatExpressionInput } from \"@openmirai/calculator-core/engine\"\nimport { cn } from \"@/lib/utils\"\n\n/** Completed scientific calculation shown in the local history panel. */\nexport interface ScientificHistoryEntry {\n  id: number\n  expression: string\n  result: string\n  value?: number\n  error?: boolean\n}\n\ninterface ScientificSidebarProps {\n  definitions: Array<string>\n  onDefinitionsChange: (definitions: Array<string>) => void\n  history: Array<ScientificHistoryEntry>\n  onHistoryClear: () => void\n  onExpressionReuse: (expression: string) => void\n}\n\n/** Renders reusable definitions, calculation history, and scientific examples. */\nexport function ScientificSidebar({\n  definitions,\n  onDefinitionsChange,\n  history,\n  onHistoryClear,\n  onExpressionReuse,\n}: ScientificSidebarProps) {\n  const definitionIdsRef = useRef<Array<number>>([])\n  const nextDefinitionIdRef = useRef(0)\n  while (definitionIdsRef.current.length < definitions.length) {\n    nextDefinitionIdRef.current += 1\n    definitionIdsRef.current.push(nextDefinitionIdRef.current)\n  }\n  definitionIdsRef.current.length = definitions.length\n\n  const addDefinition = () => {\n    nextDefinitionIdRef.current += 1\n    definitionIdsRef.current.push(nextDefinitionIdRef.current)\n    onDefinitionsChange([...definitions, \"\"])\n  }\n\n  const removeDefinition = (index: number) => {\n    definitionIdsRef.current.splice(index, 1)\n    onDefinitionsChange(definitions.filter((_, current) => current !== index))\n  }\n\n  const updateDefinition = (index: number, value: string) => {\n    onDefinitionsChange(\n      definitions.map((definition, current) => (current === index ? value : definition))\n    )\n  }\n\n  return (\n    <aside className=\"mirai-scientific-secondary flex min-h-0 flex-col bg-muted/25 @max-[699px]:min-h-75\">\n      <ScrollArea className=\"min-h-0 flex-1\">\n        <section className=\"border-b p-4\">\n          <div className=\"mb-3 flex items-center justify-between\">\n            <h3 className=\"text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n              Definitions\n            </h3>\n            <Button\n              variant=\"ghost\"\n              size=\"sm\"\n              onClick={addDefinition}\n              className=\"h-6 px-1.5 text-xs text-primary\"\n            >\n              Add\n            </Button>\n          </div>\n          <div className=\"space-y-2.5\">\n            {definitions.map((definition, index) => (\n              <div className=\"space-y-0.5\" key={definitionIdsRef.current[index]}>\n                <div className=\"flex items-center gap-1.5\">\n                  <Input\n                    value={definition}\n                    onChange={(event) => updateDefinition(index, event.target.value)}\n                    placeholder=\"a = 5\"\n                    aria-label={`Definition ${index + 1}`}\n                    className=\"h-8 rounded-md bg-background px-2.5 font-sans text-sm font-medium\"\n                  />\n                  <Button\n                    variant=\"ghost\"\n                    size=\"icon-sm\"\n                    onClick={() => removeDefinition(index)}\n                    aria-label={`Remove definition ${index + 1}`}\n                    className=\"text-muted-foreground hover:text-destructive\"\n                  >\n                    <X />\n                  </Button>\n                </div>\n                <p className=\"pl-0.5 text-xs text-muted-foreground\">\n                  Reusable in calculations and graphs\n                </p>\n              </div>\n            ))}\n            {definitions.length === 0 && (\n              <p className=\"py-2 text-xs text-muted-foreground\">\n                <CalculatorEmptyStateAction label=\"Add definition\" onClick={addDefinition} /> a\n                variable or function definition.\n              </p>\n            )}\n          </div>\n        </section>\n\n        <section className=\"min-h-47.5 p-4\">\n          <div className=\"mb-3 flex items-center justify-between\">\n            <h3 className=\"text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n              History\n            </h3>\n            {history.length > 0 && (\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                onClick={onHistoryClear}\n                className=\"h-6 px-1.5 text-xs text-primary\"\n              >\n                Clear\n              </Button>\n            )}\n          </div>\n          <div className=\"space-y-2\">\n            {history.map((entry) => (\n              <Button\n                key={entry.id}\n                variant=\"outline\"\n                onClick={() => onExpressionReuse(entry.expression)}\n                className=\"h-auto w-full flex-col items-stretch gap-0.5 rounded-lg px-3 py-2.5 text-left\"\n              >\n                <span className=\"break-all text-xs text-muted-foreground\">\n                  <span className=\"font-[math,var(--font-sans,system-ui),sans-serif]!\">\n                    {entry.expression}\n                  </span>\n                </span>\n                <span\n                  className={cn(\n                    \"font-[math,var(--font-sans,system-ui),sans-serif]! text-lg font-semibold\",\n                    entry.error && \"text-destructive\"\n                  )}\n                >\n                  {entry.result}\n                </span>\n              </Button>\n            ))}\n            {history.length === 0 && (\n              <p className=\"px-3 py-5 text-center text-sm leading-5 text-muted-foreground\">\n                Your calculations appear here. Tap one to reuse it.\n              </p>\n            )}\n          </div>\n        </section>\n      </ScrollArea>\n\n      <section className=\"shrink-0 border-t p-3\">\n        <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n          Try this\n        </h3>\n        <div className=\"flex flex-wrap gap-1.5\">\n          {SCIENTIFIC_EXAMPLES.map((example) => (\n            <Button\n              key={example.expression}\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={() => onExpressionReuse(formatExpressionInput(example.expression))}\n              className=\"h-6.5 rounded-full px-2.5 text-xs text-muted-foreground hover:border-primary hover:text-primary\"\n            >\n              {example.label}\n            </Button>\n          ))}\n        </div>\n      </section>\n\n      <p className=\"shrink-0 border-t px-4 py-3 text-xs leading-5 text-muted-foreground\">\n        Type with your keyboard too—<strong>sin</strong>, <strong>sqrt</strong>, <strong>^</strong>,\n        Enter to evaluate.\n      </p>\n    </aside>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/modes/scientific-sidebar.tsx"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/modes/statistics-config.ts",
      "content": "import type { RegressionModel } from \"@openmirai/calculator-core/statistics\"\n\n/** Chart variants available in the statistics workspace. */\nexport type StatisticsChartType = \"scatter\" | \"histogram\" | \"box\" | \"dot\" | \"residual\"\n\n/** User-facing statistics chart options. */\nexport const STATISTICS_CHART_OPTIONS: Array<{\n  value: StatisticsChartType\n  label: string\n}> = [\n  { value: \"scatter\", label: \"Scatter\" },\n  { value: \"histogram\", label: \"Histogram\" },\n  { value: \"box\", label: \"Box plot\" },\n  { value: \"dot\", label: \"Dot plot\" },\n  { value: \"residual\", label: \"Residuals\" },\n]\n\n/** User-facing regression model options. */\nexport const STATISTICS_REGRESSION_OPTIONS: Array<{\n  value: RegressionModel\n  label: string\n}> = [\n  { value: \"linear\", label: \"Linear\" },\n  { value: \"quadratic\", label: \"Quadratic\" },\n  { value: \"cubic\", label: \"Cubic\" },\n  { value: \"exponential\", label: \"Exponential\" },\n  { value: \"logarithmic\", label: \"Logarithmic\" },\n  { value: \"power\", label: \"Power\" },\n]\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/modes/statistics-config.ts"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/modes/statistics-mode.tsx",
      "content": "import { useCallback, useMemo, useRef, useState } from \"react\"\nimport { BarChart3, LineChart } from \"lucide-react\"\n\nimport { Badge } from \"@/components/ui/badge\"\nimport { Button } from \"@/components/ui/button\"\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\"\nimport { ScrollArea } from \"@/components/ui/scroll-area\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { Textarea } from \"@/components/ui/textarea\"\nimport { useStatisticsChart } from \"@/components/mirai-calculator/modes/use-statistics-chart\"\nimport {\n  STATISTICS_CHART_OPTIONS,\n  STATISTICS_REGRESSION_OPTIONS,\n} from \"@/components/mirai-calculator/modes/statistics-config\"\nimport type { StatisticsChartType } from \"@/components/mirai-calculator/modes/statistics-config\"\nimport {\n  calculateStatistics,\n  correlation,\n  covariance,\n  fitRegression,\n} from \"@openmirai/calculator-core/statistics\"\nimport type { RegressionModel } from \"@openmirai/calculator-core/statistics\"\nimport {\n  pairNumberSeries,\n  parseNumberList,\n  serializeNumberList,\n} from \"@openmirai/calculator-core/statistics-data\"\nimport type { StatisticsInitialData } from \"@openmirai/calculator-core/statistics-data\"\nimport { cn } from \"@/lib/utils\"\n\nexport interface StatisticsModeProps {\n  formatNumber: (value: number) => string\n  colorScheme?: \"light\" | \"dark\"\n  defaultData?: StatisticsInitialData\n}\n\n/** Renders descriptive statistics, regression, and distribution tools. */\nexport function StatisticsMode({\n  formatNumber: formatValue,\n  colorScheme = \"light\",\n  defaultData,\n}: StatisticsModeProps) {\n  const [xSource, setXSource] = useState(() => serializeNumberList(defaultData?.xValues))\n  const [ySource, setYSource] = useState(() => serializeNumberList(defaultData?.yValues))\n  const [chart, setChart] = useState<StatisticsChartType>(\"scatter\")\n  const [model, setModel] = useState<RegressionModel>(\"linear\")\n  const canvasRef = useRef<HTMLCanvasElement>(null)\n  const canvasHostRef = useRef<HTMLDivElement>(null)\n  const formatNumber = useCallback(\n    (value: number) => formatValue(Number.isFinite(value) ? Number(value.toPrecision(8)) : value),\n    [formatValue]\n  )\n\n  const xValues = useMemo(() => parseNumberList(xSource), [xSource])\n  const yValues = useMemo(() => parseNumberList(ySource), [ySource])\n  const statistics = useMemo(\n    () => (xValues.length ? calculateStatistics(xValues) : null),\n    [xValues]\n  )\n  const regression = useMemo(\n    () => fitRegression(xValues, yValues, model),\n    [model, xValues, yValues]\n  )\n  const pairedValues = useMemo(() => pairNumberSeries(xValues, yValues), [xValues, yValues])\n  const { correlationValue, covarianceValue } = useMemo(() => {\n    try {\n      return {\n        correlationValue: correlation(pairedValues.x, pairedValues.y),\n        covarianceValue: covariance(pairedValues.x, pairedValues.y),\n      }\n    } catch {\n      return { correlationValue: Number.NaN, covarianceValue: Number.NaN }\n    }\n  }, [pairedValues])\n  const residualRows = useMemo(() => {\n    const occurrences = new Map<string, number>()\n    return regression.residuals.slice(0, 12).map((residual, index) => {\n      const x = pairedValues.x[index]\n      const y = pairedValues.y[index]\n      const signature = `${x}:${y}:${residual}`\n      const occurrence = (occurrences.get(signature) ?? 0) + 1\n      occurrences.set(signature, occurrence)\n      return { key: `${signature}:${occurrence}`, residual, x }\n    })\n  }, [pairedValues, regression.residuals])\n\n  useStatisticsChart({\n    canvasRef,\n    canvasHostRef,\n    chart,\n    colorScheme,\n    formatNumber,\n    pairedValues,\n    regression,\n    statistics,\n    xValues,\n  })\n\n  const statRows = statistics\n    ? [\n        [\"n\", statistics.n],\n        [\"Sum\", statistics.sum],\n        [\"Mean\", statistics.mean],\n        [\"Median\", statistics.median],\n        [\"Mode\", statistics.mode],\n        [\"Min\", statistics.min],\n        [\"Q1\", statistics.q1],\n        [\"Q3\", statistics.q3],\n        [\"Max\", statistics.max],\n        [\"Range\", statistics.range],\n        [\"IQR\", statistics.iqr],\n        [\"σ (population)\", statistics.populationStandardDeviation],\n        [\"s (sample)\", statistics.sampleStandardDeviation],\n        [\"σ²\", statistics.populationVariance],\n        [\"s²\", statistics.sampleVariance],\n      ]\n    : []\n\n  return (\n    <div className=\"mirai-statistics-layout grid min-h-0 flex-1 grid-cols-[340px_minmax(0,1fr)] overflow-hidden @max-[699px]:grid-cols-1 @max-[699px]:overflow-y-auto\">\n      <aside className=\"mirai-statistics-sidebar min-h-0 border-r bg-muted/25 @max-[699px]:min-h-105 @max-[699px]:border-r-0 @max-[699px]:border-b\">\n        <ScrollArea className=\"h-full\">\n          <div className=\"space-y-5 p-5\">\n            <div>\n              <h2 className=\"text-sm font-semibold\">Data lists</h2>\n              <p className=\"text-xs text-muted-foreground\">\n                Separate values with commas, spaces, or new lines.\n              </p>\n            </div>\n            <label\n              htmlFor=\"mirai-statistics-x-values\"\n              className=\"block space-y-2 text-xs font-medium uppercase tracking-wide text-muted-foreground\"\n            >\n              X values\n              <Textarea\n                id=\"mirai-statistics-x-values\"\n                value={xSource}\n                onChange={(event) => setXSource(event.target.value)}\n                aria-label=\"X values\"\n                className=\"min-h-24 resize-y bg-background font-mono text-sm text-foreground normal-case tracking-normal placeholder:text-muted-foreground\"\n              />\n            </label>\n            <label\n              htmlFor=\"mirai-statistics-y-values\"\n              className=\"block space-y-2 text-xs font-medium uppercase tracking-wide text-muted-foreground\"\n            >\n              Y values\n              <Textarea\n                id=\"mirai-statistics-y-values\"\n                value={ySource}\n                onChange={(event) => setYSource(event.target.value)}\n                aria-label=\"Y values\"\n                className=\"min-h-24 resize-y bg-background font-mono text-sm text-foreground normal-case tracking-normal placeholder:text-muted-foreground\"\n              />\n            </label>\n            <div>\n              <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n                Chart\n              </h3>\n              <div className=\"flex flex-wrap gap-2\">\n                {STATISTICS_CHART_OPTIONS.map((item) => (\n                  <Button\n                    key={item.value}\n                    size=\"sm\"\n                    variant={chart === item.value ? \"default\" : \"outline\"}\n                    onClick={() => setChart(item.value)}\n                  >\n                    {item.label}\n                  </Button>\n                ))}\n              </div>\n            </div>\n            <div>\n              <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n                Regression model\n              </h3>\n              <div className=\"flex flex-wrap gap-2\">\n                {STATISTICS_REGRESSION_OPTIONS.map((item) => (\n                  <Button\n                    key={item.value}\n                    size=\"sm\"\n                    variant={model === item.value ? \"default\" : \"outline\"}\n                    onClick={() => setModel(item.value)}\n                  >\n                    {item.label}\n                  </Button>\n                ))}\n              </div>\n            </div>\n\n            <Card>\n              <CardHeader className=\"pb-2\">\n                <CardTitle className=\"flex items-center justify-between text-sm\">\n                  Model fit\n                  <Badge variant={regression.ok ? \"default\" : \"secondary\"}>\n                    {regression.label}\n                  </Badge>\n                </CardTitle>\n              </CardHeader>\n              <CardContent className=\"space-y-2\">\n                {regression.params.map((parameter) => (\n                  <div key={parameter.label} className=\"flex justify-between text-sm\">\n                    <span className=\"text-muted-foreground\">{parameter.label}</span>\n                    <strong className=\"font-mono\">{formatNumber(parameter.value)}</strong>\n                  </div>\n                ))}\n                <Separator />\n                <div className=\"flex justify-between text-sm\">\n                  <span className=\"text-muted-foreground\">R²</span>\n                  <strong className=\"font-mono text-primary\">\n                    {regression.ok ? formatNumber(regression.r2) : \"—\"}\n                  </strong>\n                </div>\n                <div className=\"flex justify-between text-sm\">\n                  <span className=\"text-muted-foreground\">r</span>\n                  <strong className=\"font-mono\">{formatNumber(correlationValue)}</strong>\n                </div>\n                <div className=\"flex justify-between text-sm\">\n                  <span className=\"text-muted-foreground\">covariance</span>\n                  <strong className=\"font-mono\">{formatNumber(covarianceValue)}</strong>\n                </div>\n                <p\n                  className={cn(\n                    \"pt-1 text-xs text-muted-foreground\",\n                    !regression.ok && \"text-destructive\"\n                  )}\n                >\n                  {regression.message}\n                </p>\n              </CardContent>\n            </Card>\n\n            {regression.ok && (\n              <div>\n                <h3 className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n                  Residuals\n                </h3>\n                <div className=\"space-y-1\">\n                  {residualRows.map((row) => (\n                    <div key={row.key} className=\"flex justify-between text-xs\">\n                      <span className=\"text-muted-foreground\">x = {formatNumber(row.x)}</span>\n                      <span className=\"font-mono\">{formatNumber(row.residual)}</span>\n                    </div>\n                  ))}\n                </div>\n              </div>\n            )}\n          </div>\n        </ScrollArea>\n      </aside>\n\n      <section className=\"mirai-statistics-results grid min-h-0 grid-rows-[minmax(300px,1fr)_auto] @max-[699px]:min-h-max @max-[699px]:grid-rows-[minmax(260px,1fr)_auto]\">\n        <div\n          ref={canvasHostRef}\n          className=\"mirai-statistics-chart relative min-h-75 @max-[699px]:min-h-65\"\n        >\n          <canvas\n            ref={canvasRef}\n            className=\"absolute inset-0 size-full\"\n            aria-label={`${chart} statistics chart`}\n          />\n          <Badge className=\"absolute top-3 right-3 gap-1\" variant=\"secondary\">\n            {chart === \"histogram\" || chart === \"box\" || chart === \"dot\" ? (\n              <BarChart3 className=\"size-3\" />\n            ) : (\n              <LineChart className=\"size-3\" />\n            )}\n            {STATISTICS_CHART_OPTIONS.find((item) => item.value === chart)?.label}\n          </Badge>\n        </div>\n\n        <div className=\"mirai-statistics-summary max-h-77.5 overflow-auto overscroll-contain border-t bg-muted/25 p-4 @max-[699px]:max-h-none @max-[699px]:overflow-visible\">\n          <div className=\"mb-3\">\n            <div>\n              <h3 className=\"text-sm font-semibold\">One-variable statistics · x list</h3>\n              <p className=\"text-xs text-muted-foreground\">{xValues.length} valid values parsed</p>\n            </div>\n          </div>\n          <div className=\"mirai-statistics-grid grid grid-cols-[repeat(auto-fit,minmax(min(165px,100%),1fr))] gap-2 @max-[699px]:grid-cols-2\">\n            {statRows.map(([label, value]) => (\n              <Card key={String(label)} className=\"min-w-0 gap-1 overflow-hidden py-3\">\n                <CardContent className=\"min-w-0 px-3\">\n                  <div className=\"min-h-4 wrap-break-word text-xs leading-4 text-muted-foreground\">\n                    {label}\n                  </div>\n                  <div\n                    className=\"max-w-full overflow-hidden font-mono text-sm font-semibold text-ellipsis whitespace-nowrap\"\n                    title={value === null ? \"none\" : formatNumber(Number(value))}\n                  >\n                    {value === null ? \"none\" : formatNumber(Number(value))}\n                  </div>\n                </CardContent>\n              </Card>\n            ))}\n          </div>\n        </div>\n      </section>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/modes/statistics-mode.tsx"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/modes/tools-mode.tsx",
      "content": "import { useMemo, useState } from \"react\"\nimport { CirclePercent, DraftingCompass, MoveDiagonal2, Ratio } from \"lucide-react\"\n\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  calculateCoordinates,\n  calculatePercent,\n  calculateRatio,\n  calculateShapes,\n  parseToolNumber,\n  serializeToolValue,\n} from \"@openmirai/calculator-core/tools\"\nimport type { ToolsInitialData } from \"@openmirai/calculator-core/tools\"\n\nexport interface ToolsModeProps {\n  formatNumber: (value: number) => string\n  defaultData?: ToolsInitialData\n}\n\nfunction ResultRow({ label, value }: { label: string; value: string }) {\n  return (\n    <div className=\"mirai-tools-result flex items-center justify-between gap-4 text-sm @max-[699px]:flex-col @max-[699px]:items-start @max-[699px]:gap-0.5\">\n      <span className=\"text-muted-foreground\">{label}</span>\n      <strong className=\"min-w-0 max-w-full text-right font-mono wrap-break-word @max-[699px]:text-left\">\n        {value}\n      </strong>\n    </div>\n  )\n}\n\nfunction NumberField({\n  label,\n  value,\n  onChange,\n}: {\n  label: string\n  value: string\n  onChange: (value: string) => void\n}) {\n  return (\n    <label className=\"min-w-0 space-y-1.5 text-xs text-muted-foreground\">\n      {label}\n      <Input\n        value={value}\n        onChange={(event) => onChange(event.target.value)}\n        inputMode=\"decimal\"\n        className=\"bg-background font-mono text-sm text-foreground\"\n        aria-label={label}\n      />\n    </label>\n  )\n}\n\n/** Renders focused percent, ratio, coordinate, and geometry utilities. */\nexport function ToolsMode({ formatNumber: formatValue, defaultData }: ToolsModeProps) {\n  const [percent, setPercent] = useState(() => serializeToolValue(defaultData?.percent))\n  const [percentOf, setPercentOf] = useState(() => serializeToolValue(defaultData?.percentOf))\n  const [ratioLeft, setRatioLeft] = useState(() => serializeToolValue(defaultData?.ratioLeft))\n  const [ratioRight, setRatioRight] = useState(() => serializeToolValue(defaultData?.ratioRight))\n  const [ratioScale, setRatioScale] = useState(() => serializeToolValue(defaultData?.ratioScale))\n  const [x1, setX1] = useState(() => serializeToolValue(defaultData?.x1))\n  const [y1, setY1] = useState(() => serializeToolValue(defaultData?.y1))\n  const [x2, setX2] = useState(() => serializeToolValue(defaultData?.x2))\n  const [y2, setY2] = useState(() => serializeToolValue(defaultData?.y2))\n  const [radius, setRadius] = useState(() => serializeToolValue(defaultData?.radius))\n  const [base, setBase] = useState(() => serializeToolValue(defaultData?.base))\n  const [height, setHeight] = useState(() => serializeToolValue(defaultData?.height))\n  const [depth, setDepth] = useState(() => serializeToolValue(defaultData?.depth))\n  const formatNumber = (value: number) =>\n    formatValue(Number.isFinite(value) ? Number(value.toPrecision(8)) : value)\n\n  const percentResults = useMemo(\n    () => calculatePercent(parseToolNumber(percent), parseToolNumber(percentOf)),\n    [percent, percentOf]\n  )\n  const ratioResults = useMemo(\n    () =>\n      calculateRatio(\n        parseToolNumber(ratioLeft),\n        parseToolNumber(ratioRight),\n        parseToolNumber(ratioScale, 1)\n      ),\n    [ratioLeft, ratioRight, ratioScale]\n  )\n  const coordinateResults = useMemo(\n    () =>\n      calculateCoordinates(\n        parseToolNumber(x1),\n        parseToolNumber(y1),\n        parseToolNumber(x2),\n        parseToolNumber(y2)\n      ),\n    [x1, x2, y1, y2]\n  )\n  let lineThroughPoints: string\n  switch (coordinateResults.slope) {\n    case null:\n      lineThroughPoints = `x = ${formatNumber(parseToolNumber(x1))}`\n      break\n    default: {\n      const intercept = coordinateResults.intercept\n      lineThroughPoints =\n        intercept === null\n          ? \"undefined\"\n          : `y = ${formatNumber(coordinateResults.slope)}x ${intercept < 0 ? \"−\" : \"+\"} ${formatNumber(Math.abs(intercept))}`\n    }\n  }\n  const shapeResults = useMemo(\n    () =>\n      calculateShapes(\n        parseToolNumber(radius),\n        parseToolNumber(base),\n        parseToolNumber(height),\n        parseToolNumber(depth)\n      ),\n    [base, depth, height, radius]\n  )\n\n  return (\n    <div className=\"min-h-0 flex-1 overflow-auto bg-muted/25 p-4 sm:p-6\">\n      <div className=\"mirai-tools-grid grid grid-cols-[repeat(auto-fit,minmax(min(280px,100%),1fr))] gap-4\">\n        <Card>\n          <CardHeader>\n            <CardTitle className=\"flex items-center gap-2 text-base\">\n              <CirclePercent className=\"size-4 text-primary\" />\n              Percent\n            </CardTitle>\n            <p className=\"text-xs text-muted-foreground\">\n              Numbers in, numbers out—no worked steps.\n            </p>\n          </CardHeader>\n          <CardContent className=\"space-y-4\">\n            <div className=\"mirai-tools-fields mirai-tools-fields-two grid grid-cols-2 gap-3 @max-[699px]:grid-cols-1\">\n              <NumberField label=\"Percent\" value={percent} onChange={setPercent} />\n              <NumberField label=\"Of value\" value={percentOf} onChange={setPercentOf} />\n            </div>\n            <div className=\"space-y-2 rounded-lg bg-muted p-3\">\n              <ResultRow label=\"Percent of value\" value={formatNumber(percentResults.portion)} />\n              <ResultRow label=\"Increased by\" value={formatNumber(percentResults.increased)} />\n              <ResultRow label=\"Decreased by\" value={formatNumber(percentResults.decreased)} />\n              <ResultRow\n                label=\"Original before increase\"\n                value={formatNumber(percentResults.originalBeforeIncrease)}\n              />\n            </div>\n          </CardContent>\n        </Card>\n\n        <Card>\n          <CardHeader>\n            <CardTitle className=\"flex items-center gap-2 text-base\">\n              <Ratio className=\"size-4 text-primary\" />\n              Ratio and proportion\n            </CardTitle>\n            <p className=\"text-xs text-muted-foreground\">Simplify, scale, and check equivalence.</p>\n          </CardHeader>\n          <CardContent className=\"space-y-4\">\n            <div className=\"mirai-tools-fields mirai-tools-fields-three grid grid-cols-3 gap-3 @max-[699px]:grid-cols-1\">\n              <NumberField label=\"a\" value={ratioLeft} onChange={setRatioLeft} />\n              <NumberField label=\"b\" value={ratioRight} onChange={setRatioRight} />\n              <NumberField label=\"Scale\" value={ratioScale} onChange={setRatioScale} />\n            </div>\n            <div className=\"space-y-2 rounded-lg bg-muted p-3\">\n              <ResultRow\n                label=\"Simplified\"\n                value={`${formatNumber(ratioResults.simplifiedLeft)} : ${formatNumber(ratioResults.simplifiedRight)}`}\n              />\n              <ResultRow\n                label=\"Scaled\"\n                value={`${formatNumber(ratioResults.scaledLeft)} : ${formatNumber(ratioResults.scaledRight)}`}\n              />\n              <ResultRow label=\"As decimal\" value={formatNumber(ratioResults.decimal)} />\n              <ResultRow\n                label=\"gcd · lcm\"\n                value={`${formatNumber(ratioResults.gcd)} · ${formatNumber(ratioResults.lcm)}`}\n              />\n            </div>\n          </CardContent>\n        </Card>\n\n        <Card>\n          <CardHeader>\n            <CardTitle className=\"flex items-center gap-2 text-base\">\n              <MoveDiagonal2 className=\"size-4 text-primary\" />\n              Coordinate geometry\n            </CardTitle>\n            <p className=\"text-xs text-muted-foreground\">Two points in, distance and slope out.</p>\n          </CardHeader>\n          <CardContent className=\"space-y-4\">\n            <div className=\"mirai-tools-fields mirai-tools-fields-four grid grid-cols-4 gap-2 @max-[699px]:grid-cols-1\">\n              <NumberField label=\"x₁\" value={x1} onChange={setX1} />\n              <NumberField label=\"y₁\" value={y1} onChange={setY1} />\n              <NumberField label=\"x₂\" value={x2} onChange={setX2} />\n              <NumberField label=\"y₂\" value={y2} onChange={setY2} />\n            </div>\n            <div className=\"space-y-2 rounded-lg bg-muted p-3\">\n              <ResultRow label=\"Distance\" value={formatNumber(coordinateResults.distance)} />\n              <ResultRow\n                label=\"Midpoint\"\n                value={`(${formatNumber(coordinateResults.midpoint[0])}, ${formatNumber(coordinateResults.midpoint[1])})`}\n              />\n              <ResultRow\n                label=\"Slope\"\n                value={\n                  coordinateResults.slope === null\n                    ? \"undefined\"\n                    : formatNumber(coordinateResults.slope)\n                }\n              />\n              <ResultRow label=\"Line through points\" value={lineThroughPoints} />\n            </div>\n          </CardContent>\n        </Card>\n\n        <Card className=\"mirai-tools-wide col-span-full\">\n          <CardHeader>\n            <CardTitle className=\"flex items-center gap-2 text-base\">\n              <DraftingCompass className=\"size-4 text-primary\" />\n              Shape measures\n            </CardTitle>\n            <p className=\"text-xs text-muted-foreground\">\n              Enter dimensions and the results update as you type.\n            </p>\n          </CardHeader>\n          <CardContent className=\"space-y-4\">\n            <div className=\"mirai-tools-fields mirai-tools-fields-four grid grid-cols-4 gap-3 @max-[699px]:grid-cols-1\">\n              <NumberField label=\"Radius r\" value={radius} onChange={setRadius} />\n              <NumberField label=\"Base b\" value={base} onChange={setBase} />\n              <NumberField label=\"Height h\" value={height} onChange={setHeight} />\n              <NumberField label=\"Depth d\" value={depth} onChange={setDepth} />\n            </div>\n            <div className=\"mirai-tools-shape-results grid grid-cols-4 gap-3 @max-[699px]:grid-cols-1\">\n              {[\n                [\"Circle area\", shapeResults.circleArea],\n                [\"Circumference\", shapeResults.circumference],\n                [\"Triangle area\", shapeResults.triangleArea],\n                [\"Prism volume\", shapeResults.prismVolume],\n              ].map(([label, value]) => (\n                <div key={String(label)} className=\"rounded-lg bg-muted p-3\">\n                  <div className=\"text-xs text-muted-foreground\">{label}</div>\n                  <div className=\"font-mono text-base font-semibold\">\n                    {formatValue(\n                      Number.isFinite(Number(value))\n                        ? Number(Number(value).toPrecision(6))\n                        : Number(value)\n                    )}\n                  </div>\n                </div>\n              ))}\n            </div>\n          </CardContent>\n        </Card>\n\n        <Card className=\"mirai-tools-wide col-span-full border-primary/20 bg-primary/5\">\n          <CardContent className=\"py-4\">\n            <p className=\"text-sm font-medium text-primary\">Practice tip</p>\n            <p className=\"mt-1 text-sm text-muted-foreground\">\n              These helpers perform arithmetic only. They do not factor, solve, or explain a\n              question, keeping the workflow appropriate for timed practice.\n            </p>\n          </CardContent>\n        </Card>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/modes/tools-mode.tsx"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/modes/use-statistics-chart.ts",
      "content": "import { useEffect } from \"react\"\n\nimport { readCanvasColor, resolveCanvasColor } from \"@/components/mirai-calculator/canvas-theme\"\nimport { CALCULATOR_CHART_TOKENS } from \"@/components/mirai-calculator/calculator-ui-config\"\nimport type { StatisticsChartType } from \"@/components/mirai-calculator/modes/statistics-config\"\nimport type { DescriptiveStatistics, RegressionResult } from \"@openmirai/calculator-core/statistics\"\nimport {\n  buildHistogram,\n  calculateNumberExtent,\n  countNumberFrequencies,\n  sampleRegression,\n} from \"@openmirai/calculator-core/statistics-data\"\nimport type { PairedNumberSeries } from \"@openmirai/calculator-core/statistics-data\"\n\ninterface UseStatisticsChartOptions {\n  canvasRef: React.RefObject<HTMLCanvasElement | null>\n  canvasHostRef: React.RefObject<HTMLDivElement | null>\n  chart: StatisticsChartType\n  colorScheme: \"light\" | \"dark\"\n  formatNumber: (value: number) => string\n  pairedValues: PairedNumberSeries\n  regression: RegressionResult\n  statistics: DescriptiveStatistics | null\n  xValues: Array<number>\n}\n\n/** Draws the selected statistics visualization and redraws it when its host resizes. */\nexport function useStatisticsChart({\n  canvasRef,\n  canvasHostRef,\n  chart,\n  colorScheme,\n  formatNumber,\n  pairedValues,\n  regression,\n  statistics,\n  xValues,\n}: UseStatisticsChartOptions): void {\n  useEffect(() => {\n    const canvas = canvasRef.current\n    const host = canvasHostRef.current\n    if (!canvas || !host) {\n      return\n    }\n\n    const draw = () => {\n      const bounds = host.getBoundingClientRect()\n      const width = Math.max(1, Math.floor(bounds.width))\n      const height = Math.max(1, Math.floor(bounds.height))\n      const ratio = Math.min(window.devicePixelRatio || 1, 2)\n      canvas.width = width * ratio\n      canvas.height = height * ratio\n      canvas.style.width = `${width}px`\n      canvas.style.height = `${height}px`\n      const context = canvas.getContext(\"2d\")\n      if (!context) {\n        return\n      }\n      context.setTransform(ratio, 0, 0, ratio, 0, 0)\n\n      const background = readCanvasColor(canvas, \"--background\")\n      const border = readCanvasColor(canvas, \"--border\")\n      const foreground = readCanvasColor(canvas, \"--foreground\")\n      const muted = readCanvasColor(canvas, \"--muted-foreground\")\n      const primary = resolveCanvasColor(canvas, CALCULATOR_CHART_TOKENS[0])\n      const secondary = resolveCanvasColor(canvas, CALCULATOR_CHART_TOKENS[1])\n      const padding = { top: 28, right: 28, bottom: 38, left: 48 }\n      const plotWidth = width - padding.left - padding.right\n      const plotHeight = height - padding.top - padding.bottom\n\n      context.clearRect(0, 0, width, height)\n      context.fillStyle = background\n      context.fillRect(0, 0, width, height)\n      context.font = \"11px system-ui, -apple-system, sans-serif\"\n      context.fillStyle = muted\n      context.strokeStyle = border\n      context.lineWidth = 1\n\n      const drawAxes = (xRange: [number, number], yRange: [number, number]) => {\n        const mapX = (value: number) =>\n          padding.left + ((value - xRange[0]) / (xRange[1] - xRange[0])) * plotWidth\n        const mapY = (value: number) =>\n          padding.top + (1 - (value - yRange[0]) / (yRange[1] - yRange[0])) * plotHeight\n\n        for (let index = 0; index <= 5; index += 1) {\n          const x = padding.left + (index / 5) * plotWidth\n          const y = padding.top + (index / 5) * plotHeight\n          context.strokeStyle = border\n          context.beginPath()\n          context.moveTo(x, padding.top)\n          context.lineTo(x, padding.top + plotHeight)\n          context.stroke()\n          context.beginPath()\n          context.moveTo(padding.left, y)\n          context.lineTo(padding.left + plotWidth, y)\n          context.stroke()\n          context.fillStyle = muted\n          const xValue = xRange[0] + (index / 5) * (xRange[1] - xRange[0])\n          const yValue = yRange[1] - (index / 5) * (yRange[1] - yRange[0])\n          context.textAlign = \"center\"\n          context.fillText(formatNumber(xValue), x, height - 14)\n          context.textAlign = \"right\"\n          context.fillText(formatNumber(yValue), padding.left - 8, y + 4)\n        }\n        return { mapX, mapY }\n      }\n\n      if (chart === \"histogram\" && xValues.length > 0) {\n        const bins = buildHistogram(xValues)\n        const firstBin = bins[0]\n        const lastBin = bins.at(-1)\n        if (!firstBin || !lastBin) {\n          return\n        }\n        const minimum = firstBin.minimum\n        const maximum = lastBin.maximum\n        const { mapX, mapY } = drawAxes(\n          [minimum, maximum],\n          [0, Math.max(...bins.map((bin) => bin.count), 1)]\n        )\n        context.fillStyle = primary\n        bins.forEach((bin) => {\n          const x0 = mapX(bin.minimum)\n          const x1 = mapX(bin.maximum)\n          const y = mapY(bin.count)\n          context.fillRect(x0 + 1, y, Math.max(1, x1 - x0 - 2), mapY(0) - y)\n        })\n      } else if (chart === \"box\" && statistics) {\n        const [minimum, maximum] = calculateNumberExtent(xValues)\n        const mapX = (value: number) =>\n          padding.left + ((value - minimum) / (maximum - minimum)) * plotWidth\n        const centerY = padding.top + plotHeight / 2\n        context.strokeStyle = foreground\n        context.lineWidth = 2\n        context.beginPath()\n        context.moveTo(mapX(statistics.min), centerY)\n        context.lineTo(mapX(statistics.q1), centerY)\n        context.moveTo(mapX(statistics.q3), centerY)\n        context.lineTo(mapX(statistics.max), centerY)\n        context.stroke()\n        context.fillStyle = `${primary}33`\n        context.fillRect(\n          mapX(statistics.q1),\n          centerY - 42,\n          mapX(statistics.q3) - mapX(statistics.q1),\n          84\n        )\n        context.strokeStyle = primary\n        context.strokeRect(\n          mapX(statistics.q1),\n          centerY - 42,\n          mapX(statistics.q3) - mapX(statistics.q1),\n          84\n        )\n        for (const value of [statistics.min, statistics.median, statistics.max]) {\n          context.beginPath()\n          context.moveTo(mapX(value), centerY - 48)\n          context.lineTo(mapX(value), centerY + 48)\n          context.stroke()\n          context.fillStyle = muted\n          context.fillText(formatNumber(value), mapX(value) - 10, centerY + 68)\n        }\n      } else if (chart === \"dot\" && xValues.length > 0) {\n        const [minimum, maximum] = calculateNumberExtent(xValues)\n        const frequencies = countNumberFrequencies(xValues)\n        const maxFrequency = Math.max(...frequencies.map(({ count }) => count))\n        const { mapX, mapY } = drawAxes([minimum, maximum], [0, maxFrequency + 1])\n        context.fillStyle = primary\n        for (const { value, count } of frequencies) {\n          for (let index = 1; index <= count; index += 1) {\n            context.beginPath()\n            context.arc(mapX(value), mapY(index), 5, 0, Math.PI * 2)\n            context.fill()\n          }\n        }\n      } else {\n        const yPlotValues =\n          chart === \"residual\" && regression.ok ? regression.residuals : pairedValues.y\n        const [xmin, xmax] = calculateNumberExtent(pairedValues.x)\n        const [ymin, ymax] = calculateNumberExtent(yPlotValues)\n        const { mapX, mapY } = drawAxes([xmin, xmax], [ymin, ymax])\n        if (chart === \"residual\") {\n          context.strokeStyle = muted\n          context.setLineDash([5, 4])\n          context.beginPath()\n          context.moveTo(mapX(xmin), mapY(0))\n          context.lineTo(mapX(xmax), mapY(0))\n          context.stroke()\n          context.setLineDash([])\n        } else if (regression.ok) {\n          context.strokeStyle = secondary\n          context.lineWidth = 2\n          context.beginPath()\n          sampleRegression(regression.predict, xmin, xmax).forEach(({ x, y }, index) => {\n            if (index === 0) {\n              context.moveTo(mapX(x), mapY(y))\n            } else {\n              context.lineTo(mapX(x), mapY(y))\n            }\n          })\n          context.stroke()\n        }\n\n        context.fillStyle = primary\n        pairedValues.x.forEach((x, index) => {\n          const y =\n            chart === \"residual\" && regression.ok\n              ? regression.residuals[index]\n              : pairedValues.y[index]\n          if (!Number.isFinite(y)) {\n            return\n          }\n          context.beginPath()\n          context.arc(mapX(x), mapY(y), 4.5, 0, Math.PI * 2)\n          context.fill()\n        })\n      }\n    }\n\n    draw()\n    const observer = new ResizeObserver(draw)\n    observer.observe(host)\n    return () => observer.disconnect()\n  }, [\n    canvasHostRef,\n    canvasRef,\n    chart,\n    colorScheme,\n    formatNumber,\n    pairedValues,\n    regression,\n    statistics,\n    xValues,\n  ])\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/modes/use-statistics-chart.ts"
    },
    {
      "path": "../../packages/calculator-registry/src/components/mirai-calculator/use-system-dark-mode.ts",
      "content": "import { useSyncExternalStore } from \"react\"\n\nimport type { CalculatorTheme } from \"@openmirai/calculator-core/configuration\"\n\n/** Subscribes to changes in the browser's preferred color scheme. */\nfunction subscribeToSystemTheme(onStoreChange: () => void): () => void {\n  const query = window.matchMedia(\"(prefers-color-scheme: dark)\")\n  query.addEventListener(\"change\", onStoreChange)\n  return () => query.removeEventListener(\"change\", onStoreChange)\n}\n\n/** Reads whether the browser currently prefers a dark color scheme. */\nfunction getSystemThemeSnapshot(): boolean {\n  return window.matchMedia(\"(prefers-color-scheme: dark)\").matches\n}\n\n/** Supplies the deterministic light-mode server snapshot. */\nfunction getServerSystemThemeSnapshot(): boolean {\n  return false\n}\n\n/** Returns whether the host system currently prefers a dark color scheme. */\nexport function useSystemDarkMode(): boolean {\n  return useSyncExternalStore(\n    subscribeToSystemTheme,\n    getSystemThemeSnapshot,\n    getServerSystemThemeSnapshot\n  )\n}\n\n/** Resolves the configured calculator theme against the current system preference. */\nexport function resolveCalculatorTheme(\n  theme: CalculatorTheme,\n  systemDark: boolean\n): \"light\" | \"dark\" {\n  switch (theme) {\n    case \"system\":\n      return systemDark ? \"dark\" : \"light\"\n    case \"light\":\n    case \"dark\":\n      return theme\n  }\n}\n",
      "type": "registry:component",
      "target": "@components/mirai-calculator/use-system-dark-mode.ts"
    }
  ],
  "type": "registry:block"
}