@lunora/studio 1.0.0-alpha.2 → 1.0.0-alpha.21

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 (60) hide show
  1. package/__assets__/package-og.svg +1 -1
  2. package/dist/index.d.ts +23 -17
  3. package/dist/index.js +34 -34
  4. package/dist/mount.d.ts +1 -1
  5. package/dist/mount.js +1 -1
  6. package/dist/packem_shared/{ADMIN_FUNCTION_PREFIX-DmBqMZ-z.js → ADMIN_FUNCTIONS-CdaxLGkE.js} +3 -0
  7. package/dist/packem_shared/{ApiDocsPanel-DpRjJhG5.js → ApiDocsPanel-BgrN4kml.js} +3 -3
  8. package/dist/packem_shared/{ApiReferencePanel-DMIUp-kK.js → ApiReferencePanel-1vubqG5s.js} +2 -2
  9. package/dist/packem_shared/{ApiTab-DURGU15e.js → ApiTab-CHkX49o_.js} +4 -4
  10. package/dist/packem_shared/{AuditPanel-BC59Nhst.js → AuditPanel-CP9HM9nq.js} +19 -31
  11. package/dist/packem_shared/{CommandPalette-Dx_CoB9i.js → CommandPalette-BtgkBzZI.js} +2 -2
  12. package/dist/packem_shared/{ConfirmButton-WQVUoGFb.js → ConfirmButton-C2eVi2Qv.js} +1 -1
  13. package/dist/packem_shared/{ConnectionBadge-Bxagrip8.js → ConnectionBadge-B2DDM2Tn.js} +1 -1
  14. package/dist/packem_shared/{createStudioI18n-CgvlmDkN.js → DEFAULT_LOCALE-CiX4W7Kr.js} +1 -1
  15. package/dist/packem_shared/{DataBrowser-Coz6jJE6.js → DataBrowser-Bb1gTQ6c.js} +182 -249
  16. package/dist/packem_shared/{ExportImportPanel-WO34fJxy.js → ExportImportPanel-Dj87tRa5.js} +9 -5
  17. package/dist/packem_shared/{FileBrowser-Zcr-Qgxo.js → FileBrowser-CWvxp_is.js} +7 -7
  18. package/dist/packem_shared/{FunctionRunner-j0Rd5m9t.js → FunctionRunner-DlWApMCH.js} +4 -4
  19. package/dist/packem_shared/{FunctionStatsPanel-DboBl-XL.js → FunctionStatsPanel-CuO2Tmw-.js} +141 -198
  20. package/dist/packem_shared/{GlobalDataBrowser-9MhPEfgN.js → GlobalDataBrowser-3X9uRfdO.js} +37 -66
  21. package/dist/packem_shared/{HealthPanel-DOIgbUtx.js → HealthPanel-Dj0GRo5P.js} +31 -23
  22. package/dist/packem_shared/{HomePanel-bdOCNA-p.js → HomePanel-BewOCuag.js} +450 -389
  23. package/dist/packem_shared/{InsightsPanel-DaZPnSgt.js → InsightsPanel-y_Q-xdPY.js} +104 -66
  24. package/dist/packem_shared/{LogsPanel-CWdqAGpQ.js → LogsPanel-C7crgYoS.js} +35 -47
  25. package/dist/packem_shared/{MailPanel-D_EGtDnS.js → MailPanel-PILidruz.js} +31 -38
  26. package/dist/packem_shared/{MetricsPanel-E4Gv6wTO.js → MetricsPanel-DioILzgf.js} +34 -48
  27. package/dist/packem_shared/{MigrationsPanel-DQdPY9io.js → MigrationsPanel-DkIcRoE5.js} +31 -38
  28. package/dist/packem_shared/{OpenRpcReferencePanel-j2p3HB0s.js → OpenRpcReferencePanel-vbcYbv3V.js} +2 -2
  29. package/dist/packem_shared/{PitrPanel-BbBkQR6t.js → PitrPanel-yKJ1JPPk.js} +4 -4
  30. package/dist/packem_shared/{ScheduledJobs-Ok1CYYwI.js → ScheduledJobs-BWNwIN3I.js} +164 -61
  31. package/dist/packem_shared/{SchemaViewer-D8XGnp-X.js → SchemaViewer-CNr8Wame.js} +4 -4
  32. package/dist/packem_shared/{SecurityAdvisorPanel-Cdm2IxLW.js → SecurityAdvisorPanel-BBzv7L3c.js} +44 -31
  33. package/dist/packem_shared/SettingsPanel-BKSKl8tl.js +390 -0
  34. package/dist/packem_shared/{ShardInput-DNCsT1KW.js → ShardInput-gx_DZXXn.js} +1 -1
  35. package/dist/packem_shared/SqlEditorPanel-CkemCQvy.js +13 -0
  36. package/dist/packem_shared/Studio-QkOjc2NS.js +33 -0
  37. package/dist/packem_shared/{StudioApp-dvywkJ8I.js → StudioApp-TEElspKL.js} +278 -4
  38. package/dist/packem_shared/{StudioI18nProvider-Dcajsznk.js → StudioI18nProvider-B-tDCZIi.js} +1 -1
  39. package/dist/packem_shared/TableEditor-JS3sXYgS.js +11 -0
  40. package/dist/packem_shared/{advisor-view-DBlzJi6C.js → advisor-view-CGcfMiPq.js} +1 -1
  41. package/dist/packem_shared/{app.d-CCmwDEVs.d.ts → app.d-Cnij4Zp5.d.ts} +1 -1
  42. package/dist/packem_shared/{cf-links-BZfRdxSE.js → cf-links-2cktDedA.js} +1 -1
  43. package/dist/packem_shared/{data-grid-CCh2Couo.js → data-grid-B5CaV3wS.js} +2 -2
  44. package/dist/packem_shared/{grid-features-DTjG6Sex.js → grid-features-CzUfqgsM.js} +3 -3
  45. package/dist/packem_shared/{internal-BBZYexre.js → internal-rXJEAhXS.js} +1 -1
  46. package/dist/packem_shared/{live-status-bPff1O7Y.js → live-status-FZ0psC1z.js} +1 -1
  47. package/dist/packem_shared/{reference-view-BCKIoai7.js → reference-view-B4E49Lip.js} +3 -3
  48. package/dist/packem_shared/{sql-editor-panel-CW2y2x9h.js → sql-editor-panel-CUJoYF1Z.js} +6 -6
  49. package/dist/packem_shared/{studio-BDVd7rIV.js → studio-c5Vf7kSh.js} +2897 -1668
  50. package/dist/packem_shared/{TableEditor-DIVDk3vT.js → table-editor-9fhJ4hMJ.js} +43 -7
  51. package/dist/packem_shared/{table-list-sidebar-aZHLq70w.js → table-list-sidebar-Bg7uzbvC.js} +2 -2
  52. package/dist/packem_shared/use-admin-query-BkbdNFNz.js +105 -0
  53. package/dist/standalone/studio.js +198 -176
  54. package/dist/styles.css +1 -1
  55. package/package.json +13 -12
  56. package/dist/packem_shared/SettingsPanel-D3WF2mBU.js +0 -176
  57. package/dist/packem_shared/SqlEditorPanel-BuQ7f2Hs.js +0 -13
  58. package/dist/packem_shared/Studio-D36od9Oz.js +0 -33
  59. package/dist/packem_shared/use-live-admin-D1h1Fzsd.js +0 -73
  60. package/dist/packem_shared/use-live-shard-seed-B74RYcOy.js +0 -76
