Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ cargo clippy --locked --all-targets --manifest-path src-tauri/Cargo.toml -- -D w
cargo test --locked --lib --manifest-path src-tauri/Cargo.toml
```

Run `bun run screenshot` to refresh `docs/assets/screenshot.png` from the real
page using its credential-free `?screenshot=1` fixture; the command starts and
stops its own Vite server and browser. The 2240 by 1312 image captures the
1120 by 656 webview content at 2x; the remaining 32px of the 1120 by 688 Tauri
window is native title chrome and is not captured.

## Commits

- [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/):
Expand Down
11 changes: 10 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified docs/assets/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"screenshot": "bun scripts/capture-screenshot.ts",
"tauri": "tauri"
},
"license": "MIT",
Expand All @@ -27,6 +28,7 @@
"@tailwindcss/vite": "^4.3.3",
"@tauri-apps/cli": "^2",
"@types/node": "^26.3.0",
"playwright": "1.58.2",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^4.3.3",
Expand Down
93 changes: 93 additions & 0 deletions scripts/capture-screenshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { mkdir } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { chromium } from "playwright";

const scriptDirectory = dirname(fileURLToPath(import.meta.url));
const projectRoot = resolve(scriptDirectory, "..");
const port = 1430;
const url = `http://127.0.0.1:${port}/?screenshot=1`;
const output = resolve(projectRoot, "docs/assets/screenshot.png");
const playwrightCli = resolve(projectRoot, "node_modules/playwright/cli.js");
const viteCli = resolve(projectRoot, "node_modules/vite/bin/vite.js");

let server: Bun.Subprocess | undefined;
let browser: Awaited<ReturnType<typeof chromium.launch>> | undefined;
let stopping: Promise<void> | undefined;

async function waitForServer() {
const deadline = Date.now() + 30_000;
while (Date.now() < deadline) {
try {
const response = await fetch(url);
if (response.ok) {
return;
}
} catch {}
await Bun.sleep(100);
}
throw new Error(`Vite did not become ready at ${url}`);
}

async function run(command: string[]) {
const process = Bun.spawn(command, { cwd: projectRoot, stdout: "inherit", stderr: "inherit" });
if ((await process.exited) !== 0) {
throw new Error(`Command failed: ${command.join(" ")}`);
}
}

async function stopServer() {
if (!server || server.exitCode !== null) {
return;
}
server.kill("SIGTERM");
await Promise.race([server.exited.then(() => {}), Bun.sleep(5_000)]);
if (server.exitCode === null) {
server.kill("SIGKILL");
await server.exited;
}
}

function cleanup() {
stopping ??= (async () => {
await browser?.close();
await stopServer();
})();
return stopping;
}

for (const signal of ["SIGINT", "SIGTERM"] as const) {
process.once(signal, () => {
void cleanup().finally(() => process.exit(128));
});
}

try {
await run([process.execPath, playwrightCli, "install", "chromium"]);
server = Bun.spawn(
[process.execPath, viteCli, "dev", "--host", "127.0.0.1", "--port", String(port), "--strictPort"],
{ cwd: projectRoot, stdout: "inherit", stderr: "inherit" },
);
await waitForServer();
browser = await chromium.launch();
const context = await browser.newContext({
viewport: { width: 1120, height: 656 },
deviceScaleFactor: 2,
locale: "en-US",
timezoneId: "UTC",
});
const page = await context.newPage();
await page.goto(url, { waitUntil: "domcontentloaded" });
await page.waitForSelector('html[data-screenshot-ready="true"]');
await page.evaluate(async () => {
await document.fonts.ready;
});
await page.addStyleTag({
content: "*, *::before, *::after { animation: none !important; caret-color: transparent !important; transition: none !important; }",
});
await mkdir(dirname(output), { recursive: true });
await page.screenshot({ path: output, animations: "disabled" });
await context.close();
} finally {
await cleanup();
}
1 change: 1 addition & 0 deletions src/app.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare const __SCREENSHOT_VERSION__: string;
43 changes: 43 additions & 0 deletions src/lib/screenshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { SubnetRow } from "./import";

const probedAt = Date.UTC(2026, 7, 24, 14, 30);

