@lunora/studio 0.0.0 → 1.0.0-alpha.2

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 (81) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +123 -9
  3. package/__assets__/package-og.svg +14 -0
  4. package/dist/index.d.ts +1402 -0
  5. package/dist/index.js +41 -0
  6. package/dist/mount.d.ts +21 -0
  7. package/dist/mount.js +26 -0
  8. package/dist/packem_shared/ADMIN_FUNCTION_PREFIX-DmBqMZ-z.js +45 -0
  9. package/dist/packem_shared/ApiDocsPanel-DpRjJhG5.js +842 -0
  10. package/dist/packem_shared/ApiReferencePanel-DMIUp-kK.js +229 -0
  11. package/dist/packem_shared/ApiTab-DURGU15e.js +251 -0
  12. package/dist/packem_shared/AuditPanel-BC59Nhst.js +212 -0
  13. package/dist/packem_shared/CommandPalette-Dx_CoB9i.js +373 -0
  14. package/dist/packem_shared/ConfirmButton-WQVUoGFb.js +59 -0
  15. package/dist/packem_shared/ConnectionBadge-Bxagrip8.js +111 -0
  16. package/dist/packem_shared/DEFAULT_AUTO_REFRESH_MS-Vxwaxx51.js +50 -0
  17. package/dist/packem_shared/DEFAULT_INSIGHT_THRESHOLDS-DjF0h-gA.js +89 -0
  18. package/dist/packem_shared/DataBrowser-Coz6jJE6.js +4542 -0
  19. package/dist/packem_shared/DataFilters-FNquMaiu.js +249 -0
  20. package/dist/packem_shared/ErrorBoundary-BzAApI7J.js +66 -0
  21. package/dist/packem_shared/ExportImportPanel-WO34fJxy.js +193 -0
  22. package/dist/packem_shared/FileBrowser-Zcr-Qgxo.js +2932 -0
  23. package/dist/packem_shared/FunctionRunner-j0Rd5m9t.js +343 -0
  24. package/dist/packem_shared/FunctionStatsPanel-DboBl-XL.js +432 -0
  25. package/dist/packem_shared/GlobalDataBrowser-9MhPEfgN.js +318 -0
  26. package/dist/packem_shared/HealthPanel-DOIgbUtx.js +640 -0
  27. package/dist/packem_shared/HomePanel-bdOCNA-p.js +1273 -0
  28. package/dist/packem_shared/InsightsPanel-DaZPnSgt.js +423 -0
  29. package/dist/packem_shared/LogsPanel-CWdqAGpQ.js +839 -0
  30. package/dist/packem_shared/MailPanel-D_EGtDnS.js +447 -0
  31. package/dist/packem_shared/MetricsPanel-E4Gv6wTO.js +1625 -0
  32. package/dist/packem_shared/MigrationsPanel-DQdPY9io.js +246 -0
  33. package/dist/packem_shared/OpenRpcReferencePanel-j2p3HB0s.js +191 -0
  34. package/dist/packem_shared/PitrPanel-BbBkQR6t.js +252 -0
  35. package/dist/packem_shared/STUDIO_ROOT_CLASS-D12gX2dV.js +3 -0
  36. package/dist/packem_shared/ScheduledJobs-Ok1CYYwI.js +159 -0
  37. package/dist/packem_shared/SchemaViewer-D8XGnp-X.js +2512 -0
  38. package/dist/packem_shared/SecurityAdvisorPanel-Cdm2IxLW.js +79 -0
  39. package/dist/packem_shared/SettingsPanel-D3WF2mBU.js +176 -0
  40. package/dist/packem_shared/ShardInput-DNCsT1KW.js +107 -0
  41. package/dist/packem_shared/SqlEditorPanel-BuQ7f2Hs.js +13 -0
  42. package/dist/packem_shared/Studio-D36od9Oz.js +33 -0
  43. package/dist/packem_shared/StudioApp-dvywkJ8I.js +383 -0
  44. package/dist/packem_shared/StudioI18nProvider-Dcajsznk.js +48 -0
  45. package/dist/packem_shared/TableEditor-DIVDk3vT.js +371 -0
  46. package/dist/packem_shared/advisor-view-DBlzJi6C.js +159 -0
  47. package/dist/packem_shared/aggregateMetrics-D4nUHEKU.js +108 -0
  48. package/dist/packem_shared/app.d-CCmwDEVs.d.ts +300 -0
  49. package/dist/packem_shared/badge-B2PKA1-5.js +49 -0
  50. package/dist/packem_shared/bar-chart-CzJAgqkp.js +3245 -0
  51. package/dist/packem_shared/button-BhsN2uZH.js +49 -0
  52. package/dist/packem_shared/card-DURq3ElK.js +175 -0
  53. package/dist/packem_shared/cf-links-BZfRdxSE.js +8 -0
  54. package/dist/packem_shared/checkbox-UNkzAxl-.js +63 -0
  55. package/dist/packem_shared/createStudioI18n-CgvlmDkN.js +27 -0
  56. package/dist/packem_shared/data-grid-CCh2Couo.js +183 -0
  57. package/dist/packem_shared/dropdown-menu-WY4B_eJO.js +280 -0
  58. package/dist/packem_shared/empty-state-DY_oe0k6.js +98 -0
  59. package/dist/packem_shared/grid-features-DTjG6Sex.js +840 -0
  60. package/dist/packem_shared/input-XH4r1Pt1.js +53 -0
  61. package/dist/packem_shared/internal-BBZYexre.js +68 -0
  62. package/dist/packem_shared/label-D8ykjn5J.js +46 -0
  63. package/dist/packem_shared/live-status-bPff1O7Y.js +44 -0
  64. package/dist/packem_shared/reference-view-BCKIoai7.js +2180 -0
  65. package/dist/packem_shared/shard-history-DyebH1R5.js +38 -0
  66. package/dist/packem_shared/sparkline-10dG-_f0.js +93 -0
  67. package/dist/packem_shared/sql-editor-panel-CW2y2x9h.js +2562 -0
  68. package/dist/packem_shared/storage-tier-CL98eOvn.js +85 -0
  69. package/dist/packem_shared/studio-BDVd7rIV.js +10303 -0
  70. package/dist/packem_shared/table-_RzNvy3R.js +246 -0
  71. package/dist/packem_shared/table-list-sidebar-aZHLq70w.js +832 -0
  72. package/dist/packem_shared/textarea-D3gaCU_-.js +46 -0
  73. package/dist/packem_shared/use-live-admin-D1h1Fzsd.js +73 -0
  74. package/dist/packem_shared/use-live-shard-seed-B74RYcOy.js +76 -0
  75. package/dist/packem_shared/useDebounced-Dxncpg6z.js +32 -0
  76. package/dist/packem_shared/utils-B05Dmz_H.js +8 -0
  77. package/dist/packem_shared/virtual-rect-CVMUskSm.js +10 -0
  78. package/dist/standalone/studio.js +356 -0
  79. package/dist/styles.css +2 -0
  80. package/package.json +77 -17
  81. package/src/theme.css +59 -0
