@dbx-tools/ui 0.9.112 → 0.9.116

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.
package/index.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  // Hand edits are overwritten on the next watch; this file is read-only.
4
4
 
5
5
  export const PACKAGE_IDENTIFIER = "@dbx-tools/ui";
6
- export const PACKAGE_VERSION = "0.9.112";
6
+ export const PACKAGE_VERSION = "0.9.116";
7
7
  export * as authReactAuthClient from "./src/auth/react/auth-client.ts";
8
8
  export * as authReactAuthGate from "./src/auth/react/auth-gate.tsx";
9
9
  export * as authReactPasskeyManager from "./src/auth/react/passkey-manager.tsx";
@@ -38,7 +38,8 @@ export type { EmailComposeProps } from "./src/email/react/email-compose.tsx";
38
38
  export { joinAddresses, parseAddresses, attachmentNames } from "./src/email/react/fields.ts";
39
39
  export type { EmailDraft } from "./src/email/react/fields.ts";
40
40
  export type { BrandPreset, BrandPickerProps } from "./src/react/brand-picker.tsx";
41
- export { JsonBlock } from "./src/react/highlighted-code.tsx";
41
+ export { HighlightedCodeBlock, JsonBlock } from "./src/react/highlighted-code.tsx";
42
+ export type { DetectedCodeLanguage } from "./src/react/highlighted-code.tsx";
42
43
  export type { RecordPreviewProps } from "./src/react/record-preview.tsx";
43
44
  export type { RecordPreviewRow } from "./src/react/record-preview-data.ts";
44
45
  export type { SearchBoxProps } from "./src/search/react/search-box.tsx";
package/package.json CHANGED
@@ -30,11 +30,11 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@databricks/appkit-ui": "0.81.0",
33
- "@dbx-tools/shared-auth": "^0.9.112",
34
- "@dbx-tools/shared-core": "^0.9.112",
35
- "@dbx-tools/shared-email": "^0.9.112",
36
- "@dbx-tools/shared-email-template": "^0.9.112",
37
- "@dbx-tools/shared-search": "^0.9.112",
33
+ "@dbx-tools/shared-auth": "^0.9.116",
34
+ "@dbx-tools/shared-core": "^0.9.116",
35
+ "@dbx-tools/shared-email": "^0.9.116",
36
+ "@dbx-tools/shared-email-template": "^0.9.116",
37
+ "@dbx-tools/shared-search": "^0.9.116",
38
38
  "lucide-react": "^0.554.0",
39
39
  "shiki": "^3.0.0",
40
40
  "streamdown": "^2.5.0",
@@ -44,7 +44,7 @@
44
44
  "publishConfig": {
45
45
  "access": "public"
46
46
  },