const countries = ["US", "GB", "DE", "CA", "NL", "FR", "JP", "SG", "AU", null];
const tagSets = [
["residential"],
["mobile"],
["checkout"],
[],
["backup"],
["monitoring"],
["europe"],
["retail"],
["stable"],
[],
];

export const screenshotRows: SubnetRow[] = Array.from({ length: 36 }, (_, index) => {
const state = index % 6;
const measured = state !== 5;
const hasMetrics = measured && state !== 4;
const quantity = 24 + ((index * 13) % 77);
const ok = state === 4 ? 0 : measured ? quantity - ((index * 3) % 9) : null;
const base = state === 0 ? 180 : state === 1 ? 720 : state === 2 ? 1840 : 420;

return {
cidr: `198.18.${index}.0/24`,
country: countries[index % countries.length],
quantity,
tags: tagSets[index % tagSets.length],
ok,
connectP50: hasMetrics ? base : null,
connectP95: hasMetrics ? base + 90 : null,
ttfbP50: hasMetrics ? base + 140 : null,
ttfbP95: hasMetrics ? base + 330 : null,
lastRunAt: measured ? probedAt : null,
};
});

export const screenshotSelectedCidrs = new Set(
screenshotRows.slice(0, 3).map((row) => row.cidr),
);
4 changes: 1 addition & 3 deletions src/lib/ui/MenuSurface.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { onDestroy, tick, type Snippet } from "svelte";
import { tick, type Snippet } from "svelte";

let {
children,
Expand Down Expand Up @@ -34,8 +34,6 @@
});
});

onDestroy(restoreFocus);

function moveFocus(event: KeyboardEvent) {
const items = [...(root?.querySelectorAll<HTMLButtonElement>('[role="menuitem"]:not(:disabled)') ?? [])];
const current = items.indexOf(document.activeElement as HTMLButtonElement);
Expand Down
24 changes: 23 additions & 1 deletion src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { open, save } from "@tauri-apps/plugin-dialog";
import { openUrl } from "@tauri-apps/plugin-opener";
import { getCurrentWebview } from "@tauri-apps/api/webview";
import { onMount } from "svelte";
import { onMount, tick } from "svelte";
import type { ImportResult, Progress, RunResult, SubnetRow } from "$lib/import";
import { emptyMetrics, withMetrics } from "$lib/import";
import SubnetTable from "$lib/SubnetTable.svelte";
Expand Down Expand Up @@ -46,6 +46,8 @@
});
let noticeTimer: ReturnType<typeof setTimeout> | null = null;

const screenshotMode = import.meta.env.DEV && new URLSearchParams(window.location.search).get("screenshot") === "1";

const locked = $derived(busy || running || updating);
const selectedScope = $derived(selectedCidrs.size > 0 ? [...selectedCidrs] : null);

Expand All @@ -57,6 +59,12 @@
}
});

$effect(() => {
if (selectedCidrs.size > 0) {
exportMenuOpen = false;
}
});

function showNotice(message: string, isError: boolean) {
if (noticeTimer) {
clearTimeout(noticeTimer);
Expand All @@ -82,6 +90,10 @@
}

onMount(() => {
if (screenshotMode) {
void loadScreenshot();
return;
}
let disposed = false;
let stopDrop = () => {};
let stopProgress = () => {};
Expand Down Expand Up @@ -155,6 +167,16 @@
};
});

async function loadScreenshot() {
const { screenshotRows, screenshotSelectedCidrs } = await import("$lib/screenshot");
rows = screenshotRows;
draft = Object.fromEntries(screenshotRows.map((row) => [row.cidr, ""]));
selectedCidrs = new Set(screenshotSelectedCidrs);
version = __SCREENSHOT_VERSION__;
await tick();
document.documentElement.dataset.screenshotReady = "true";
}

function applyProgress(progress: Progress) {
done = progress.done;
total = progress.total;
Expand Down
4 changes: 4 additions & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { sveltekit } from "@sveltejs/kit/vite";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
import packageJson from "./package.json" with { type: "json" };

const host = process.env.TAURI_DEV_HOST;

export default defineConfig(async () => ({
plugins: [tailwindcss(), sveltekit()],
define: {
__SCREENSHOT_VERSION__: JSON.stringify(packageJson.version),
},
clearScreen: false,
server: {
port: 1420,
Expand Down