@timbal-ai/timbal-react 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/app.cjs +138 -73
  3. package/dist/app.d.cts +3 -3
  4. package/dist/app.d.ts +3 -3
  5. package/dist/app.esm.js +5 -5
  6. package/dist/{chart-artifact-Dpt4t5sf.d.cts → chart-artifact-CuTiCITz.d.cts} +221 -15
  7. package/dist/{chart-artifact-BYl5C-dk.d.ts → chart-artifact-U-x0UNJm.d.ts} +221 -15
  8. package/dist/chat.esm.js +3 -3
  9. package/dist/{chunk-YYEI6XME.esm.js → chunk-22KWC2LS.esm.js} +5 -5
  10. package/dist/{chunk-MBS7XHV2.esm.js → chunk-45NXD3IG.esm.js} +3 -3
  11. package/dist/{chunk-24B4I4XC.esm.js → chunk-64RHAJVG.esm.js} +1 -1
  12. package/dist/{chunk-WQIQW7EM.esm.js → chunk-7AGIAQE6.esm.js} +1 -1
  13. package/dist/{chunk-NO5AWNWT.esm.js → chunk-7WU3IKAN.esm.js} +1 -1
  14. package/dist/{chunk-UVPXH4MB.esm.js → chunk-M5IBJBEY.esm.js} +80 -21
  15. package/dist/{chunk-HSL36SJ4.esm.js → chunk-PMMI7LBV.esm.js} +20 -8
  16. package/dist/{chunk-TMP7RIA7.esm.js → chunk-VKXOHVDE.esm.js} +2 -2
  17. package/dist/{chunk-ELEY66OH.esm.js → chunk-XOCOZU7J.esm.js} +11 -1
  18. package/dist/cli/timbal-ui-lint.mjs +32 -1
  19. package/dist/index.cjs +274 -198
  20. package/dist/index.d.cts +2 -2
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.esm.js +9 -9
  23. package/dist/{kanban-FFBeaZPS.d.cts → kanban-BQxWliCS.d.cts} +17 -0
  24. package/dist/{kanban-FFBeaZPS.d.ts → kanban-BQxWliCS.d.ts} +17 -0
  25. package/dist/studio.cjs +104 -85
  26. package/dist/studio.esm.js +6 -6
  27. package/dist/ui.cjs +6 -6
  28. package/dist/ui.d.cts +1 -1
  29. package/dist/ui.d.ts +1 -1
  30. package/dist/ui.esm.js +4 -4
  31. package/package.json +1 -1
@@ -2,9 +2,11 @@ import {
2
2
  SIDEBAR_INSET_PX_EXPANDED,
3
3
  STORAGE_KEYS,
4
4
  ShellInsetProvider,
5
+ ShellNavProvider,
5
6
  studioChromeShellStyle,
6
- studioSidebarWidthTransition
7
- } from "./chunk-ELEY66OH.esm.js";
7
+ studioSidebarWidthTransition,
8
+ useOptionalShellNav
9
+ } from "./chunk-XOCOZU7J.esm.js";
8
10
  import {
9
11
  ChartArtifactView,
10
12
  LineAreaChart,
@@ -16,7 +18,7 @@ import {
16
18
  studioIntegrationCardClass,
17
19
  studioTopbarPillHeightClass,
18
20
  toNum
19
- } from "./chunk-WQIQW7EM.esm.js";
21
+ } from "./chunk-7AGIAQE6.esm.js";
20
22
  import {
21
23
  Checkbox,
22
24
  CopyButton,
@@ -29,7 +31,7 @@ import {
29
31
  SelectTrigger,
30
32
  SelectValue,
31
33
  Skeleton
32
- } from "./chunk-NO5AWNWT.esm.js";
34
+ } from "./chunk-7WU3IKAN.esm.js";
33
35
  import {
34
36
  PillSegmentedTabs
35
37
  } from "./chunk-R4RQT2XQ.esm.js";
@@ -45,7 +47,7 @@ import {
45
47
  TIMBAL_V2_SWITCH_TRACK_OFF,
46
48
  TimbalV2Button,
47
49
  controlClass
48
- } from "./chunk-MBS7XHV2.esm.js";
50
+ } from "./chunk-45NXD3IG.esm.js";
49
51
  import {
50
52
  cn
51
53
  } from "./chunk-EDEKQYSU.esm.js";