47
- "version": "0.9.112",
47
+ "version": "0.9.116",
48
48
  "dbxToolsConfig": {
49
49
  "tags": [
50
50
  "ui"
@@ -54,6 +54,7 @@
54
54
  "exports": {
55
55
  "./react": "./src/react/index.ts",
56
56
  "./styles.css": "./src/styles.css",
57
+ "./*": "./src/*/index.ts",
57
58
  "./branding/react": "./src/branding/react/index.ts",
58
59
  "./branding/browser": "./src/branding/browser.ts",
59
60
  "./branding/assets": "./src/branding/generated/assets.ts",
@@ -0,0 +1,6 @@
1
+ // GENERATED by projen watch - DO NOT EDIT.
2
+ // Regenerated from the exporting modules in ./src.
3
+ // Hand edits are overwritten on the next watch; this file is read-only.
4
+
5
+ export * as assets from "./assets.ts";
6
+ export { dbxToolsAssetSvg, dbxToolsAssetDataUrls } from "./assets.ts";
@@ -0,0 +1,8 @@
1
+ // GENERATED by projen watch - DO NOT EDIT.
2
+ // Regenerated from the exporting modules in ./src.
3
+ // Hand edits are overwritten on the next watch; this file is read-only.
4
+
5
+ export * as browser from "./browser.ts";
6
+ export * as generated from "./generated/index.ts";
7
+ export { resolveBrandAsset } from "./browser.ts";
8
+ export type { BrandAssetResolver, ApplyBrandContextOptions } from "./browser.ts";
@@ -4,6 +4,7 @@
4
4
  * @module
5
5
  */
6
6
 
7
+ import { json } from "@dbx-tools/shared-core";
7
8
  import { useEffect, useState } from "react";
8
9
 
9
10
  import { cn } from "./appkit-ui.ts";
@@ -11,6 +12,30 @@ import { highlightToHtml } from "./shiki-plugin.ts";
11
12
 
12
13
  export { createShikiPlugin, highlightToHtml } from "./shiki-plugin.ts";
13
14
 
15
+ /** Code languages detected automatically in free-form text fields. */
16
+ export type DetectedCodeLanguage = "sql" | "python" | "typescript" | "bash" | "json";
17
+
18
+ /** Detect a supported code language from a complete free-form text value. */
19
+ export function detectCodeLanguage(source: string): DetectedCodeLanguage | undefined {
20
+ const trimmed = source.trim();
21
+ if (/^(?:select|with|insert|update|delete|merge|create|alter)\b/i.test(trimmed)) return "sql";
22
+ if (/^(?:from\s+\S+\s+import|import\s+\S+|def\s+\w+|class\s+\w+):?/m.test(trimmed)) {
23
+ return "python";
24
+ }
25
+ if (/^(?:const|let|var|interface|type|export|import)\b/m.test(trimmed)) return "typescript";
26
+ if (/^(?:#!.*\b(?:ba|z|k)?sh\b|(?:cd|ls|cat|grep|find|bun|npm|pnpm|yarn)\s)/m.test(trimmed)) {
27
+ return "bash";
28
+ }
29
+ if (
30
+ (trimmed.startsWith("{") || trimmed.startsWith("[")) &&
31
+ trimmed.length > 1 &&
32
+ json.parse(trimmed) !== undefined
33
+ ) {
34
+ return "json";
35
+ }
36
+ return undefined;
37
+ }
38
+
14
39
  /** Resolve syntax-highlighted HTML while preserving plaintext during lazy load. */
15
40
  export function useHighlightedHtml(source: string, language: string): string | null {
16
41
  const [highlighted, setHighlighted] = useState<{
@@ -32,21 +57,35 @@ export function useHighlightedHtml(source: string, language: string): string | n
32
57
  : null;
33
58
  }
34
59
 
35
- /** Render formatted JSON with the shared lazily loaded Shiki runtime. */
36
- export const JsonBlock = ({ json, className }: { json: string; className?: string }) => {
37
- const html = useHighlightedHtml(json, "json");
60
+ /** Render code with the shared lazily loaded Shiki runtime. */
61
+ export const HighlightedCodeBlock = ({
62
+ source,
63
+ language,
64
+ className,
65
+ }: {
66
+ source: string;
67
+ language: string;
68
+ className?: string;
69
+ }) => {
70
+ const html = useHighlightedHtml(source, language);
38
71
  return (
39
72
  <pre
73
+ data-language={language}
40
74
  className={cn(
41
75
  "max-w-full overflow-auto whitespace-pre-wrap break-words font-mono text-xs leading-relaxed",
42
76
  className,
43
77
  )}
44
78
  >
45
79
  {html === null ? (
46
- <code>{json}</code>
80
+ <code>{source}</code>
47
81
  ) : (
48
82
  <code data-dbx-highlighted-code dangerouslySetInnerHTML={{ __html: html }} />
49
83
  )}
50
84
  </pre>
51
85
  );
52
86
  };
87
+
88
+ /** Render formatted JSON with the shared lazily loaded Shiki runtime. */
89
+ export const JsonBlock = ({ json, className }: { json: string; className?: string }) => (
90
+ <HighlightedCodeBlock source={json} language="json" className={className} />
91
+ );
@@ -4,7 +4,7 @@
4
4
  * @module
5
5
  */
6
6
 
7
- import { stringUtils } from "@dbx-tools/shared-core";
7
+ import { json, object as objectUtils, stringUtils } from "@dbx-tools/shared-core";
8
8
 
9
9
  /** One field shown in the table view of {@link RecordPreview}. */
10
10
  export interface RecordPreviewRow {
@@ -39,6 +39,18 @@ export function formatRecordPreviewJson(value: unknown): string {
39
39
  }
40
40
  }
41
41
 
42
+ /** Parse a string whose complete value is a serialized JSON object or array. */
43
+ export function parseNestedJsonText(
44
+ value: unknown,
45
+ ): Record<string, unknown> | unknown[] | undefined {
46
+ if (typeof value !== "string") return undefined;
47
+ const trimmed = value.trim();
48
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return undefined;
49
+ const parsed = json.parse(trimmed);
50
+ if (Array.isArray(parsed)) return parsed;
51
+ return objectUtils.isRecord(parsed) ? parsed : undefined;
52
+ }
53
+
42
54
  /** YAML document fence at the start of a markdown file (`---` ... `---`). */
43
55
  const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
44
56
 
@@ -21,11 +21,12 @@ import {
21
21
  TooltipTrigger,
22
22
  cn,
23
23
  } from "./appkit-ui.ts";
24
- import { JsonBlock } from "./highlighted-code.tsx";
24
+ import { detectCodeLanguage, HighlightedCodeBlock, JsonBlock } from "./highlighted-code.tsx";
25
25
  import {
26
26
  formatRecordPreviewJson,
27
27
  looksLikeMarkdown,
28
28
  markdownForPreview,
29
+ parseNestedJsonText,
29
30
  recordPreviewRows,
30
31
  type RecordPreviewRow,
31
32
  } from "./record-preview-data.ts";
@@ -35,6 +36,7 @@ export {
35
36
  formatRecordPreviewJson,
36
37
  looksLikeMarkdown,
37
38
  markdownForPreview,
39
+ parseNestedJsonText,
38
40
  recordPreviewRows,
39
41
  } from "./record-preview-data.ts";
40
42
 
@@ -42,7 +44,8 @@ export {
42
44
  const MARKDOWN_CELL_CLASSES =
43
45
  "min-w-0 max-w-full text-[11px] leading-snug [&_p]:my-0.5 [&_p]:leading-snug [&_ul]:my-0.5 [&_ol]:my-0.5 [&_li]:my-0 [&_h1]:my-1 [&_h1]:text-[11px] [&_h1]:font-semibold [&_h2]:my-1 [&_h2]:text-[11px] [&_h2]:font-semibold [&_h3]:my-1 [&_h3]:text-[11px] [&_h3]:font-semibold [&_pre]:my-1 [&_pre]:overflow-x-auto [&_pre]:rounded [&_pre]:bg-background/60 [&_pre]:p-1.5 [&_pre]:text-[10px] [&_code]:text-[10px]";
44
46
 
45
- const NESTED_DEPTH = 4;
47
+ const MAX_NESTED_DEPTH = 12;
48
+ const MAX_INDENT_DEPTH = 4;
46
49
 
47
50
  /** Props for {@link RecordPreview}. */
48
51
  export interface RecordPreviewProps {
@@ -60,87 +63,270 @@ export function RecordPreview({ value, className }: RecordPreviewProps) {
60
63
  return (
61
64
  <div
62
65
  className={cn(
63
- "relative min-w-0 overflow-hidden [&_[data-slot=table-container]]:overflow-x-hidden",
66
+ "relative min-w-0 [&_[data-slot=table-container]]:overflow-x-hidden",
64
67
  className,
65
68
  )}
66
69
  >
67
- <Tooltip>
68
- <TooltipTrigger asChild>
69
- <Button
70
- type="button"
71
- size="icon"
72
- variant="ghost"
73
- aria-pressed={raw}
74
- aria-label={raw ? "Show table" : "Show JSON"}
75
- className="absolute right-1 top-1 z-10 size-6"
76
- onClick={() => setRaw((current) => !current)}
77
- >
78
- {raw ? <Table2Icon className="size-3" /> : <BracesIcon className="size-3" />}
79
- </Button>
80
- </TooltipTrigger>
81
- <TooltipContent>{raw ? "Table" : "JSON"}</TooltipContent>
82
- </Tooltip>
83
- <div className="max-h-[inherit] min-w-0 overflow-auto">
84
- {raw ? (
85
- <JsonBlock json={rawJson} className="rounded bg-background/40 p-2 pr-8 text-[11px]" />
86
- ) : rows.length === 0 ? (
87
- <pre className="whitespace-pre-wrap break-words rounded bg-background/40 p-2 pr-8 font-mono text-[11px] leading-relaxed">
88
- {rawJson}
89
- </pre>
90
- ) : (
91
- <RecordTable rows={rows} depth={0} />
92
- )}
70
+ <div className="sticky top-1 z-10 float-right mr-1 h-0">
71
+ <Tooltip>
72
+ <TooltipTrigger asChild>
73
+ <Button
74
+ type="button"
75
+ size="icon"
76
+ variant="ghost"
77
+ aria-pressed={raw}
78
+ aria-label={raw ? "Show table" : "Show JSON"}
79
+ className="size-6 bg-background/85 backdrop-blur-sm"
80
+ onClick={() => setRaw((current) => !current)}
81
+ >
82
+ {raw ? <Table2Icon className="size-3" /> : <BracesIcon className="size-3" />}
83
+ </Button>
84
+ </TooltipTrigger>
85
+ <TooltipContent>{raw ? "Table" : "JSON"}</TooltipContent>
86
+ </Tooltip>
93
87
  </div>
88
+ {raw ? (
89
+ <JsonBlock json={rawJson} className="rounded bg-background/40 p-2 pr-8 text-[11px]" />
90
+ ) : rows.length === 0 ? (
91
+ <pre className="whitespace-pre-wrap break-words rounded bg-background/40 p-2 pr-8 font-mono text-[11px] leading-relaxed">
92
+ {rawJson}
93
+ </pre>
94
+ ) : (
95
+ <RecordTable rows={rows} />
96
+ )}
94
97
  </div>
95
98
  );
96
99
  }
97
100
 
98
- function RecordTable({ rows, depth }: { rows: RecordPreviewRow[]; depth: number }) {
101
+ function RecordTable({ rows }: { rows: RecordPreviewRow[] }) {
99
102
  if (rows.length === 0) return null;
100
103
  return (
101
- <Table className="table-fixed text-[11px]">
102
- <TableBody>
103
- {rows.map((row) => {
104
- const nested = isNestedValue(row.value) && depth < NESTED_DEPTH;
105
- return nested ? (
106
- <Fragment key={row.key}>
107
- <TableRow className="border-b-0 hover:bg-transparent">
108
- <TableHead
109
- colSpan={2}
110
- className="h-auto whitespace-normal px-2 pb-1 pt-2 font-medium text-muted-foreground"
111
- >
112
- {row.label}
113
- </TableHead>
114
- </TableRow>
115
- <TableRow className="hover:bg-transparent">
116
- <TableCell
117
- colSpan={2}
118
- className="min-w-0 whitespace-normal p-0 pb-1 pl-2 [overflow-wrap:anywhere]"
119
- >
120
- <RecordValue value={row.value} depth={depth} />
121
- </TableCell>
122
- </TableRow>
123
- </Fragment>
124
- ) : (
125
- <TableRow key={row.key} className="hover:bg-transparent">
126
- <TableHead className="h-auto w-28 align-top whitespace-normal px-2 py-1.5 font-medium text-muted-foreground sm:w-[8.5rem]">
127
- {row.label}
128
- </TableHead>
129
- <TableCell className="min-w-0 whitespace-normal px-2 py-1.5 pr-8 align-top [overflow-wrap:anywhere]">
130
- <RecordValue value={row.value} depth={depth} />
131
- </TableCell>
132
- </TableRow>
133
- );
134
- })}
135
- </TableBody>
136
- </Table>
104
+ <div className="min-w-0 px-2 pb-2">
105
+ <Table className="table-fixed text-[11px]">
106
+ <TableBody>
107
+ {rows.map((row) => (
108
+ <RecordRow key={row.key} row={row} depth={0} path={row.key} />
109
+ ))}
110
+ </TableBody>
111
+ </Table>
112
+ </div>
113
+ );
114
+ }
115
+
116
+ function RecordRow({
117
+ row,
118
+ depth,
119
+ path,
120
+ divided,
121
+ }: {
122
+ row: RecordPreviewRow;
123
+ depth: number;
124
+ path: string;
125
+ divided?: boolean;
126
+ }) {
127
+ const nested = nestedRecordValue(row.value);
128
+ if (!nested) {
129
+ return <FieldRow row={row} depth={depth} divided={divided} />;
130
+ }
131
+ return (
132
+ <Fragment>
133
+ <SectionRow
134
+ label={row.label}
135
+ depth={depth}
136
+ divided={divided}
137
+ parsedFromText={nested.parsedFromText}
138
+ />
139
+ <NestedRows value={nested.value} depth={depth + 1} path={path} />
140
+ </Fragment>
141
+ );
142
+ }
143
+
144
+ function FieldRow({
145
+ row,
146
+ depth,
147
+ divided,
148
+ }: {
149
+ row: RecordPreviewRow;
150
+ depth: number;
151
+ divided?: boolean;
152
+ }) {
153
+ return (
154
+ <TableRow className={cn("hover:bg-transparent", divided && "border-t-2 border-border/50")}>
155
+ <TableHead
156
+ className="h-auto w-28 align-top whitespace-normal px-0 py-1.5 pr-2 font-medium text-muted-foreground sm:w-[8.5rem]"
157
+ style={{ paddingLeft: rowIndent(depth) }}
158
+ >
159
+ {row.label}
160
+ </TableHead>
161
+ <TableCell className="min-w-0 whitespace-normal px-0 py-1.5 pr-8 align-top [overflow-wrap:anywhere]">
162
+ <RecordValue value={row.value} depth={depth} />
163
+ </TableCell>
164
+ </TableRow>
165
+ );
166
+ }
167
+
168
+ function SectionRow({
169
+ label,
170
+ depth,
171
+ divided,
172
+ parsedFromText,
173
+ }: {
174
+ label: string;
175
+ depth: number;
176
+ divided?: boolean;
177
+ parsedFromText: boolean;
178
+ }) {
179
+ return (
180
+ <TableRow
181
+ className={cn("border-b-0 hover:bg-transparent", divided && "border-t-2 border-border/50")}
182
+ >
183
+ <TableHead
184
+ colSpan={2}
185
+ className="h-auto px-0 pb-1 pt-3 text-[10px] font-semibold uppercase tracking-wide text-foreground/85"
186
+ style={{ paddingLeft: rowIndent(depth) }}
187
+ >
188
+ <span className="inline-flex items-center gap-1">
189
+ {label}
190
+ {parsedFromText ? (
191
+ <BracesIcon
192
+ className="size-3 text-muted-foreground"
193
+ aria-label="JSON parsed from text"
194
+ />
195
+ ) : null}
196
+ </span>
197
+ </TableHead>
198
+ </TableRow>
199
+ );
200
+ }
201
+
202
+ function NestedRows({ value, depth, path }: { value: unknown; depth: number; path: string }) {
203
+ if (depth > MAX_NESTED_DEPTH) {
204
+ return <ValueRow value={value} depth={depth} />;
205
+ }
206
+ if (Array.isArray(value)) {
207
+ return (
208
+ <Fragment>
209
+ {value.map((item, index) => (
210
+ <ArrayItemRows
211
+ key={`${path}.${index}`}
212
+ value={item}
213
+ depth={depth}
214
+ path={`${path}.${index}`}
215
+ divided={index > 0}
216
+ />
217
+ ))}
218
+ </Fragment>
219
+ );
220
+ }
221
+ if (value != null && typeof value === "object") {
222
+ const rows = recordPreviewRows(value);
223
+ if (rows.length === 0) return <ValueRow value={value} depth={depth} />;
224
+ return (
225
+ <Fragment>
226
+ {rows.map((row) => (
227
+ <RecordRow
228
+ key={`${path}.${row.key}`}
229
+ row={row}
230
+ depth={depth}
231
+ path={`${path}.${row.key}`}
232
+ />
233
+ ))}
234
+ </Fragment>
235
+ );
236
+ }
237
+ return <ValueRow value={value} depth={depth} />;
238
+ }
239
+
240
+ function ArrayItemRows({
241
+ value,
242
+ depth,
243
+ path,
244
+ divided,
245
+ }: {
246
+ value: unknown;
247
+ depth: number;
248
+ path: string;
249
+ divided: boolean;
250
+ }) {
251
+ const nested = nestedRecordValue(value);
252
+ if (nested?.parsedFromText) {
253
+ return (
254
+ <Fragment>
255
+ <NestedTextMarkerRow depth={depth} divided={divided} />
256
+ <NestedRows value={nested.value} depth={depth + 1} path={path} />
257
+ </Fragment>
258
+ );
259
+ }
260
+ if (value != null && typeof value === "object") {
261
+ const rows = recordPreviewRows(value);
262
+ if (rows.length === 0) return <ValueRow value={value} depth={depth} divided={divided} />;
263
+ return (
264
+ <Fragment>
265
+ {rows.map((row, index) => (
266
+ <RecordRow
267
+ key={`${path}.${row.key}`}
268
+ row={row}
269
+ depth={depth}
270
+ path={`${path}.${row.key}`}
271
+ divided={divided && index === 0}
272
+ />
273
+ ))}
274
+ </Fragment>
275
+ );
276
+ }
277
+ return <ValueRow value={value} depth={depth} divided={divided} />;
278
+ }
279
+
280
+ function NestedTextMarkerRow({ depth, divided }: { depth: number; divided: boolean }) {
281
+ return (
282
+ <TableRow className={cn("border-b-0 hover:bg-transparent", divided && "border-t-2")}>
283
+ <TableHead
284
+ colSpan={2}
285
+ className="h-auto px-0 pb-0.5 pt-2 text-muted-foreground"
286
+ style={{ paddingLeft: rowIndent(depth) }}
287
+ >
288
+ <BracesIcon className="size-3" aria-label="JSON parsed from text" />
289
+ </TableHead>
290
+ </TableRow>
291
+ );
292
+ }
293
+
294
+ function ValueRow({ value, depth, divided }: { value: unknown; depth: number; divided?: boolean }) {
295
+ return (
296
+ <TableRow className={cn("hover:bg-transparent", divided && "border-t-2 border-border/50")}>
297
+ <TableHead
298
+ aria-hidden
299
+ className="h-auto w-28 px-0 py-1.5 pr-2 sm:w-[8.5rem]"
300
+ style={{ paddingLeft: rowIndent(depth) }}
301
+ />
302
+ <TableCell className="min-w-0 whitespace-normal px-0 py-1.5 pr-8 align-top [overflow-wrap:anywhere]">
303
+ <RecordValue value={value} depth={depth} />
304
+ </TableCell>
305
+ </TableRow>
137
306
  );
138
307
  }
139
308
 
140
- function isNestedValue(value: unknown): boolean {
141
- if (value == null || typeof value !== "object") return false;
142
- if (!Array.isArray(value)) return true;
143
- return value.some((item) => item != null && typeof item === "object");
309
+ function rowIndent(depth: number): string {
310
+ return `${Math.min(depth, MAX_INDENT_DEPTH) * 0.9}rem`;
311
+ }
312
+
313
+ function nestedRecordValue(
314
+ value: unknown,
315
+ ): { value: Record<string, unknown> | unknown[]; parsedFromText: boolean } | undefined {
316
+ const parsed = parseNestedJsonText(value);
317
+ if (parsed) return { value: parsed, parsedFromText: true };
318
+ if (value == null || typeof value !== "object") return undefined;
319
+ if (Array.isArray(value)) {
320
+ const hasNestedValue = value.some(
321
+ (item) =>
322
+ (item != null && typeof item === "object") || parseNestedJsonText(item) !== undefined,
323
+ );
324
+ if (!hasNestedValue) return undefined;
325
+ }
326
+ return {
327
+ value: value as Record<string, unknown> | unknown[],
328
+ parsedFromText: false,
329
+ };
144
330
  }
145
331
 
146
332
  function RecordValue({ value, depth }: { value: unknown; depth: number }) {
@@ -151,6 +337,16 @@ function RecordValue({ value, depth }: { value: unknown; depth: number }) {
151
337
  return <span className="tabular-nums">{String(value)}</span>;
152
338
  }
153
339
  if (typeof value === "string") {
340
+ const language = detectCodeLanguage(value);
341
+ if (language === "sql") {
342
+ return (
343
+ <HighlightedCodeBlock
344
+ source={value}
345
+ language={language}
346
+ className="rounded bg-background/60 p-1.5 text-[10px]"
347
+ />
348
+ );
349
+ }
154
350
  if (looksLikeMarkdown(value)) {
155
351
  return (
156
352
  <div className={MARKDOWN_CELL_CLASSES}>
@@ -160,16 +356,13 @@ function RecordValue({ value, depth }: { value: unknown; depth: number }) {
160
356
  }
161
357
  return <span className="whitespace-pre-wrap break-words">{value}</span>;
162
358
  }
163
- if (typeof value === "object" && !Array.isArray(value) && depth < NESTED_DEPTH) {
164
- return <RecordTable rows={recordPreviewRows(value)} depth={depth + 1} />;
165
- }
166
359
  if (Array.isArray(value)) {
167
360
  if (value.every((item) => item == null || typeof item !== "object")) {
168
361
  return (
169
362
  <span className="whitespace-pre-wrap break-words">{value.map(String).join(", ")}</span>
170
363
  );
171
364
  }
172
- if (depth < NESTED_DEPTH) {
365
+ if (depth < MAX_NESTED_DEPTH) {
173
366
  return (
174
367
  <div className="min-w-0 divide-y divide-border/60">
175
368
  {value.map((item, index) => (