@@ -1,16 +1,17 @@
1
1
  import { c } from 'react/compiler-runtime';
2
2
  import { useLunora } from '@lunora/react';
3
3
  import { useNavigate } from '@tanstack/react-router';
4
- import { useState, useEffect, useMemo } from 'react';
5
- import { ShardInput } from './ShardInput-DNCsT1KW.js';
4
+ import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
5
+ import { ShardInput } from './ShardInput-gx_DZXXn.js';
6
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';
7
+ import { u as useAdminQuery } from './use-admin-query-BkbdNFNz.js';
8
+ import useDebounced from './useDebounced-Dxncpg6z.js';
9
+ import { useT } from './DEFAULT_LOCALE-CiX4W7Kr.js';
10
+ import { ADMIN_FUNCTIONS } from './ADMIN_FUNCTIONS-CdaxLGkE.js';
11
+ import { f as fireAndForget, c as callOptions, a as adminRef } from './internal-rXJEAhXS.js';
10
12
  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';
13
+ import { a as advisoryRow, A as AdvisorView } from './advisor-view-CGcfMiPq.js';
14
+ import { ConfirmButton } from './ConfirmButton-C2eVi2Qv.js';
14
15
  import { jsxDEV } from 'react/jsx-dev-runtime';
15
16
  import { deriveInsights } from './DEFAULT_INSIGHT_THRESHOLDS-DjF0h-gA.js';
16
17
  import { runAdvisor, RUNTIME_LINTS } from '@lunora/advisor';
@@ -185,9 +186,6 @@ const deriveRuntimeAdvisories = (inputs) => {
185
186
  return visible.map((finding) => advisoryRow(finding));
186
187
  };
187
188
 
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
189
  const LIST_TABLES = adminRef(ADMIN_FUNCTIONS.listTables);
192
190
  const LIST_TABLE_INDEXES = adminRef(ADMIN_FUNCTIONS.listTableIndexes);
193
191
  const percent = (rate) => `${(rate * 100).toFixed(1)}%`;
