@rstest/browser 0.8.3 → 0.8.4

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.
@@ -44,6 +44,19 @@ type RsbuildDevServer = rsbuild.RsbuildDevServer;
44
44
  type RsbuildInstance = rsbuild.RsbuildInstance;
45
45
 
46
46
  const __dirname = dirname(fileURLToPath(import.meta.url));
47
+ const OPTIONS_PLACEHOLDER = '__RSTEST_OPTIONS_PLACEHOLDER__';
48
+
49
+ /**
50
+ * Serialize JSON for inline <script> injection.
51
+ * Escapes '<' to prevent accidental </script> break-out.
52
+ * Escapes U+2028/U+2029 to keep script parsing safe.
53
+ */
54
+ const serializeForInlineScript = (value: unknown): string => {
55
+ return JSON.stringify(value)
56
+ .replace(/</g, '\\u003c')
57
+ .replace(/\u2028/g, '\\u2028')
58
+ .replace(/\u2029/g, '\\u2029');
59
+ };
47
60
 
48
61
  // ============================================================================
49
62
  // Type Definitions
@@ -716,6 +729,21 @@ const htmlTemplate = `<!DOCTYPE html>
716
729
  </html>
717
730
  `;
718
731
 
732
+ const fallbackSchedulerHtmlTemplate = `<!DOCTYPE html>
733
+ <html lang="en">
734
+ <head>
735
+ <meta charset="UTF-8" />
736
+ <title>Rstest Browser Scheduler</title>
737
+ <script>
738
+ window.__RSTEST_BROWSER_OPTIONS__ = ${OPTIONS_PLACEHOLDER};
739
+ </script>
740
+ </head>
741
+ <body>
742
+ <script type="module" src="/container-static/js/scheduler.js"></script>
743
+ </body>
744
+ </html>
745
+ `;
746
+
719
747
  // Workaround for noisy "removed ..." logs caused by VirtualModulesPlugin.
720
748
  // Rsbuild suppresses the removed-file log if all removed paths include "virtual":
721
749
  // https://github.com/web-infra-dev/rsbuild/blob/1258fa9dba5c321a4629b591a6dadbd2e26c6963/packages/core/src/createCompiler.ts#L73-L76
@@ -800,22 +828,30 @@ const createBrowserRuntime = async ({
800
828
  [manifestPath]: manifestSource,
801
829
  });
802
830
 
803
- const optionsPlaceholder = '__RSTEST_OPTIONS_PLACEHOLDER__';
804
831
  const containerHtmlTemplate = containerDistPath
805
- ? await fs.readFile(join(containerDistPath, 'container.html'), 'utf-8')
832
+ ? await fs.readFile(join(containerDistPath, 'index.html'), 'utf-8')
833
+ : null;
834
+ const schedulerHtmlTemplate = containerDistPath
835
+ ? await fs
836
+ .readFile(join(containerDistPath, 'scheduler.html'), 'utf-8')
837
+ .catch(() => null)
806
838
  : null;
807
839
 
808
840
  let injectedContainerHtml: string | null = null;
841
+ let injectedSchedulerHtml: string | null = null;
809
842
  let serializedOptions = 'null';
810
843
 
811
844
  const setContainerOptions = (options: BrowserHostConfig): void => {
812
- serializedOptions = JSON.stringify(options).replace(/</g, '\\u003c');
845
+ serializedOptions = serializeForInlineScript(options);
813
846
  if (containerHtmlTemplate) {
814
847
  injectedContainerHtml = containerHtmlTemplate.replace(
815
- optionsPlaceholder,
848
+ OPTIONS_PLACEHOLDER,
816
849
  serializedOptions,
817
850
  );
818
851
  }
852
+ injectedSchedulerHtml = (
853
+ schedulerHtmlTemplate || fallbackSchedulerHtmlTemplate
854
+ ).replace(OPTIONS_PLACEHOLDER, serializedOptions);
819
855
  };
820
856
 
821
857
  // Get user Rsbuild config from the first browser project