@@ -178,6 +180,13 @@ var HOUSE_RULES = [
178
180
  slop: `<span className="text-blue-600 bg-green-50">`,
179
181
  good: `<span className="text-primary bg-muted">`
180
182
  },
183
+ {
184
+ id: "chart-token-color",
185
+ rule: "Pass chart and theme color tokens directly (var(--chart-1)) \u2014 never wrap them in hsl(), rgb(), or oklch().",
186
+ why: "The --chart-N and theme tokens are already full OKLCH colors. Wrapping them in hsl()/rgb() is invalid CSS, so the chart renders empty/uncolored \u2014 and the build still passes, so it's a silent runtime bug.",
187
+ slop: `<Cell fill="hsl(var(--chart-1))" />`,
188
+ good: `<Cell fill="var(--chart-1)" />`
189
+ },
181
190
  {
182
191
  id: "no-decorative-icons",
183
192
  rule: "Icons must earn their place (action, nav, or status). Never add an icon beside a label that already says the thing.",
@@ -579,6 +588,7 @@ var RAW_COLOR_RE = new RegExp(
579
588
  "g"
580
589
  );
581
590
  var COLOR_LITERAL_RE = /#[0-9a-fA-F]{3,8}\b|\b(?:oklch|rgba?|hsla?)\s*\(/g;
591
+ var COLOR_FN_WRAPPING_VAR_RE = /\b(?:hsl|hsla|rgb|rgba|oklch|oklab|lab|lch|hwb|color)\s*\(\s*var\(\s*--/i;
582
592
  var INLINE_STYLE_COLOR_RE = /style=\{\{[^}]*\b(?:color|background|backgroundColor|borderColor|fill|stroke)\b/;
583
593
  var BOLD_VALUE_RE = /text-(?:xl|2xl|3xl|4xl|5xl|6xl)[^"'`]*\bfont-(?:bold|extrabold|black|semibold)|font-(?:bold|extrabold|black|semibold)[^"'`]*text-(?:xl|2xl|3xl|4xl|5xl|6xl)/;
584
594
  var GRADIENT_RE = /\bbg-(?:gradient|linear|radial|conic)-/;
@@ -601,11 +611,26 @@ var RESERVED_GRADIENT_SET = new Set(RESERVED_GRADIENT_TOKENS);
601
611
  function stripVariants(util) {
602
612
  return util.replace(/^(?:[a-z-]+:)*/, "");
603
613
  }
614
+ function describeArg(value) {
615
+ if (value === null) return "null";
616
+ if (Array.isArray(value)) return "an array";
617
+ const t = typeof value;
618
+ if (t === "object") {
619
+ const keys = Object.keys(value).slice(0, 4);
620
+ return keys.length ? `an object with keys { ${keys.join(", ")} }` : "an object";
621
+ }
622
+ return `a ${t}`;
623
+ }
604
624
  function isCommentOrImport(line) {
605
625
  const t = line.trim();
606
626
  return t.startsWith("//") || t.startsWith("*") || t.startsWith("/*") || t.startsWith("import ") || t.startsWith("export ");
607
627
  }
608
628
  function lintGeneratedUi(source, options = {}) {
629
+ if (typeof source !== "string") {
630
+ throw new TypeError(
631
+ `lintGeneratedUi(source, options?) expects the generated code as a string, but received ${describeArg(source)}. Pass the raw .tsx source \u2014 lintGeneratedUi(code) \u2014 not an object like { filename, source } and not a previous LintResult.`
632
+ );
633
+ }
609
634
  const maxIcons = options.maxIconsPerView ?? SLOP_BUDGETS.maxIconsPerView;
610
635
  const maxRowDividers = options.maxRowDividers ?? SLOP_BUDGETS.maxRowDividers;
611
636
  const findings = [];
@@ -685,7 +710,17 @@ function lintGeneratedUi(source, options = {}) {
685
710
  });
686
711
  }
687
712
  }
688
- const literals = line.match(COLOR_LITERAL_RE);
713
+ const wrapsTokenInColorFn = COLOR_FN_WRAPPING_VAR_RE.test(line);
714
+ if (wrapsTokenInColorFn) {
715
+ findings.push({
716
+ rule: "chart-token-color-fn",
717
+ severity: "error",
718
+ line: lineNo,
719
+ message: "Color function wrapping a token (e.g. hsl(var(--chart-1))). The --chart-N and theme tokens are already OKLCH colors \u2014 wrapping them in hsl()/rgb() is invalid CSS and renders an empty/uncolored chart (the build still passes). Pass the token directly: var(--chart-1), or let the app-kit charts use --chart-N automatically.",
720
+ snippet: line.trim().slice(0, 120)
721
+ });
722
+ }
723
+ const literals = wrapsTokenInColorFn ? null : line.match(COLOR_LITERAL_RE);
689
724
  if (literals) {
690
725
  findings.push({
691
726
  rule: "color-literal",
@@ -836,6 +871,11 @@ function lintGeneratedUi(source, options = {}) {
836
871
  };
837
872
  }
838
873
  function formatLintReport(findings) {
874
+ if (!Array.isArray(findings)) {
875
+ throw new TypeError(
876
+ `formatLintReport(findings) expects the findings array, but received ${describeArg(findings)}. Pass result.findings \u2014 formatLintReport(lintGeneratedUi(code).findings) \u2014 not the whole LintResult.`
877
+ );
878
+ }
839
879
  if (findings.length === 0) return "";
840
880
  const lines = findings.slice().sort((a, b) => a.line - b.line).map((f) => {
841
881
  const tag = f.severity === "error" ? "ERROR" : "warn ";
@@ -2031,20 +2071,20 @@ function useAppShellChat() {
2031
2071
  }
2032
2072
 
2033
2073
  // src/app/layout/app-shell-nav-context.tsx
2034
- import { createContext as createContext3, useContext as useContext3 } from "react";
2035
- var AppShellNavContext = createContext3(null);
2036
- var AppShellNavProvider = AppShellNavContext.Provider;
2074
+ var AppShellNavProvider = ShellNavProvider;
2075
+ var NAV_NOOP = {
2076
+ open: false,
2077
+ setOpen: () => {
2078
+ },
2079
+ toggle: () => {
2080
+ }
2081
+ };
2037
2082
  function useAppShellNav() {
2038
- return useContext3(AppShellNavContext) ?? {
2039
- open: false,
2040
- setOpen: () => {
2041
- },
2042
- toggle: () => {
2043
- }
2044
- };
2083
+ return useOptionalShellNav() ?? NAV_NOOP;
2045
2084
  }
2046
2085
 
2047
2086
  // src/app/layout/AppShell.tsx
2087
+ import { MenuIcon } from "lucide-react";
2048
2088
  import { motion, useReducedMotion } from "motion/react";
2049
2089
  import { useCallback, useEffect, useMemo, useState as useState2 } from "react";
2050
2090
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
@@ -2054,6 +2094,12 @@ var floatingTriggerClass = cn(
2054
2094
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
2055
2095
  "bottom-6 right-6 max-sm:bottom-4 max-sm:right-4"
2056
2096
  );
2097
+ var floatingNavTriggerClass = cn(
2098
+ "aui-app-shell-nav-trigger-fixed fixed left-4 top-4 z-30 inline-flex size-10 items-center justify-center rounded-xl md:hidden",
2099
+ "border border-border/60 bg-card/85 text-foreground shadow-card-elevated backdrop-blur-xl supports-backdrop-filter:bg-card/75",
2100
+ "transition-colors hover:bg-card",
2101
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
2102
+ );
2057
2103
  var floatingPanelClass = cn(
2058
2104
  "aui-app-shell-chat-float fixed z-50 flex flex-col overflow-hidden rounded-2xl border border-border/60 shadow-card-elevated",
2059
2105
  "bg-card/85 backdrop-blur-xl supports-backdrop-filter:bg-card/75",
@@ -2144,12 +2190,14 @@ var AppShell = ({
2144
2190
  navOpen: navOpenProp,
2145
2191
  defaultNavOpen = false,
2146
2192
  onNavOpenChange,
2193
+ mobileSidebarTrigger = "auto",
2147
2194
  className,
2148
2195
  mainClassName,
2149
2196
  contentFill = false
2150
2197
  }) => {
2151
2198
  const topbarContent = topbar ?? header;
2152
2199
  const hasChat = Boolean(chat);
2200
+ const showFloatingNavTrigger = Boolean(sidebar) && mobileSidebarTrigger !== "none" && !(mobileSidebarTrigger === "topbar") && !topbarContent;
2153
2201
  const [uncontrolledNavOpen, setUncontrolledNavOpen] = useState2(defaultNavOpen);
2154
2202
  const isNavControlled = navOpenProp !== void 0;
2155
2203
  const navOpen = isNavControlled ? navOpenProp : uncontrolledNavOpen;
@@ -2209,6 +2257,17 @@ var AppShell = ({
2209
2257
  style: studioChromeShellStyle,
2210
2258
  children: [
2211
2259
  sidebar,
2260
+ showFloatingNavTrigger && !navOpen ? /* @__PURE__ */ jsx7(
2261
+ "button",
2262
+ {
2263
+ type: "button",
2264
+ "aria-label": "Open navigation",
2265
+ "aria-expanded": false,
2266
+ onClick: () => setNavOpen(true),
2267
+ className: floatingNavTriggerClass,
2268
+ children: /* @__PURE__ */ jsx7(MenuIcon, { className: "size-5", "aria-hidden": true })
2269
+ }
2270
+ ) : null,
2212
2271
  sidebar && navOpen ? /* @__PURE__ */ jsx7(
2213
2272
  "button",
2214
2273
  {
@@ -2293,7 +2352,7 @@ var AppShellChatTrigger = ({
2293
2352
  };
2294
2353
 
2295
2354
  // src/app/layout/AppShellSidebarTrigger.tsx
2296
- import { MenuIcon } from "lucide-react";
2355
+ import { MenuIcon as MenuIcon2 } from "lucide-react";
2297
2356
  import { jsx as jsx9 } from "react/jsx-runtime";
2298
2357
  var AppShellSidebarTrigger = ({
2299
2358
  label = "Open navigation",
@@ -2312,7 +2371,7 @@ var AppShellSidebarTrigger = ({
2312
2371
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/10",
2313
2372
  className
2314
2373
  ),
2315
- children: /* @__PURE__ */ jsx9(MenuIcon, { className: "size-5", "aria-hidden": true })
2374
+ children: /* @__PURE__ */ jsx9(MenuIcon2, { className: "size-5", "aria-hidden": true })
2316
2375
  }
2317
2376
  );
2318
2377
  };
@@ -2450,9 +2509,9 @@ var Stack = ({
2450
2509
  );
2451
2510
 
2452
2511
  // src/app/copilot/app-copilot-context.tsx
2453
- import { createContext as createContext4, useContext as useContext4 } from "react";
2512
+ import { createContext as createContext3, useContext as useContext3 } from "react";
2454
2513
  import { jsx as jsx14 } from "react/jsx-runtime";
2455
- var AppCopilotContext = createContext4(null);
2514
+ var AppCopilotContext = createContext3(null);
2456
2515
  var AppCopilotProvider = ({
2457
2516
  value,
2458
2517
  children
@@ -2460,7 +2519,7 @@ var AppCopilotProvider = ({
2460
2519
  return /* @__PURE__ */ jsx14(AppCopilotContext.Provider, { value, children });
2461
2520
  };
2462
2521
  function useAppCopilotContext() {
2463
- return useContext4(AppCopilotContext) ?? {};
2522
+ return useContext3(AppCopilotContext) ?? {};
2464
2523
  }
2465
2524
 
2466
2525
  // src/app/chat/AppChatPanel.tsx
@@ -16,11 +16,12 @@ import {
16
16
  studioSidebarEntriesTransition,
17
17
  studioSidebarEntryItemVariants,
18
18
  studioSidebarWidthTransition,
19
+ useOptionalShellNav,
19
20
  useShellInsetReporter
20
- } from "./chunk-ELEY66OH.esm.js";
21
+ } from "./chunk-XOCOZU7J.esm.js";
21
22
  import {
22
23
  WorkforceSelector
23
- } from "./chunk-TMP7RIA7.esm.js";
24
+ } from "./chunk-VKXOHVDE.esm.js";
24
25
  import {
25
26
  Composer,
26
27
  TimbalChat,
@@ -43,7 +44,7 @@ import {
43
44
  studioTopbarIconPillClass,
44
45
  studioTopbarPillHeightClass,
45
46
  useTimbalRuntime
46
- } from "./chunk-WQIQW7EM.esm.js";
47
+ } from "./chunk-7AGIAQE6.esm.js";
47
48
  import {
48
49
  DropdownMenu,
49
50
  DropdownMenuContent,
@@ -51,7 +52,7 @@ import {
51
52
  DropdownMenuLabel,
52
53
  DropdownMenuSeparator,
53
54
  DropdownMenuTrigger
54
- } from "./chunk-24B4I4XC.esm.js";
55
+ } from "./chunk-64RHAJVG.esm.js";
55
56
  import {
56
57
  PillSegmentedTabs
57
58
  } from "./chunk-R4RQT2XQ.esm.js";
@@ -63,7 +64,7 @@ import {
63
64
  Tooltip,
64
65
  TooltipContent,
65
66
  TooltipTrigger
66
- } from "./chunk-MBS7XHV2.esm.js";
67
+ } from "./chunk-45NXD3IG.esm.js";
67
68
  import {
68
69
  cn
69
70
  } from "./chunk-EDEKQYSU.esm.js";
@@ -1119,14 +1120,25 @@ var StudioSidebar = ({
1119
1120
  window.addEventListener("resize", onResize);
1120
1121
  return () => window.removeEventListener("resize", onResize);
1121
1122
  }, [mobileBreakpointPx]);
1123
+ const shellNav = useOptionalShellNav();
1124
+ const isControlled = mobileOpenProp !== void 0;
1125
+ const usingShellNav = !isControlled && shellNav !== null;
1122
1126
  const [internalMobileOpen, setInternalMobileOpen] = useState5(false);
1123
- const mobileOpen = mobileOpenProp ?? internalMobileOpen;
1127
+ const mobileOpen = isControlled ? mobileOpenProp : usingShellNav ? shellNav.open : internalMobileOpen;
1124
1128
  const setMobileOpen = useCallback3(
1125
1129
  (next) => {
1126
- if (mobileOpenProp === void 0) setInternalMobileOpen(next);
1130
+ if (isControlled) {
1131
+ onMobileOpenChangeProp?.(next);
1132
+ return;
1133
+ }
1134
+ if (usingShellNav) {
1135
+ shellNav.setOpen(next);
1136
+ } else {
1137
+ setInternalMobileOpen(next);
1138
+ }
1127
1139
  onMobileOpenChangeProp?.(next);
1128
1140
  },
1129
- [mobileOpenProp, onMobileOpenChangeProp]
1141
+ [isControlled, usingShellNav, shellNav, onMobileOpenChangeProp]
1130
1142
  );
1131
1143
  const handleSelect = useCallback3(
1132
1144
  (id) => {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  studioTopbarPillHeightClass
3
- } from "./chunk-WQIQW7EM.esm.js";
3
+ } from "./chunk-7AGIAQE6.esm.js";
4
4
  import {
5
5
  controlSurfaceClass
6
- } from "./chunk-MBS7XHV2.esm.js";
6
+ } from "./chunk-45NXD3IG.esm.js";
7
7
  import {
8
8
  cn
9
9
  } from "./chunk-EDEKQYSU.esm.js";
@@ -119,6 +119,14 @@ function useShellInsetReporter() {
119
119
  return useContext(ShellInsetContext);
120
120
  }
121
121
 
122
+ // src/layout/shell-nav-context.tsx
123
+ import { createContext as createContext2, useContext as useContext2 } from "react";
124
+ var ShellNavContext = createContext2(null);
125
+ var ShellNavProvider = ShellNavContext.Provider;
126
+ function useOptionalShellNav() {
127
+ return useContext2(ShellNavContext);
128
+ }
129
+
122
130
  export {
123
131
  SIDEBAR_WIDTH_PX,
124
132
  SIDEBAR_WIDTH_COLLAPSED_PX,
@@ -138,5 +146,7 @@ export {
138
146
  studioSidebarDrawerTransition,
139
147
  studioSidebarBackdropTransition,
140
148
  ShellInsetProvider,
141
- useShellInsetReporter
149
+ useShellInsetReporter,
150
+ ShellNavProvider,
151
+ useOptionalShellNav
142
152
  };
@@ -86,6 +86,7 @@ var RAW_COLOR_RE = new RegExp(
86
86
  "g"
87
87
  );
88
88
  var COLOR_LITERAL_RE = /#[0-9a-fA-F]{3,8}\b|\b(?:oklch|rgba?|hsla?)\s*\(/g;
89
+ var COLOR_FN_WRAPPING_VAR_RE = /\b(?:hsl|hsla|rgb|rgba|oklch|oklab|lab|lch|hwb|color)\s*\(\s*var\(\s*--/i;
89
90
  var INLINE_STYLE_COLOR_RE = /style=\{\{[^}]*\b(?:color|background|backgroundColor|borderColor|fill|stroke)\b/;
90
91
  var BOLD_VALUE_RE = /text-(?:xl|2xl|3xl|4xl|5xl|6xl)[^"'`]*\bfont-(?:bold|extrabold|black|semibold)|font-(?:bold|extrabold|black|semibold)[^"'`]*text-(?:xl|2xl|3xl|4xl|5xl|6xl)/;
91
92
  var GRADIENT_RE = /\bbg-(?:gradient|linear|radial|conic)-/;
@@ -108,11 +109,26 @@ var RESERVED_GRADIENT_SET = new Set(RESERVED_GRADIENT_TOKENS);
108
109
  function stripVariants(util) {
109
110
  return util.replace(/^(?:[a-z-]+:)*/, "");
110
111
  }
112
+ function describeArg(value) {
113
+ if (value === null) return "null";
114
+ if (Array.isArray(value)) return "an array";
115
+ const t = typeof value;
116
+ if (t === "object") {
117
+ const keys = Object.keys(value).slice(0, 4);
118
+ return keys.length ? `an object with keys { ${keys.join(", ")} }` : "an object";
119
+ }
120
+ return `a ${t}`;
121
+ }
111
122
  function isCommentOrImport(line) {
112
123
  const t = line.trim();
113
124
  return t.startsWith("//") || t.startsWith("*") || t.startsWith("/*") || t.startsWith("import ") || t.startsWith("export ");
114
125
  }
115
126
  function lintGeneratedUi(source, options = {}) {
127
+ if (typeof source !== "string") {
128
+ throw new TypeError(
129
+ `lintGeneratedUi(source, options?) expects the generated code as a string, but received ${describeArg(source)}. Pass the raw .tsx source \u2014 lintGeneratedUi(code) \u2014 not an object like { filename, source } and not a previous LintResult.`
130
+ );
131
+ }
116
132
  const maxIcons = options.maxIconsPerView ?? SLOP_BUDGETS.maxIconsPerView;
117
133
  const maxRowDividers = options.maxRowDividers ?? SLOP_BUDGETS.maxRowDividers;
118
134
  const findings = [];
@@ -192,7 +208,17 @@ function lintGeneratedUi(source, options = {}) {
192
208
  });
193
209
  }
194
210
  }
195
- const literals = line.match(COLOR_LITERAL_RE);
211
+ const wrapsTokenInColorFn = COLOR_FN_WRAPPING_VAR_RE.test(line);
212
+ if (wrapsTokenInColorFn) {
213
+ findings.push({
214
+ rule: "chart-token-color-fn",
215
+ severity: "error",
216
+ line: lineNo,
217
+ message: "Color function wrapping a token (e.g. hsl(var(--chart-1))). The --chart-N and theme tokens are already OKLCH colors \u2014 wrapping them in hsl()/rgb() is invalid CSS and renders an empty/uncolored chart (the build still passes). Pass the token directly: var(--chart-1), or let the app-kit charts use --chart-N automatically.",
218
+ snippet: line.trim().slice(0, 120)
219
+ });
220
+ }
221
+ const literals = wrapsTokenInColorFn ? null : line.match(COLOR_LITERAL_RE);
196
222
  if (literals) {
197
223
  findings.push({
198
224
  rule: "color-literal",
@@ -343,6 +369,11 @@ function lintGeneratedUi(source, options = {}) {
343
369
  };
344
370
  }
345
371
  function formatLintReport(findings) {
372
+ if (!Array.isArray(findings)) {
373
+ throw new TypeError(
374
+ `formatLintReport(findings) expects the findings array, but received ${describeArg(findings)}. Pass result.findings \u2014 formatLintReport(lintGeneratedUi(code).findings) \u2014 not the whole LintResult.`
375
+ );
376
+ }
346
377
  if (findings.length === 0) return "";
347
378
  const lines = findings.slice().sort((a, b) => a.line - b.line).map((f) => {
348
379
  const tag = f.severity === "error" ? "ERROR" : "warn ";