@@ -286,46 +284,41 @@ const InsightsPanel = ({
286
284
  const navigate = useNavigate();
287
285
  const t = useT();
288
286
  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
287
  const [declaredIndexes, setDeclaredIndexes] = useState(null);
294
288
  const [shardTraffic, setShardTraffic] = useState(null);
295
- const [error, setError] = useState(null);
289
+ const debouncedShard = useDebounced(shardKey.trim(), 400);
290
+ const latestEnumeratedShard = useRef("");
291
+ const metricsQuery = useAdminQuery(ADMIN_FUNCTIONS.getMetrics, {}, {
292
+ live: true,
293
+ shardKey: debouncedShard
294
+ });
295
+ const functionsQuery = useAdminQuery(ADMIN_FUNCTIONS.getFunctionStats, {}, {
296
+ live: true,
297
+ shardKey: debouncedShard
298
+ });
299
+ const advisoriesQuery = useAdminQuery(ADMIN_FUNCTIONS.getAdvisories, {}, {
300
+ live: true,
301
+ shardKey: debouncedShard
302
+ });
303
+ const metrics = metricsQuery.data ?? null;
304
+ const functions = functionsQuery.data?.functions ?? null;
305
+ const advisories = advisoriesQuery.data?.advisories ?? null;
306
+ const indexHits = metricsQuery.data?.indexHits ?? null;
307
+ const error = metricsQuery.error !== null && functionsQuery.error !== null ? metricsQuery.error : null;
296
308
  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
- }
309
+ const loadShardTrafficFeed = async (tableNames, shard) => {
310
+ const seedTable = tableNames[0];
311
+ if (seedTable === void 0 || seedTable === "") {
312
+ if (latestEnumeratedShard.current === shard) {
313
+ setShardTraffic(null);
322
314
  }
323
- setDeclaredIndexes(declared);
324
- } catch {
325
- setDeclaredIndexes(null);
315
+ return;
326
316
  }
327
317
  try {
328
- const traffic = await fanShardTraffic(tableNames[0] ?? "");
318
+ const traffic = await fanShardTraffic(seedTable);
319
+ if (latestEnumeratedShard.current !== shard) {
320
+ return;
321
+ }
329
322
  const byShard = /* @__PURE__ */ new Map();
330
323
  for (const entry of traffic.shards) {
331
324
  if (!byShard.has(entry.shardKey)) {
@@ -337,31 +330,76 @@ const InsightsPanel = ({
337
330
  }
338
331
  setShardTraffic([...byShard.values()]);
339
332
  } catch {
340
- setShardTraffic(null);
333
+ if (latestEnumeratedShard.current === shard) {
334
+ setShardTraffic(null);
335
+ }
341
336
  }
342
337
  };
343
- useLiveShardSeed(shardKey, refresh);
338
+ const enumerateShard = useCallback(
339
+ async (shard) => {
340
+ latestEnumeratedShard.current = shard;
341
+ let tableNames = [];
342
+ try {
343
+ const tables = await client.query(LIST_TABLES, {}, callOptions(shard));
344
+ if (latestEnumeratedShard.current !== shard) {
345
+ return;
346
+ }
347
+ recordShard(shard);
348
+ tableNames = tables.map((table) => table.name);
349
+ const indexResults = await Promise.allSettled(tables.map(async (table) => [table.name, await client.query(LIST_TABLE_INDEXES, {
350
+ table: table.name
351
+ }, callOptions(shard))]));
352
+ if (latestEnumeratedShard.current !== shard) {
353
+ return;
354
+ }
355
+ const declared = [];
356
+ for (const result of indexResults) {
357
+ if (result.status === "fulfilled") {
358
+ const [name, payload] = result.value;
359
+ declared.push(...declaredIndexesFor(name, payload.indexes));
360
+ }
361
+ }
362
+ setDeclaredIndexes(declared);
363
+ } catch {
364
+ if (latestEnumeratedShard.current === shard) {
365
+ setDeclaredIndexes(null);
366
+ }
367
+ }
368
+ await loadShardTrafficFeed(tableNames, shard);
369
+ },
370
+ // `fanShardTraffic`/`loadShardTrafficFeed` are render-fresh closures; the
371
+ // enumeration only needs to re-run on a shard change (or a manual visibility
372
+ // refresh), so they're intentionally excluded from the cache key.
373
+ // eslint-disable-next-line react-hooks/exhaustive-deps
374
+ [client]
375
+ );
376
+ useEffect(() => {
377
+ fireAndForget(enumerateShard(debouncedShard));
378
+ }, [debouncedShard, enumerateShard]);
344
379
  useEffect(() => {
345
380
  const onVisible = () => {
346
381
  if (document.visibilityState === "visible") {
347
- fireAndForget(refresh(shardKey));
382
+ metricsQuery.refetch();
383
+ functionsQuery.refetch();
384
+ advisoriesQuery.refetch();
385
+ fireAndForget(enumerateShard(debouncedShard));
348
386
  }
349
387
  };
350
388
  document.addEventListener("visibilitychange", onVisible);
351
389
  return () => {
352
390
  document.removeEventListener("visibilitychange", onVisible);
353
391
  };
354
- }, [refresh, shardKey]);
355
- const jumpToSchemaIndex = (table_2) => {
392
+ }, [advisoriesQuery, debouncedShard, enumerateShard, functionsQuery, metricsQuery]);
393
+ const jumpToSchemaIndex = useCallback((table) => {
356
394
  fireAndForget(navigate({
357
395
  search: {
358
- table: table_2
396
+ table
359
397
  },
360
398
  to: "/schema"
361
399
  }));
362
- };
400
+ }, [navigate]);
363
401
  const insights = deriveInsights(metrics, functions);
364
- const missingIndexTables = new Set(insights.filter((insight) => insight.kind === "missing-index").flatMap((insight_0) => insight_0.tables ?? []));
402
+ const missingIndexTables = new Set(insights.filter((insight) => insight.kind === "missing-index").flatMap((insight) => insight.tables ?? []));
365
403
  const runtimeRows = deriveRuntimeAdvisories({
366
404
  declaredIndexes: declaredIndexes ?? [],
367
405
  functions,
@@ -370,18 +408,18 @@ const InsightsPanel = ({
370
408
  suppressHotScanTables: missingIndexTables
371
409
  });
372
410
  const rows = useMemo(() => {
373
- const insightRows = insights.map((insight_1) => {
374
- const tables_0 = insight_1.kind === "missing-index" ? insight_1.tables ?? [] : [];
411
+ const insightRows = insights.map((insight) => {
412
+ const tables = insight.kind === "missing-index" ? insight.tables ?? [] : [];
375
413
  return {
376
- action: tables_0.length > 0 ? tables_0.map((table_3) => /* @__PURE__ */ jsxDEV(AddIndexButton, {
414
+ action: tables.length > 0 ? tables.map((table) => /* @__PURE__ */ jsxDEV(AddIndexButton, {
377
415
  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
416
+ table
417
+ }, table, false)) : void 0,
418
+ description: insight.message === void 0 ? insightDetail(t, insight) : `${insightDetail(t, insight)} — ${insight.message}`,
419
+ entity: insight.fn,
420
+ issueType: insightTitle(t, insight),
421
+ key: `${insight.kind}:${insight.fn ?? ""}`,
422
+ level: insight.severity
385
423
  };
386
424
  });
387
425
  const indexAdvisoryLints = /* @__PURE__ */ new Set(["unindexed_foreign_key", "unindexed_relation_target"]);
@@ -389,16 +427,16 @@ const InsightsPanel = ({
389
427
  const base = advisoryRow(finding);
390
428
  if (indexAdvisoryLints.has(finding.name) && hasIndexMetadata(finding.metadata)) {
391
429
  const {
392
- table: table_4,
430
+ table,
393
431
  suggestedIndex
394
432
  } = finding.metadata;
395
- const testId = `in-apply-index-${table_4}-${suggestedIndex.name}`;
433
+ const testId = `in-apply-index-${table}-${suggestedIndex.name}`;
396
434
  return {
397
435
  ...base,
398
436
  action: /* @__PURE__ */ jsxDEV(ApplyIndexButton, {
399
437
  fields: suggestedIndex.fields,
400
438
  indexName: suggestedIndex.name,
401
- table: table_4,
439
+ table,
402
440
  testId
403
441
  }, void 0, false)
404
442
  };
@@ -1,21 +1,19 @@
1
1
  import { c } from 'react/compiler-runtime';
2
- import { useLunora } from '@lunora/react';
3
2
  import { useVirtualizer } from '@tanstack/react-virtual';
4
- import { useState, useMemo, useRef } from 'react';
5
- import { L as LiveError } from './live-status-bPff1O7Y.js';
6
- import { ShardInput } from './ShardInput-DNCsT1KW.js';
3
+ import { useState, useEffect, useMemo, useRef } from 'react';
4
+ import { L as LiveError } from './live-status-FZ0psC1z.js';
5
+ import { ShardInput } from './ShardInput-gx_DZXXn.js';
7
6
  import { B as Badge } from './badge-B2PKA1-5.js';
8
7
  import { E as EmptyState } from './empty-state-DY_oe0k6.js';
9
8
  import { I as Input } from './input-XH4r1Pt1.js';
10
- import { u as useLiveAdmin } from './use-live-admin-D1h1Fzsd.js';
11
- import { useT } from './createStudioI18n-CgvlmDkN.js';
12
- import { ADMIN_FUNCTIONS } from './ADMIN_FUNCTION_PREFIX-DmBqMZ-z.js';
13
- import { a as CLOUDFLARE_OBSERVABILITY_URL } from './cf-links-BZfRdxSE.js';
14
- import { c as callOptions, e as errorMessage, a as adminRef } from './internal-BBZYexre.js';
9
+ import { u as useAdminQuery } from './use-admin-query-BkbdNFNz.js';
10
+ import useDebounced from './useDebounced-Dxncpg6z.js';
11
+ import { useT } from './DEFAULT_LOCALE-CiX4W7Kr.js';
12
+ import { ADMIN_FUNCTIONS } from './ADMIN_FUNCTIONS-CdaxLGkE.js';
13
+ import { C as CLOUDFLARE_OBSERVABILITY_URL } from './cf-links-2cktDedA.js';
15
14
  import { r as recordShard } from './shard-history-DyebH1R5.js';
16
15
  import { c as cn } from './utils-B05Dmz_H.js';
17
16
  import { f as flooredRectObserver } from './virtual-rect-CVMUskSm.js';
18
- import { u as useLiveShardSeed } from './use-live-shard-seed-B74RYcOy.js';
19
17
  import { jsxDEV } from 'react/jsx-dev-runtime';
20
18
 
21
19
  const SCROLL_HEIGHT = 400;
@@ -316,8 +314,6 @@ const RequestRow = (t0) => {
316
314
  }
317
315
  return t17;
318
316
  };
319
- const GET_LOGS = adminRef(ADMIN_FUNCTIONS.getLogs);
320
- const GET_REQUEST_LOG = adminRef(ADMIN_FUNCTIONS.getRequestLog);
321
317
  const entriesOf = (result) => {
322
318
  const {
323
319
  entries
@@ -488,13 +484,9 @@ const SummaryBucketRow = (t0) => {
488
484
  const LogsPanel = ({
489
485
  initialShardKey
490
486
  }) => {
491
- const client = useLunora();
492
487
  const t = useT();
493
488
  const [view, setView] = useState("requests");
494
489
  const [shardKey, setShardKey] = useState(initialShardKey ?? "");
495
- const [entries, setEntries] = useState([]);
496
- const [requests, setRequests] = useState([]);
497
- const [error, setError] = useState(null);
498
490
  const [search, setSearch] = useState("");
499
491
  const [levelFilter, setLevelFilter] = useState(() => /* @__PURE__ */ new Set());
500
492
  const [pathFilter, setPathFilter] = useState("");
@@ -504,43 +496,36 @@ const LogsPanel = ({
504
496
  const [userIdFilter, setUserIdFilter] = useState("");
505
497
  const [tableFilter, setTableFilter] = useState("");
506
498
  const [outcomeFilter, setOutcomeFilter] = useState("all");
507
- const [liveError, setLiveError] = useState(void 0);
499
+ const debouncedShard = useDebounced(shardKey.trim(), 400);
508
500
  const requestQuery = buildRequestQuery({
509
501
  functionPathPrefix: pathPrefix,
510
502
  outcome: outcomeFilter,
511
503
  tableTouched: tableFilter,
512
504
  userId: userIdFilter
513
505
  });
514
- const refresh = async (shard) => {
515
- setError(null);
516
- try {
517
- if (view === "requests") {
518
- const result = await client.query(GET_REQUEST_LOG, requestQuery, callOptions(shard));
519
- recordShard(shard);
520
- setRequests(entriesOf(result));
521
- } else {
522
- const result_0 = await client.query(GET_LOGS, {}, callOptions(shard));
523
- recordShard(shard);
524
- setEntries(entriesOf(result_0));
525
- }
526
- } catch (error_) {
527
- setEntries([]);
528
- setRequests([]);
529
- setError(errorMessage(error_));
530
- throw error_;
506
+ const requestsQuery = useAdminQuery(ADMIN_FUNCTIONS.getRequestLog, requestQuery, {
507
+ enabled: view === "requests",
508
+ live: true,
509
+ shardKey: debouncedShard
510
+ });
511
+ const errorsQuery = useAdminQuery(ADMIN_FUNCTIONS.getLogs, {}, {
512
+ enabled: view === "errors",
513
+ live: true,
514
+ shardKey: debouncedShard
515
+ });
516
+ const activeQuery = view === "requests" ? requestsQuery : errorsQuery;
517
+ const {
518
+ error,
519
+ isLoading: activeLoading,
520
+ liveError
521
+ } = activeQuery;
522
+ const entries = entriesOf(errorsQuery.data);
523
+ const requests = entriesOf(requestsQuery.data);
524
+ useEffect(() => {
525
+ if (activeQuery.data !== void 0) {
526
+ recordShard(debouncedShard);
531
527
  }
532
- };
533
- const committedShard = useLiveShardSeed(shardKey, refresh, [view, requestQuery]);
534
- useLiveAdmin(ADMIN_FUNCTIONS.getRequestLog, requestQuery, committedShard ?? "", (result_1) => {
535
- setError(null);
536
- setLiveError(void 0);
537
- setRequests(entriesOf(result_1));
538
- }, view === "requests" && committedShard !== void 0, setLiveError);
539
- useLiveAdmin(ADMIN_FUNCTIONS.getLogs, {}, committedShard ?? "", (result_2) => {
540
- setError(null);
541
- setLiveError(void 0);
542
- setEntries(entriesOf(result_2));
543
- }, view === "errors" && committedShard !== void 0, setLiveError);
528
+ }, [activeQuery.data, debouncedShard]);
544
529
  const filtered = filterLogs(entries, {
545
530
  levels: levelFilter,
546
531
  now: Date.now(),
@@ -556,10 +541,13 @@ const LogsPanel = ({
556
541
  return "error";
557
542
  }
558
543
  if (activeCount === 0) {
544
+ if (activeLoading) {
545
+ return summaryVisible ? "summary" : "list";
546
+ }
559
547
  return "empty";
560
548
  }
561
549
  return summaryVisible ? "summary" : "list";
562
- }, [activeCount, error, summaryVisible]);
550
+ }, [activeCount, activeLoading, error, summaryVisible]);
563
551
  const scrollRef = useRef(null);
564
552
  const virtualizer = useVirtualizer({
565
553
  count: activeCount,
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { useLunora } from '@lunora/react';
3
- import { useState, useEffect, useMemo } from 'react';
3
+ import { useState, useMemo } from 'react';
4
4
  import { B as Badge } from './badge-B2PKA1-5.js';
5
5
  import { B as Button } from './button-BhsN2uZH.js';
6
6
  import { E as EmptyState } from './empty-state-DY_oe0k6.js';
@@ -10,10 +10,11 @@ import { ScrollArea as ScrollArea$1 } from '@base-ui/react/scroll-area';
10
10
  import { c as cn } from './utils-B05Dmz_H.js';
11
11
  import { jsxDEV, Fragment } from 'react/jsx-dev-runtime';
12
12
  import { Separator as Separator$1 } from '@base-ui/react/separator';
13
+ import { u as useAdminQuery } from './use-admin-query-BkbdNFNz.js';
13
14
  import { useAutoRefresh } from './DEFAULT_AUTO_REFRESH_MS-Vxwaxx51.js';
14
- import { useT } from './createStudioI18n-CgvlmDkN.js';
15
- import { ADMIN_FUNCTIONS } from './ADMIN_FUNCTION_PREFIX-DmBqMZ-z.js';
16
- import { f as fireAndForget, d as formatTimestamp, c as callOptions, e as errorMessage, b as copyToClipboard, a as adminRef } from './internal-BBZYexre.js';
15
+ import { useT } from './DEFAULT_LOCALE-CiX4W7Kr.js';
16
+ import { ADMIN_FUNCTIONS } from './ADMIN_FUNCTIONS-CdaxLGkE.js';
17
+ import { b as formatTimestamp, f as fireAndForget, g as copyToClipboard, c as callOptions, e as errorMessage, a as adminRef } from './internal-rXJEAhXS.js';
17
18
 
18
19
  function Separator({
19
20
  className,
@@ -114,7 +115,6 @@ function ScrollBar({
114
115
  }, void 0, false);
115
116
  }
116
117
 
117
- const GET_CAPTURED_MAIL = adminRef(ADMIN_FUNCTIONS.getCapturedMail);
118
118
  const CLEAR_CAPTURED_MAIL = adminRef(ADMIN_FUNCTIONS.clearCapturedMail);
119
119
  const SEND_TEST_MAIL = adminRef(ADMIN_FUNCTIONS.sendTestMail);
120
120
  const LINK_PATTERN = /https?:\/\/[^\s"'<>)]+/i;
@@ -144,49 +144,42 @@ const MailPanel = ({
144
144
  }) => {
145
145
  const client = useLunora();
146
146
  const t = useT();
147
- const [entries, setEntries] = useState([]);
148
- const [error, setError] = useState(null);
147
+ const {
148
+ data,
149
+ error: readError,
150
+ isLoading,
151
+ refetch
152
+ } = useAdminQuery(ADMIN_FUNCTIONS.getCapturedMail, {
153
+ limit
154
+ });
155
+ const [actionError, setActionError] = useState(null);
149
156
  const [selectedId, setSelectedId] = useState(null);
150
157
  const [tab, setTab] = useState("html");
151
158
  const [filter, setFilter] = useState("");
152
- const refresh = async () => {
153
- setError(null);
154
- try {
155
- const next = await client.query(GET_CAPTURED_MAIL, {
156
- limit
157
- }, callOptions(""));
158
- setEntries(next.entries);
159
- } catch (error_) {
160
- setEntries([]);
161
- setError(errorMessage(error_));
162
- }
163
- };
159
+ const entries = useMemo(() => data?.entries ?? [], [data]);
160
+ const error = readError ?? actionError;
164
161
  const clearInbox = async () => {
165
- setError(null);
162
+ setActionError(null);
166
163
  try {
167
164
  await client.query(CLEAR_CAPTURED_MAIL, {}, callOptions(""));
168
165
  setSelectedId(null);
169
- await refresh();
170
- } catch (error__0) {
171
- setError(errorMessage(error__0));
166
+ refetch();
167
+ } catch (error_) {
168
+ setActionError(errorMessage(error_));
172
169
  }
173
170
  };
174
171
  const sendTest = async () => {
175
- setError(null);
172
+ setActionError(null);
176
173
  try {
177
174
  await client.query(SEND_TEST_MAIL, {}, callOptions(""));
178
- await refresh();
179
- } catch (error__1) {
180
- setError(errorMessage(error__1));
175
+ refetch();
176
+ } catch (error__0) {
177
+ setActionError(errorMessage(error__0));
181
178
  }
182
179
  };
183
- useEffect(() => {
184
- fireAndForget(refresh());
185
- }, [refresh]);
186
- const reload = () => {
187
- fireAndForget(refresh());
188
- };
189
- useAutoRefresh(reload, true);
180
+ useAutoRefresh(() => {
181
+ refetch();
182
+ }, true);
190
183
  const onClear = () => {
191
184
  fireAndForget(clearInbox());
192
185
  };
@@ -205,9 +198,9 @@ const MailPanel = ({
205
198
  }
206
199
  };
207
200
  const onSelectTab = (event_1) => {
208
- const next_0 = event_1.currentTarget.dataset["tab"];
209
- if (next_0 === "headers" || next_0 === "html" || next_0 === "text") {
210
- setTab(next_0);
201
+ const next = event_1.currentTarget.dataset["tab"];
202
+ if (next === "headers" || next === "html" || next === "text") {
203
+ setTab(next);
211
204
  }
212
205
  };
213
206
  const tabTitle = (value) => {
@@ -276,7 +269,7 @@ const MailPanel = ({
276
269
  "data-testid": "mail-error",
277
270
  role: "alert",
278
271
  children: error
279
- }, void 0, false), error === null && entries.length === 0 && /* @__PURE__ */ jsxDEV(EmptyState, {
272
+ }, void 0, false), error === null && !isLoading && entries.length === 0 && /* @__PURE__ */ jsxDEV(EmptyState, {
280
273
  description: t("Email your app sends in dev is captured here — nothing is delivered."),
281
274
  icon: /* @__PURE__ */ jsxDEV("svg", {
282
275
  "aria-hidden": "true",
@@ -2,18 +2,18 @@
2
2
  import { c } from 'react/compiler-runtime';
3
3
  import { useLunora } from '@lunora/react';
4
4
  import { useState, useRef, useEffect, useMemo } from 'react';
5
- import { L as LiveError } from './live-status-bPff1O7Y.js';
6
- import { ShardInput } from './ShardInput-DNCsT1KW.js';
5
+ import { L as LiveError } from './live-status-FZ0psC1z.js';
6
+ import { ShardInput } from './ShardInput-gx_DZXXn.js';
7
7
  import { B as Button } from './button-BhsN2uZH.js';
8
8
  import { a as CardContent, C as Card, b as CardHeader, c as CardTitle, d as CardDescription } from './card-DURq3ElK.js';
9
9
  import { T as Table, a as TableHeader, b as TableRow, c as TableHead, d as TableBody, e as TableCell } from './table-_RzNvy3R.js';
10
- import { u as useLiveAdmin } from './use-live-admin-D1h1Fzsd.js';
11
- import { useT } from './createStudioI18n-CgvlmDkN.js';
12
- import { ADMIN_FUNCTIONS } from './ADMIN_FUNCTION_PREFIX-DmBqMZ-z.js';
13
- import { c as CLOUDFLARE_DURABLE_OBJECTS_URL } from './cf-links-BZfRdxSE.js';
14
- import { g as formatBytes, f as fireAndForget, c as callOptions, e as errorMessage, a as adminRef } from './internal-BBZYexre.js';
15
- import { l as loadRecentShards, r as recordShard } from './shard-history-DyebH1R5.js';
16
- import { u as useLiveShardSeed } from './use-live-shard-seed-B74RYcOy.js';
10
+ import { u as useAdminQuery } from './use-admin-query-BkbdNFNz.js';
11
+ import useDebounced from './useDebounced-Dxncpg6z.js';
12
+ import { useT } from './DEFAULT_LOCALE-CiX4W7Kr.js';
13
+ import { ADMIN_FUNCTIONS } from './ADMIN_FUNCTIONS-CdaxLGkE.js';
14
+ import { c as CLOUDFLARE_DURABLE_OBJECTS_URL } from './cf-links-2cktDedA.js';
15
+ import { d as formatBytes, f as fireAndForget, c as callOptions, e as errorMessage, a as adminRef } from './internal-rXJEAhXS.js';
16
+ import { r as recordShard, l as loadRecentShards } from './shard-history-DyebH1R5.js';
17
17
  import { aggregateMetrics, computeLatencyPercentiles, enrichQueryStats, shardsToAggregate } from './aggregateMetrics-D4nUHEKU.js';
18
18
  import { B as Badge } from './badge-B2PKA1-5.js';
19
19
  import { Select as Select$1 } from '@base-ui/react/select';
@@ -1205,6 +1205,7 @@ const formatDuration = (ms) => {
1205
1205
  }
1206
1206
  return `${seconds.toString()}s`;
1207
1207
  };
1208
+ const resolveTab = (hasQueryStats, activeTab) => hasQueryStats ? activeTab : "overview";
1208
1209
  const hitRate = (hits, misses) => {
1209
1210
  const total = hits + misses;
1210
1211
  return total === 0 ? "—" : `${(hits / total * 100).toFixed(1)}%`;
@@ -1297,24 +1298,33 @@ const MetricsPanel = ({
1297
1298
  const client = useLunora();
1298
1299
  const t = useT();
1299
1300
  const [shardKey, setShardKey] = useState(initialShardKey ?? "");
1300
- const [metrics, setMetrics] = useState(null);
1301
- const [error, setError] = useState(null);
1302
1301
  const [activeTab, setActiveTab] = useState("overview");
1303
- const [liveError, setLiveError] = useState(void 0);
1304
1302
  const [history, setHistory] = useState([]);
1305
- const mountedRef = useRef(true);
1306
1303
  const lastRequestsRef = useRef(null);
1307
1304
  const lastShardRef = useRef(null);
1305
+ const mountedRef = useRef(true);
1308
1306
  useEffect(() => {
1309
1307
  mountedRef.current = true;
1310
1308
  return () => {
1311
1309
  mountedRef.current = false;
1312
1310
  };
1313
1311
  }, []);
1314
- const applySample = (next) => {
1315
- setError(null);
1316
- setLiveError(void 0);
1317
- setMetrics(next);
1312
+ const debouncedShard = useDebounced(shardKey.trim(), 400);
1313
+ const {
1314
+ data,
1315
+ error,
1316
+ liveError
1317
+ } = useAdminQuery(ADMIN_FUNCTIONS.getMetrics, {}, {
1318
+ live: true,
1319
+ shardKey: debouncedShard
1320
+ });
1321
+ const metrics = data ?? null;
1322
+ useEffect(() => {
1323
+ if (data === void 0) {
1324
+ return;
1325
+ }
1326
+ recordShard(debouncedShard);
1327
+ const next = data;
1318
1328
  const previous = lastRequestsRef.current;
1319
1329
  const shardChanged = lastShardRef.current !== null && lastShardRef.current !== next.shard;
1320
1330
  lastShardRef.current = next.shard;
@@ -1326,32 +1336,7 @@ const MetricsPanel = ({
1326
1336
  if (previous !== null && next.requests >= previous) {
1327
1337
  setHistory((prior) => [...prior, next.requests - previous].slice(-30));
1328
1338
  }
1329
- };
1330
- const refresh = async (shard) => {
1331
- try {
1332
- const next_0 = await client.query(GET_METRICS, {}, callOptions(shard));
1333
- recordShard(shard);
1334
- if (mountedRef.current) {
1335
- applySample(next_0);
1336
- }
1337
- } catch (error_) {
1338
- if (mountedRef.current) {
1339
- setMetrics(null);
1340
- setError(errorMessage(error_));
1341
- }
1342
- throw error_;
1343
- }
1344
- };
1345
- const committedShard = useLiveShardSeed(shardKey, refresh);
1346
- useLiveAdmin(ADMIN_FUNCTIONS.getMetrics, {}, committedShard ?? "", (next_1) => {
1347
- if (mountedRef.current) {
1348
- applySample(next_1);
1349
- }
1350
- }, committedShard !== void 0, (message) => {
1351
- if (mountedRef.current) {
1352
- setLiveError(message);
1353
- }
1354
- });
1339
+ }, [data, debouncedShard]);
1355
1340
  const [shardResults, setShardResults] = useState(null);
1356
1341
  const [aggregating, setAggregating] = useState(false);
1357
1342
  const aggregateAll = async () => {
@@ -1412,6 +1397,7 @@ const MetricsPanel = ({
1412
1397
  }
1413
1398
  return enrichQueryStats(snapQs);
1414
1399
  }, [metrics]);
1400
+ const effectiveTab = resolveTab(queryStats !== void 0, activeTab);
1415
1401
  const runAggregate = () => {
1416
1402
  fireAndForget(aggregateAll());
1417
1403
  };
@@ -1468,13 +1454,13 @@ const MetricsPanel = ({
1468
1454
  className: "flex gap-2 border-b",
1469
1455
  "data-testid": "mt-tabs",
1470
1456
  children: [/* @__PURE__ */ jsxDEV("button", {
1471
- className: `pb-2 text-sm font-medium transition-colors ${activeTab === "overview" ? "border-b-2 border-primary text-foreground" : "text-muted-foreground hover:text-foreground"}`,
1457
+ className: `pb-2 text-sm font-medium transition-colors ${effectiveTab === "overview" ? "border-b-2 border-primary text-foreground" : "text-muted-foreground hover:text-foreground"}`,
1472
1458
  "data-testid": "mt-tab-overview",
1473
1459
  onClick: switchToOverview,
1474
1460
  type: "button",
1475
1461
  children: t("Overview")
1476
1462
  }, void 0, false), /* @__PURE__ */ jsxDEV("button", {
1477
- className: `pb-2 text-sm font-medium transition-colors ${activeTab === "query-insights" ? "border-b-2 border-primary text-foreground" : "text-muted-foreground hover:text-foreground"}`,
1463
+ className: `pb-2 text-sm font-medium transition-colors ${effectiveTab === "query-insights" ? "border-b-2 border-primary text-foreground" : "text-muted-foreground hover:text-foreground"}`,
1478
1464
  "data-testid": "mt-tab-query-insights",
1479
1465
  onClick: switchToQueryInsights,
1480
1466
  type: "button",
@@ -1483,9 +1469,9 @@ const MetricsPanel = ({
1483
1469
  children: queryStats.length
1484
1470
  }, void 0, false)]
1485
1471
  }, void 0, true)]
1486
- }, void 0, true), activeTab === "query-insights" && queryStats !== void 0 && /* @__PURE__ */ jsxDEV(QueryInsights, {
1472
+ }, void 0, true), effectiveTab === "query-insights" && queryStats !== void 0 && /* @__PURE__ */ jsxDEV(QueryInsights, {
1487
1473
  queryStats
1488
- }, void 0, false), activeTab === "overview" && metrics !== null && /* @__PURE__ */ jsxDEV("dl", {
1474
+ }, void 0, false), effectiveTab === "overview" && metrics !== null && /* @__PURE__ */ jsxDEV("dl", {
1489
1475
  className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
1490
1476
  "data-testid": "mt-stats",
1491
1477
  children: [/* @__PURE__ */ jsxDEV(StatCard, {
@@ -1542,7 +1528,7 @@ const MetricsPanel = ({
1542
1528
  rate: hitRate(metrics.cache.hits, metrics.cache.misses)
1543
1529
  })
1544
1530
  }, void 0, false)]
1545
- }, void 0, true), activeTab === "overview" && metrics === null && null, activeTab === "overview" && aggregate !== null && shardResults !== null && /* @__PURE__ */ jsxDEV("div", {
1531
+ }, void 0, true), effectiveTab === "overview" && metrics === null && null, effectiveTab === "overview" && aggregate !== null && shardResults !== null && /* @__PURE__ */ jsxDEV("div", {
1546
1532
  className: "flex flex-col gap-4",
1547
1533
  "data-testid": "mt-aggregate-view",
1548
1534
  children: [/* @__PURE__ */ jsxDEV("dl", {