@@ -996,7 +1032,7 @@ const createBrowserRuntime = async ({
996
1032
  const serveContainer = containerDistPath
997
1033
  ? sirv(containerDistPath, {
998
1034
  dev: false,
999
- single: 'container.html',
1035
+ single: 'index.html',
1000
1036
  })
1001
1037
  : null;
1002
1038
 
@@ -1020,7 +1056,7 @@ const createBrowserRuntime = async ({
1020
1056
  }
1021
1057
 
1022
1058
  let html = await response.text();
1023
- html = html.replace(optionsPlaceholder, serializedOptions);
1059
+ html = html.replace(OPTIONS_PLACEHOLDER, serializedOptions);
1024
1060
 
1025
1061
  res.statusCode = response.status;
1026
1062
  response.headers.forEach((value, key) => {
@@ -1097,14 +1133,26 @@ const createBrowserRuntime = async ({
1097
1133
  }
1098
1134
  return;
1099
1135
  }
1100
- if (url.pathname === '/') {
1136
+ if (url.pathname === '/' || url.pathname === '/scheduler.html') {
1101
1137
  if (await respondWithDevServerHtml(url, res)) {
1102
1138
  return;
1103
1139
  }
1104
1140
 
1141
+ if (url.pathname === '/scheduler.html') {
1142
+ res.setHeader('Content-Type', 'text/html');
1143
+ res.end(
1144
+ injectedSchedulerHtml ||
1145
+ (schedulerHtmlTemplate || fallbackSchedulerHtmlTemplate).replace(
1146
+ OPTIONS_PLACEHOLDER,
1147
+ 'null',
1148
+ ),
1149
+ );
1150
+ return;
1151
+ }
1152
+
1105
1153
  const html =
1106
1154
  injectedContainerHtml ||
1107
- containerHtmlTemplate?.replace(optionsPlaceholder, 'null');
1155
+ containerHtmlTemplate?.replace(OPTIONS_PLACEHOLDER, 'null');
1108
1156
 
1109
1157
  if (html) {
1110
1158
  res.setHeader('Content-Type', 'text/html');
@@ -1232,6 +1280,56 @@ export const runBrowserController = async (
1232
1280
  ): Promise<BrowserTestRunResult | void> => {
1233
1281
  const { skipOnTestRunEnd = false } = options ?? {};
1234
1282
  const buildStart = Date.now();
1283
+ const browserProjects = getBrowserProjects(context);
1284
+ const useSchedulerPage = browserProjects.every(
1285
+ (project) => project.normalizedConfig.browser.headless,
1286
+ );
1287
+
1288
+ /**
1289
+ * Build an error BrowserTestRunResult and call onTestRunEnd if needed.
1290
+ * Used for early-exit error paths to ensure errors reach the summary report.
1291
+ */
1292
+ const buildErrorResult = async (
1293
+ error: Error,
1294
+ ): Promise<BrowserTestRunResult> => {
1295
+ const elapsed = Math.max(0, Date.now() - buildStart);
1296
+ const errorResult: BrowserTestRunResult = {
1297
+ results: [],
1298
+ testResults: [],
1299
+ duration: { totalTime: elapsed, buildTime: elapsed, testTime: 0 },
1300
+ hasFailure: true,
1301
+ unhandledErrors: [error],
1302
+ };
1303
+
1304
+ if (!skipOnTestRunEnd) {
1305
+ for (const reporter of context.reporters) {
1306
+ await (reporter as Reporter).onTestRunEnd?.({
1307
+ results: [],
1308
+ testResults: [],
1309
+ duration: errorResult.duration,
1310
+ snapshotSummary: context.snapshotManager.summary,
1311
+ getSourcemap: async () => null,
1312
+ unhandledErrors: errorResult.unhandledErrors,
1313
+ });
1314
+ }
1315
+ }
1316
+
1317
+ return errorResult;
1318
+ };
1319
+
1320
+ const toError = (error: unknown): Error => {
1321
+ return error instanceof Error ? error : new Error(String(error));
1322
+ };
1323
+
1324
+ const failWithError = async (
1325
+ error: unknown,
1326
+ cleanup?: () => Promise<void>,
1327
+ ): Promise<BrowserTestRunResult> => {
1328
+ ensureProcessExitCode(1);
1329
+ await cleanup?.();
1330
+ return buildErrorResult(toError(error));
1331
+ };
1332
+
1235
1333
  const containerDevServerEnv = process.env.RSTEST_CONTAINER_DEV_SERVER;
1236
1334
  let containerDevServer: string | undefined;
1237
1335
  let containerDistPath: string | undefined;
@@ -1243,13 +1341,9 @@ export const runBrowserController = async (
1243
1341
  `[Browser UI] Using dev server for container: ${containerDevServer}`,
1244
1342
  );
1245
1343
  } catch (error) {
1246
- logger.error(
1247
- color.red(
1248
- `Invalid RSTEST_CONTAINER_DEV_SERVER value: ${String(error)}`,
1249
- ),
1250
- );
1251
- ensureProcessExitCode(1);
1252
- return;
1344
+ const originalError = toError(error);
1345
+ originalError.message = `Invalid RSTEST_CONTAINER_DEV_SERVER value: ${originalError.message}`;
1346
+ return failWithError(originalError);
1253
1347
  }
1254
1348
  }
1255
1349
 
@@ -1257,9 +1351,7 @@ export const runBrowserController = async (
1257
1351
  try {
1258
1352
  containerDistPath = resolveContainerDist();
1259
1353
  } catch (error) {
1260
- logger.error(color.red(String(error)));
1261
- ensureProcessExitCode(1);
1262
- return;
1354
+ return failWithError(error);
1263
1355
  }
1264
1356
  }
1265
1357
 
@@ -1340,10 +1432,9 @@ export const runBrowserController = async (
1340
1432
  containerDevServer,
1341
1433
  });
1342
1434
  } catch (error) {
1343
- logger.error(error instanceof Error ? error : new Error(String(error)));
1344
- ensureProcessExitCode(1);
1345
- await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});
1346
- return;
1435
+ return failWithError(error, async () => {
1436
+ await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});
1437
+ });
1347
1438
  }
1348
1439
 
1349
1440
  if (isWatchMode) {
@@ -1366,20 +1457,19 @@ export const runBrowserController = async (
1366
1457
 
1367
1458
  // Only include browser mode projects in runtime configs
1368
1459
  // Normalize projectRoot to posix format for cross-platform compatibility
1369
- const browserProjectsForRuntime = getBrowserProjects(context);
1370
- const projectRuntimeConfigs: BrowserProjectRuntime[] =
1371
- browserProjectsForRuntime.map((project: ProjectContext) => ({
1460
+ const projectRuntimeConfigs: BrowserProjectRuntime[] = browserProjects.map(
1461
+ (project: ProjectContext) => ({
1372
1462
  name: project.name,
1373
1463
  environmentName: project.environmentName,
1374
1464
  projectRoot: normalize(project.rootPath),
1375
1465
  runtimeConfig: serializableConfig(getRuntimeConfigFromProject(project)),
1376
- }));
1466
+ viewport: project.normalizedConfig.browser.viewport,
1467
+ }),
1468
+ );
1377
1469
 
1378
1470
  // Get max testTimeout from all browser projects for RPC timeout
1379
1471
  const maxTestTimeoutForRpc = Math.max(
1380
- ...browserProjectsForRuntime.map(
1381
- (p) => p.normalizedConfig.testTimeout ?? 5000,
1382
- ),
1472
+ ...browserProjects.map((p) => p.normalizedConfig.testTimeout ?? 5000),
1383
1473
  );
1384
1474
 
1385
1475
  const hostOptions: BrowserHostConfig = {
@@ -1443,7 +1533,11 @@ export const runBrowserController = async (
1443
1533
  // Forward browser console to terminal
1444
1534
  containerPage.on('console', (msg: ConsoleMessage) => {
1445
1535
  const text = msg.text();
1446
- if (text.includes('[Container]') || text.includes('[Runner]')) {
1536
+ if (
1537
+ text.startsWith('[Container]') ||
1538
+ text.startsWith('[Runner]') ||
1539
+ text.startsWith('[Scheduler]')
1540
+ ) {
1447
1541
  logger.log(color.gray(`[Browser Console] ${text}`));
1448
1542
  }
1449
1543
  });
@@ -1584,21 +1678,28 @@ export const runBrowserController = async (
1584
1678
 
1585
1679
  // Only navigate on first creation
1586
1680
  if (isNewPage) {
1587
- await containerPage.goto(`http://localhost:${port}/`, {
1681
+ const pagePath = useSchedulerPage ? '/scheduler.html' : '/';
1682
+ if (useSchedulerPage) {
1683
+ const serializedOptions = serializeForInlineScript(hostOptions);
1684
+ await containerPage.addInitScript(
1685
+ `window.__RSTEST_BROWSER_OPTIONS__ = ${serializedOptions};`,
1686
+ );
1687
+ }
1688
+ await containerPage.goto(`http://localhost:${port}${pagePath}`, {
1588
1689
  waitUntil: 'load',
1589
1690
  });
1590
1691
 
1591
1692
  logger.log(
1592
- color.cyan(`\nBrowser mode opened at http://localhost:${port}/\n`),
1693
+ color.cyan(
1694
+ `\nBrowser mode opened at http://localhost:${port}${pagePath}\n`,
1695
+ ),
1593
1696
  );
1594
1697
  }
1595
1698
 
1596
1699
  // Wait for all tests to complete
1597
1700
  // Calculate total timeout based on config: max testTimeout * file count + buffer
1598
1701
  const maxTestTimeout = Math.max(
1599
- ...browserProjectsForRuntime.map(
1600
- (p) => p.normalizedConfig.testTimeout ?? 5000,
1601
- ),
1702
+ ...browserProjects.map((p) => p.normalizedConfig.testTimeout ?? 5000),
1602
1703
  );
1603
1704
  const totalTimeoutMs = maxTestTimeout * allTestFiles.length + 30_000;
1604
1705
 
@@ -1670,15 +1771,21 @@ export const runBrowserController = async (
1670
1771
  }
1671
1772
 
1672
1773
  if (!isWatchMode) {
1774
+ try {
1775
+ await containerPage.close();
1776
+ } catch {
1777
+ // ignore
1778
+ }
1779
+ try {
1780
+ await containerContext.close();
1781
+ } catch {
1782
+ // ignore
1783
+ }
1673
1784
  await destroyBrowserRuntime(runtime);
1674
1785
  }
1675
1786
 
1676
1787
  if (fatalError) {
1677
- logger.error(
1678
- color.red(`Browser test run failed: ${(fatalError as Error).message}`),
1679
- );
1680
- ensureProcessExitCode(1);
1681
- return;
1788
+ return failWithError(fatalError);
1682
1789
  }
1683
1790
 
1684
1791
  const duration = {
@@ -1815,6 +1922,7 @@ export const listBrowserTests = async (
1815
1922
  environmentName: project.environmentName,
1816
1923
  projectRoot: normalize(project.rootPath),
1817
1924
  runtimeConfig: serializableConfig(getRuntimeConfigFromProject(project)),
1925
+ viewport: project.normalizedConfig.browser.viewport,
1818
1926
  }),
1819
1927
  );
1820
1928
 
@@ -1895,10 +2003,7 @@ export const listBrowserTests = async (
1895
2003
  );
1896
2004
 
1897
2005
  // Inject host options before navigation so the runner can access them
1898
- const serializedOptions = JSON.stringify(hostOptions).replace(
1899
- /</g,
1900
- '\\u003c',
1901
- );
2006
+ const serializedOptions = serializeForInlineScript(hostOptions);
1902
2007
  await page.addInitScript(
1903
2008
  `window.__RSTEST_BROWSER_OPTIONS__ = ${serializedOptions};`,
1904
2009
  );
package/src/index.ts CHANGED
@@ -9,6 +9,14 @@ import {
9
9
  runBrowserController,
10
10
  } from './hostController';
11
11
 
12
+ export { validateBrowserConfig } from './configValidation';
13
+
14
+ export {
15
+ BROWSER_VIEWPORT_PRESET_DIMENSIONS,
16
+ BROWSER_VIEWPORT_PRESET_IDS,
17
+ resolveBrowserViewportPreset,
18
+ } from './viewportPresets';
19
+
12
20
  export async function runBrowserTests(
13
21
  context: Rstest,
14
22
  options?: BrowserTestRunOptions,
package/src/protocol.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { DevicePreset } from '@rstest/core/browser';
1
2
  import type {
2
3
  RuntimeConfig,
3
4
  Test,
@@ -8,11 +9,19 @@ import type { SnapshotUpdateState } from '@vitest/snapshot';
8
9
 
9
10
  export type SerializedRuntimeConfig = RuntimeConfig;
10
11
 
12
+ export type BrowserViewport =
13
+ | {
14
+ width: number;
15
+ height: number;
16
+ }
17
+ | DevicePreset;
18
+
11
19
  export type BrowserProjectRuntime = {
12
20
  name: string;
13
21
  environmentName: string;
14
22
  projectRoot: string;
15
23
  runtimeConfig: SerializedRuntimeConfig;
24
+ viewport?: BrowserViewport;
16
25
  };
17
26
 
18
27
  /**
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Runtime source of truth for browser viewport presets.
3
+ *
4
+ * IMPORTANT: Keep this list/map in sync with `DevicePreset` typing in
5
+ * `@rstest/core` (`packages/core/src/types/config.ts`) so `defineConfig`
6
+ * autocomplete and runtime validation stay consistent.
7
+ */
8
+ export const BROWSER_VIEWPORT_PRESET_IDS = [
9
+ 'iPhoneSE',
10
+ 'iPhoneXR',
11
+ 'iPhone12Pro',
12
+ 'iPhone14ProMax',
13
+ 'Pixel7',
14
+ 'SamsungGalaxyS8Plus',
15
+ 'SamsungGalaxyS20Ultra',
16
+ 'iPadMini',
17
+ 'iPadAir',
18
+ 'iPadPro',
19
+ 'SurfacePro7',
20
+ 'SurfaceDuo',
21
+ 'GalaxyZFold5',
22
+ 'AsusZenbookFold',
23
+ 'SamsungGalaxyA51A71',
24
+ 'NestHub',
25
+ 'NestHubMax',
26
+ ] as const;
27
+
28
+ type BrowserViewportPresetId = (typeof BROWSER_VIEWPORT_PRESET_IDS)[number];
29
+
30
+ type BrowserViewportSize = {
31
+ width: number;
32
+ height: number;
33
+ };
34
+
35
+ export const BROWSER_VIEWPORT_PRESET_DIMENSIONS: Record<
36
+ BrowserViewportPresetId,
37
+ BrowserViewportSize
38
+ > = {
39
+ iPhoneSE: { width: 375, height: 667 },
40
+ iPhoneXR: { width: 414, height: 896 },
41
+ iPhone12Pro: { width: 390, height: 844 },
42
+ iPhone14ProMax: { width: 430, height: 932 },
43
+ Pixel7: { width: 412, height: 915 },
44
+ SamsungGalaxyS8Plus: { width: 360, height: 740 },
45
+ SamsungGalaxyS20Ultra: { width: 412, height: 915 },
46
+ iPadMini: { width: 768, height: 1024 },
47
+ iPadAir: { width: 820, height: 1180 },
48
+ iPadPro: { width: 1024, height: 1366 },
49
+ SurfacePro7: { width: 912, height: 1368 },
50
+ SurfaceDuo: { width: 540, height: 720 },
51
+ GalaxyZFold5: { width: 344, height: 882 },
52
+ AsusZenbookFold: { width: 853, height: 1280 },
53
+ SamsungGalaxyA51A71: { width: 412, height: 914 },
54
+ NestHub: { width: 1024, height: 600 },
55
+ NestHubMax: { width: 1280, height: 800 },
56
+ };
57
+
58
+ export const resolveBrowserViewportPreset = (
59
+ presetId: string,
60
+ ): BrowserViewportSize | null => {
61
+ const size =
62
+ BROWSER_VIEWPORT_PRESET_DIMENSIONS[presetId as BrowserViewportPresetId];
63
+ return size ?? null;
64
+ };
@@ -1 +0,0 @@
1
- @import "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap";@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-duration:initial;--tw-ease:initial}::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-zinc-950:#09090b;--color-black:#000;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--font-weight-light:300;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wider:.05em;--tracking-widest:.1em;--radius-md:.375rem;--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1)}@supports (color:color(display-p3 0 0 0)){:root,:host{--color-zinc-950:color(display-p3 .0353716 .0353595 .0435539)}}@supports (color:lab(0% 0 0)){:root,:host{--color-zinc-950:lab(2.51107% .242703 -.886115)}}}@layer base,components;@layer utilities{.absolute{position:absolute}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.inset-x-0{inset-inline:calc(var(--spacing)*0)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.bottom-0{bottom:calc(var(--spacing)*0)}.left-0{left:calc(var(--spacing)*0)}.z-10{z-index:10}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing)*0)}.ml-1{margin-left:calc(var(--spacing)*1)}.block{display:block}.flex{display:flex}.grid{display:grid}.inline{display:inline}.inline-flex{display:inline-flex}.h-2{height:calc(var(--spacing)*2)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-8{height:calc(var(--spacing)*8)}.h-\[1px\]{height:1px}.h-\[48px\]{height:48px}.h-full{height:100%}.h-screen{height:100vh}.min-h-0{min-height:calc(var(--spacing)*0)}.w-2{width:calc(var(--spacing)*2)}.w-5{width:calc(var(--spacing)*5)}.w-8{width:calc(var(--spacing)*8)}.w-\[16px\]{width:16px}.w-full{width:100%}.max-w-\[400px\]{max-width:400px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.grid-cols-\[auto_minmax\(0\,1fr\)_auto\]{grid-template-columns:auto minmax(0,1fr) auto}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.bg-black\/\[0\.02\]{background-color:#00000005}@supports (color:color-mix(in lab, red, red)){.bg-black\/\[0\.02\]{background-color:color-mix(in oklab,var(--color-black)2%,transparent)}}.bg-transparent{background-color:#0000}.bg-zinc-950{background-color:var(--color-zinc-950)}.p-0{padding:calc(var(--spacing)*0)}.p-3{padding:calc(var(--spacing)*3)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-4{padding-inline:calc(var(--spacing)*4)}.py-0{padding-block:calc(var(--spacing)*0)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-3{padding-block:calc(var(--spacing)*3)}.font-mono{font-family:var(--font-mono)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.leading-none{--tw-leading:1;line-height:1}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.text-\(--accents-3\){color:var(--accents-3)}.text-\(--accents-4\){color:var(--accents-4)}.text-\(--accents-5\){color:var(--accents-5)}.text-\(--accents-7\){color:var(--accents-7)}.text-\(--muted-foreground\){color:var(--muted-foreground)}.text-\(--warning\){color:var(--warning)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal, )var(--tw-slashed-zero, )var(--tw-numeric-figure, )var(--tw-numeric-spacing, )var(--tw-numeric-fraction, )}.opacity-0{opacity:0}.opacity-40{opacity:.4}.opacity-70{opacity:.7}.opacity-80{opacity:.8}.opacity-90{opacity:.9}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.ease-\[cubic-bezier\(0\.4\,0\,0\.2\,1\)\]{--tw-ease:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(.4,0,.2,1)}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.hover\:bg-\(--accents-1\):hover{background-color:var(--accents-1)}.hover\:text-\(--accents-6\):hover{color:var(--accents-6)}}}.ant-typography.font-mono{font-family:var(--font-mono)}:root{--font-mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--background:#fff;--foreground:#000;--ds-gray-100:#fafafa;--ds-gray-200:#eaeaea;--ds-gray-300:#999;--ds-gray-400:#888;--ds-gray-500:#666;--ds-gray-600:#444;--ds-gray-700:#333;--ds-gray-800:#111;--ds-gray-900:#000;--ds-gray-1000:#000;--ds-red-100:#fff0f0;--ds-red-200:#ffebeb;--ds-red-300:#ffe6e6;--ds-red-700:#e5484d;--ds-red-800:#da2f35;--ds-red-900:#cb2a2f;--ds-green-100:#effbef;--ds-green-200:#ebfaeb;--ds-green-300:#daf6da;--ds-green-700:#45a557;--ds-green-800:#398e4a;--ds-green-900:#297a3a;--ds-amber-100:#fff6e6;--ds-amber-200:#fff4d6;--ds-amber-300:#fef0cd;--ds-amber-700:#ffb224;--ds-amber-800:#ff990a;--ds-amber-900:#a35200;--ds-blue-700:#0070f3;--accents-1:var(--ds-gray-100);--accents-2:var(--ds-gray-200);--accents-3:var(--ds-gray-300);--accents-4:var(--ds-gray-400);--accents-5:var(--ds-gray-500);--accents-6:var(--ds-gray-600);--accents-7:var(--ds-gray-700);--accents-8:var(--ds-gray-800);--border:var(--accents-2);--muted:var(--accents-1);--muted-foreground:var(--accents-5);--primary:var(--foreground);--success:var(--ds-green-700);--error:var(--ds-red-800);--warning:var(--ds-amber-700);--lightningcss-light:initial;--lightningcss-dark: ;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}[data-theme=dark]{--background:#000;--foreground:#fff;--ds-gray-100:#111;--ds-gray-200:#333;--ds-gray-300:#444;--ds-gray-400:#666;--ds-gray-500:#888;--ds-gray-600:#999;--ds-gray-700:#eaeaea;--ds-gray-800:#fafafa;--ds-gray-900:#fff;--ds-gray-1000:#fff;--ds-red-100:#2a1314;--ds-red-200:#3c1618;--ds-red-300:#561a1e;--ds-red-700:#e5484d;--ds-red-800:#d93036;--ds-red-900:#ff6166;--ds-green-100:#0b2212;--ds-green-200:#0f2e18;--ds-green-300:#12361b;--ds-green-700:#45a557;--ds-green-800:#398e4a;--ds-green-900:#62c073;--ds-amber-100:#291800;--ds-amber-200:#331b00;--ds-amber-300:#4d2a00;--ds-amber-700:#ffb224;--ds-amber-800:#ff990a;--ds-amber-900:#f2a20d;--ds-blue-700:#0070f3;--accents-1:var(--ds-gray-100);--accents-2:var(--ds-gray-200);--accents-3:var(--ds-gray-300);--accents-4:var(--ds-gray-400);--accents-5:var(--ds-gray-500);--accents-6:var(--ds-gray-600);--accents-7:var(--ds-gray-700);--accents-8:var(--ds-gray-800);--border:var(--accents-2);--muted:var(--accents-1);--muted-foreground:var(--accents-5);--primary:var(--foreground);--success:var(--ds-green-700);--error:var(--ds-red-800);--warning:var(--ds-amber-700);--lightningcss-light: ;--lightningcss-dark:initial;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}*{box-sizing:border-box}body{background:var(--background);min-height:100vh;color:var(--foreground);margin:0;font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;overflow:hidden}.ant-input-affix-wrapper:focus,.ant-input-affix-wrapper-focused{border-color:var(--foreground);box-shadow:0 0 0 1px var(--foreground)}.ant-tree .ant-tree-node-content-wrapper.ant-tree-node-selected{transition:none}@keyframes status-flash{0%{filter:brightness()}5%{filter:brightness(2.5)}15%{filter:brightness(1.8)}40%{filter:brightness(1.3)}to{filter:brightness()}}.status-icon-flash{animation:1.5s ease-out status-flash}@keyframes progress-segment-flash{0%{filter:brightness();box-shadow:none}10%{filter:brightness(2.5);box-shadow:0 0 8px 1px}to{filter:brightness();box-shadow:none}}.progress-flash-active{animation:1s cubic-bezier(.4,0,.2,1) progress-segment-flash}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}
@@ -1 +0,0 @@
1
- /*! For license information please see 837.e631233e.js.LICENSE.txt */
@@ -1 +0,0 @@
1
- /*! For license information please see lib-react.48335539.js.LICENSE.txt */