@@ -0,0 +1,423 @@
1
+ import { c } from 'react/compiler-runtime';
2
+ import { useLunora } from '@lunora/react';
3
+ import { useNavigate } from '@tanstack/react-router';
4
+ import { useState, useEffect, useMemo } from 'react';
5
+ import { ShardInput } from './ShardInput-DNCsT1KW.js';
6
+ import { B as Button } from './button-BhsN2uZH.js';
7
+ import { useT } from './createStudioI18n-CgvlmDkN.js';
8
+ import { ADMIN_FUNCTIONS } from './ADMIN_FUNCTION_PREFIX-DmBqMZ-z.js';
9
+ import { f as fireAndForget, c as callOptions, e as errorMessage, a as adminRef } from './internal-BBZYexre.js';
10
+ import { r as recordShard } from './shard-history-DyebH1R5.js';
11
+ import { u as useLiveShardSeed } from './use-live-shard-seed-B74RYcOy.js';
12
+ import { a as advisoryRow, A as AdvisorView } from './advisor-view-DBlzJi6C.js';
13
+ import { ConfirmButton } from './ConfirmButton-WQVUoGFb.js';
14
+ import { jsxDEV } from 'react/jsx-dev-runtime';
15
+ import { deriveInsights } from './DEFAULT_INSIGHT_THRESHOLDS-DjF0h-gA.js';
16
+ import { runAdvisor, RUNTIME_LINTS } from '@lunora/advisor';
17
+
18
+ const hasIndexMetadata = (metadata) => {
19
+ const {
20
+ suggestedIndex,
21
+ table
22
+ } = metadata;
23
+ if (typeof table !== "string" || table.length === 0) {
24
+ return false;
25
+ }
26
+ if (typeof suggestedIndex !== "object" || suggestedIndex === null || !Array.isArray(suggestedIndex["fields"]) || suggestedIndex["fields"].length === 0) {
27
+ return false;
28
+ }
29
+ return true;
30
+ };
31
+ const composeCreateIndex = (table, indexName, fields) => {
32
+ const quotedTable = `"${table}"`;
33
+ const quotedIndex = `"${indexName}"`;
34
+ const quotedColumns = fields.map((f) => `"${f}"`).join(", ");
35
+ return `CREATE INDEX IF NOT EXISTS ${quotedIndex} ON ${quotedTable} (${quotedColumns});`;
36
+ };
37
+
38
+ const ApplyIndexButton = (t0) => {
39
+ const $ = c(19);
40
+ const {
41
+ fields,
42
+ indexName,
43
+ table,
44
+ testId
45
+ } = t0;
46
+ const t = useT();
47
+ const [applied, setApplied] = useState(false);
48
+ let t1;
49
+ if ($[0] !== fields || $[1] !== indexName || $[2] !== table) {
50
+ t1 = () => {
51
+ const clipboard = "navigator" in globalThis ? globalThis.navigator.clipboard : void 0;
52
+ if (clipboard === void 0) {
53
+ return;
54
+ }
55
+ const sql = composeCreateIndex(table, indexName, fields);
56
+ fireAndForget(clipboard.writeText(sql));
57
+ setApplied(true);
58
+ };
59
+ $[0] = fields;
60
+ $[1] = indexName;
61
+ $[2] = table;
62
+ $[3] = t1;
63
+ } else {
64
+ t1 = $[3];
65
+ }
66
+ const onConfirm = t1;
67
+ if (applied) {
68
+ const t22 = `${testId}-applied`;
69
+ let t32;
70
+ if ($[4] !== t) {
71
+ t32 = t("CREATE INDEX SQL copied to clipboard.");
72
+ $[4] = t;
73
+ $[5] = t32;
74
+ } else {
75
+ t32 = $[5];
76
+ }
77
+ let t42;
78
+ if ($[6] !== t22 || $[7] !== t32) {
79
+ t42 = /* @__PURE__ */ jsxDEV("span", {
80
+ className: "text-xs text-muted-foreground",
81
+ "data-testid": t22,
82
+ children: t32
83
+ }, void 0, false);
84
+ $[6] = t22;
85
+ $[7] = t32;
86
+ $[8] = t42;
87
+ } else {
88
+ t42 = $[8];
89
+ }
90
+ return t42;
91
+ }
92
+ let t2;
93
+ if ($[9] !== t) {
94
+ t2 = t("Apply?");
95
+ $[9] = t;
96
+ $[10] = t2;
97
+ } else {
98
+ t2 = $[10];
99
+ }
100
+ let t3;
101
+ if ($[11] !== t || $[12] !== table) {
102
+ t3 = t("Apply index on {table}", {
103
+ table
104
+ });
105
+ $[11] = t;
106
+ $[12] = table;
107
+ $[13] = t3;
108
+ } else {
109
+ t3 = $[13];
110
+ }
111
+ let t4;
112
+ if ($[14] !== onConfirm || $[15] !== t2 || $[16] !== t3 || $[17] !== testId) {
113
+ t4 = /* @__PURE__ */ jsxDEV(ConfirmButton, {
114
+ confirmLabel: t2,
115
+ onConfirm,
116
+ testId,
117
+ children: t3
118
+ }, void 0, false);
119
+ $[14] = onConfirm;
120
+ $[15] = t2;
121
+ $[16] = t3;
122
+ $[17] = testId;
123
+ $[18] = t4;
124
+ } else {
125
+ t4 = $[18];
126
+ }
127
+ return t4;
128
+ };
129
+
130
+ const reconcileIndexHits = (declaredIndexes, indexHits) => {
131
+ const readsByKey = /* @__PURE__ */ new Map();
132
+ for (const hit of indexHits) {
133
+ const key = `${hit.table} ${hit.index}`;
134
+ readsByKey.set(key, (readsByKey.get(key) ?? 0) + hit.reads);
135
+ }
136
+ return declaredIndexes.map((declared) => {
137
+ return {
138
+ index: declared.index,
139
+ reads: readsByKey.get(`${declared.table} ${declared.index}`) ?? 0,
140
+ table: declared.table
141
+ };
142
+ });
143
+ };
144
+ const aggregateTableScans = (functions) => {
145
+ const scansByTable = /* @__PURE__ */ new Map();
146
+ for (const callStat of functions) {
147
+ for (const attribution of callStat.scannedTables ?? []) {
148
+ scansByTable.set(attribution.table, (scansByTable.get(attribution.table) ?? 0) + attribution.scans);
149
+ }
150
+ }
151
+ return [...scansByTable].map(([table, scans]) => {
152
+ return {
153
+ scans,
154
+ table
155
+ };
156
+ });
157
+ };
158
+ const declaredIndexesFor = (table, indexes) => indexes.map((index) => {
159
+ return {
160
+ index: index.name,
161
+ table
162
+ };
163
+ });
164
+ const deriveRuntimeAdvisories = (inputs) => {
165
+ const inDoIndexHits = reconcileIndexHits(inputs.declaredIndexes ?? [], inputs.indexHits ?? []);
166
+ const inDoTableScans = aggregateTableScans(inputs.functions ?? []);
167
+ const inDoShardTraffic = inputs.shardTraffic ?? [];
168
+ const analytics = inputs.analyticsMetrics;
169
+ const indexHits = analytics && analytics.indexHits.length > 0 ? analytics.indexHits : inDoIndexHits;
170
+ const tableScans = analytics && analytics.tableScans.length > 0 ? analytics.tableScans : inDoTableScans;
171
+ const shardTraffic = analytics && analytics.shardTraffic.length > 0 ? analytics.shardTraffic : inDoShardTraffic;
172
+ const findings = runAdvisor({
173
+ indexHits,
174
+ schema: {
175
+ tables: []
176
+ },
177
+ shardTraffic,
178
+ tableScans
179
+ }, {
180
+ lints: RUNTIME_LINTS,
181
+ source: "runtime"
182
+ });
183
+ const suppress = inputs.suppressHotScanTables;
184
+ const visible = suppress === void 0 ? findings : findings.filter((finding) => !(finding.metadata["kind"] === "hot_scan" && typeof finding.metadata["table"] === "string" && suppress.has(finding.metadata["table"])));
185
+ return visible.map((finding) => advisoryRow(finding));
186
+ };
187
+
188
+ const GET_ADVISORIES = adminRef(ADMIN_FUNCTIONS.getAdvisories);
189
+ const GET_FUNCTION_STATS = adminRef(ADMIN_FUNCTIONS.getFunctionStats);
190
+ const GET_METRICS = adminRef(ADMIN_FUNCTIONS.getMetrics);
191
+ const LIST_TABLES = adminRef(ADMIN_FUNCTIONS.listTables);
192
+ const LIST_TABLE_INDEXES = adminRef(ADMIN_FUNCTIONS.listTableIndexes);
193
+ const percent = (rate) => `${(rate * 100).toFixed(1)}%`;
194
+ const seconds = (ms) => `${(ms / 1e3).toFixed(2)}s`;
195
+ const tableList = (tables) => {
196
+ if (tables.length <= 1) {
197
+ return tables[0] ?? "";
198
+ }
199
+ const last = tables[tables.length - 1] ?? "";
200
+ return `${tables.slice(0, -1).join(", ")} and ${last}`;
201
+ };
202
+ const insightTitle = (t, insight) => ({
203
+ "high-error-rate": t("High error rate"),
204
+ "high-evictions": t("High cache eviction rate"),
205
+ "high-write-contention": t("High write contention"),
206
+ "low-cache-hit-rate": t("Low cache hit rate"),
207
+ "missing-index": t("Missing index"),
208
+ "slow-function": t("Slow function")
209
+ })[insight.kind];
210
+ const insightDetail = (t, insight) => ({
211
+ "high-error-rate": t("{rate} of calls failed.", {
212
+ rate: percent(insight.value)
213
+ }),
214
+ "high-evictions": t("{count} entries evicted recently.", {
215
+ count: insight.value
216
+ }),
217
+ "high-write-contention": t("{rate} of calls hit a write conflict — consider sharding to cut contention.", {
218
+ rate: percent(insight.value)
219
+ }),
220
+ "low-cache-hit-rate": t("{rate} hit rate over recent traffic.", {
221
+ rate: percent(insight.value)
222
+ }),
223
+ "missing-index": t("Slowest call took {duration} — it full-scanned {tables} with no index.", {
224
+ duration: seconds(insight.value),
225
+ tables: tableList(insight.tables ?? [])
226
+ }),
227
+ "slow-function": t("Slowest call took {duration}.", {
228
+ duration: seconds(insight.value)
229
+ })
230
+ })[insight.kind];
231
+ const AddIndexButton = (t0) => {
232
+ const $ = c(10);
233
+ const {
234
+ onJump,
235
+ table
236
+ } = t0;
237
+ const t = useT();
238
+ let t1;
239
+ if ($[0] !== onJump || $[1] !== table) {
240
+ t1 = () => {
241
+ onJump(table);
242
+ };
243
+ $[0] = onJump;
244
+ $[1] = table;
245
+ $[2] = t1;
246
+ } else {
247
+ t1 = $[2];
248
+ }
249
+ const onClick = t1;
250
+ const t2 = `in-add-index-${table}`;
251
+ let t3;
252
+ if ($[3] !== t || $[4] !== table) {
253
+ t3 = t("Add index on {table}", {
254
+ table
255
+ });
256
+ $[3] = t;
257
+ $[4] = table;
258
+ $[5] = t3;
259
+ } else {
260
+ t3 = $[5];
261
+ }
262
+ let t4;
263
+ if ($[6] !== onClick || $[7] !== t2 || $[8] !== t3) {
264
+ t4 = /* @__PURE__ */ jsxDEV(Button, {
265
+ "data-testid": t2,
266
+ onClick,
267
+ size: "sm",
268
+ type: "button",
269
+ variant: "outline",
270
+ children: t3
271
+ }, void 0, false);
272
+ $[6] = onClick;
273
+ $[7] = t2;
274
+ $[8] = t3;
275
+ $[9] = t4;
276
+ } else {
277
+ t4 = $[9];
278
+ }
279
+ return t4;
280
+ };
281
+ const InsightsPanel = ({
282
+ initialShardKey,
283
+ loadShardTraffic
284
+ }) => {
285
+ const client = useLunora();
286
+ const navigate = useNavigate();
287
+ const t = useT();
288
+ const [shardKey, setShardKey] = useState(initialShardKey ?? "");
289
+ const [metrics, setMetrics] = useState(null);
290
+ const [functions, setFunctions] = useState(null);
291
+ const [advisories, setAdvisories] = useState(null);
292
+ const [indexHits, setIndexHits] = useState(null);
293
+ const [declaredIndexes, setDeclaredIndexes] = useState(null);
294
+ const [shardTraffic, setShardTraffic] = useState(null);
295
+ const [error, setError] = useState(null);
296
+ const fanShardTraffic = loadShardTraffic ?? ((table) => client.shardTraffic(table));
297
+ const refresh = async (shard) => {
298
+ const [snapshot, stats, advisorySnapshot] = await Promise.allSettled([client.query(GET_METRICS, {}, callOptions(shard)), client.query(GET_FUNCTION_STATS, {}, callOptions(shard)), client.query(GET_ADVISORIES, {}, callOptions(shard))]);
299
+ if (snapshot.status === "rejected" && stats.status === "rejected") {
300
+ setError(errorMessage(snapshot.reason));
301
+ } else {
302
+ setError(null);
303
+ recordShard(shard);
304
+ }
305
+ setMetrics(snapshot.status === "fulfilled" ? snapshot.value : null);
306
+ setFunctions(stats.status === "fulfilled" ? stats.value.functions : null);
307
+ setAdvisories(advisorySnapshot.status === "fulfilled" ? advisorySnapshot.value.advisories : null);
308
+ setIndexHits(snapshot.status === "fulfilled" ? snapshot.value.indexHits ?? null : null);
309
+ let tableNames = [];
310
+ try {
311
+ const tables = await client.query(LIST_TABLES, {}, callOptions(shard));
312
+ tableNames = tables.map((table_0) => table_0.name);
313
+ const indexResults = await Promise.allSettled(tables.map(async (table_1) => [table_1.name, await client.query(LIST_TABLE_INDEXES, {
314
+ table: table_1.name
315
+ }, callOptions(shard))]));
316
+ const declared = [];
317
+ for (const result of indexResults) {
318
+ if (result.status === "fulfilled") {
319
+ const [name, payload] = result.value;
320
+ declared.push(...declaredIndexesFor(name, payload.indexes));
321
+ }
322
+ }
323
+ setDeclaredIndexes(declared);
324
+ } catch {
325
+ setDeclaredIndexes(null);
326
+ }
327
+ try {
328
+ const traffic = await fanShardTraffic(tableNames[0] ?? "");
329
+ const byShard = /* @__PURE__ */ new Map();
330
+ for (const entry of traffic.shards) {
331
+ if (!byShard.has(entry.shardKey)) {
332
+ byShard.set(entry.shardKey, {
333
+ requests: entry.requests,
334
+ shardKey: entry.shardKey
335
+ });
336
+ }
337
+ }
338
+ setShardTraffic([...byShard.values()]);
339
+ } catch {
340
+ setShardTraffic(null);
341
+ }
342
+ };
343
+ useLiveShardSeed(shardKey, refresh);
344
+ useEffect(() => {
345
+ const onVisible = () => {
346
+ if (document.visibilityState === "visible") {
347
+ fireAndForget(refresh(shardKey));
348
+ }
349
+ };
350
+ document.addEventListener("visibilitychange", onVisible);
351
+ return () => {
352
+ document.removeEventListener("visibilitychange", onVisible);
353
+ };
354
+ }, [refresh, shardKey]);
355
+ const jumpToSchemaIndex = (table_2) => {
356
+ fireAndForget(navigate({
357
+ search: {
358
+ table: table_2
359
+ },
360
+ to: "/schema"
361
+ }));
362
+ };
363
+ const insights = deriveInsights(metrics, functions);
364
+ const missingIndexTables = new Set(insights.filter((insight) => insight.kind === "missing-index").flatMap((insight_0) => insight_0.tables ?? []));
365
+ const runtimeRows = deriveRuntimeAdvisories({
366
+ declaredIndexes: declaredIndexes ?? [],
367
+ functions,
368
+ indexHits,
369
+ shardTraffic,
370
+ suppressHotScanTables: missingIndexTables
371
+ });
372
+ const rows = useMemo(() => {
373
+ const insightRows = insights.map((insight_1) => {
374
+ const tables_0 = insight_1.kind === "missing-index" ? insight_1.tables ?? [] : [];
375
+ return {
376
+ action: tables_0.length > 0 ? tables_0.map((table_3) => /* @__PURE__ */ jsxDEV(AddIndexButton, {
377
+ onJump: jumpToSchemaIndex,
378
+ table: table_3
379
+ }, table_3, false)) : void 0,
380
+ description: insight_1.message === void 0 ? insightDetail(t, insight_1) : `${insightDetail(t, insight_1)} — ${insight_1.message}`,
381
+ entity: insight_1.fn,
382
+ issueType: insightTitle(t, insight_1),
383
+ key: `${insight_1.kind}:${insight_1.fn ?? ""}`,
384
+ level: insight_1.severity
385
+ };
386
+ });
387
+ const indexAdvisoryLints = /* @__PURE__ */ new Set(["unindexed_foreign_key", "unindexed_relation_target"]);
388
+ const staticRows = (advisories ?? []).map((finding) => {
389
+ const base = advisoryRow(finding);
390
+ if (indexAdvisoryLints.has(finding.name) && hasIndexMetadata(finding.metadata)) {
391
+ const {
392
+ table: table_4,
393
+ suggestedIndex
394
+ } = finding.metadata;
395
+ const testId = `in-apply-index-${table_4}-${suggestedIndex.name}`;
396
+ return {
397
+ ...base,
398
+ action: /* @__PURE__ */ jsxDEV(ApplyIndexButton, {
399
+ fields: suggestedIndex.fields,
400
+ indexName: suggestedIndex.name,
401
+ table: table_4,
402
+ testId
403
+ }, void 0, false)
404
+ };
405
+ }
406
+ return base;
407
+ });
408
+ return [...staticRows, ...runtimeRows, ...insightRows];
409
+ }, [advisories, insights, jumpToSchemaIndex, runtimeRows, t]);
410
+ const toolbar = /* @__PURE__ */ jsxDEV(ShardInput, {
411
+ onChange: setShardKey,
412
+ testId: "in-shard-input",
413
+ value: shardKey
414
+ }, void 0, false);
415
+ return /* @__PURE__ */ jsxDEV(AdvisorView, {
416
+ error,
417
+ rows,
418
+ testId: "lunora-insights",
419
+ toolbar
420
+ }, void 0, false);
421
+ };
422
+
423
+ export { InsightsPanel };