diff --git a/web/e2e/smoke.spec.ts b/web/e2e/smoke.spec.ts index 8a79f09..e44a223 100644 --- a/web/e2e/smoke.spec.ts +++ b/web/e2e/smoke.spec.ts @@ -45,6 +45,15 @@ test.describe("Compiler Explorer smoke", () => { await expect(page.getByRole("textbox", { name: "source" })).toContainText("H = relu(X @ W1 + b1)"); }); + test("the landing page plays the hero clip and links to the explorer", async ({ page }) => { + await page.goto("/"); + const video = page.locator("video"); + await expect(video).toHaveAttribute("src", "/hero.webm"); + const ready = await video.evaluate((v: HTMLVideoElement) => v.readyState >= 1 || new Promise((r) => (v.onloadedmetadata = () => r(true)))); + expect(ready).toBeTruthy(); + await expect(page.getByRole("link", { name: "Open the Compiler Explorer" })).toHaveAttribute("href", "/app"); + }); + test("a rejected program is shown as an error, not a crash", async ({ page }) => { await page.goto("/app"); await page.getByLabel("source").fill("input A : i8[2, 2]\nC = A @ A +\noutput C\n"); diff --git a/web/eslint.config.js b/web/eslint.config.js index ab7ca60..64a4c5f 100644 --- a/web/eslint.config.js +++ b/web/eslint.config.js @@ -1,10 +1,15 @@ import js from "@eslint/js"; import tseslint from "typescript-eslint"; +import globals from "globals"; export default tseslint.config( { ignores: [".vercel/", "dist/", ".astro/", "node_modules/", "src/lib/examples.generated.ts", "src/pages/docs/*.md", "public/"] }, js.configs.recommended, ...tseslint.configs.recommended, + { + files: ["scripts/**/*.mjs"], + languageOptions: { globals: globals.node }, + }, { files: ["**/*.ts", "**/*.tsx"], rules: { "@typescript-eslint/no-explicit-any": "error" }, diff --git a/web/package.json b/web/package.json index 7762f97..2411aa1 100644 --- a/web/package.json +++ b/web/package.json @@ -30,6 +30,7 @@ "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "eslint": "^9.39.5", + "globals": "^15.15.0", "typescript": "^5.6.3", "typescript-eslint": "^8.72.0", "vitest": "^2.1.9" @@ -37,6 +38,9 @@ "pnpm": { "onlyBuiltDependencies": [ "esbuild" + ], + "ignoredBuiltDependencies": [ + "sharp" ] } } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 742244d..d7b5de5 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -48,6 +48,9 @@ importers: eslint: specifier: ^9.39.5 version: 9.39.5(supports-color@7.2.0) + globals: + specifier: ^15.15.0 + version: 15.15.0 typescript: specifier: ^5.6.3 version: 5.9.3 @@ -1841,6 +1844,10 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + h3@1.15.11: resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} @@ -4796,6 +4803,8 @@ snapshots: globals@14.0.0: {} + globals@15.15.0: {} + h3@1.15.11: dependencies: cookie-es: 1.2.3 diff --git a/web/public/hero-poster.jpg b/web/public/hero-poster.jpg new file mode 100644 index 0000000..2560f7f Binary files /dev/null and b/web/public/hero-poster.jpg differ diff --git a/web/public/hero.webm b/web/public/hero.webm new file mode 100644 index 0000000..0eac773 Binary files /dev/null and b/web/public/hero.webm differ diff --git a/web/scripts/record-hero.mjs b/web/scripts/record-hero.mjs new file mode 100644 index 0000000..4fff1df --- /dev/null +++ b/web/scripts/record-hero.mjs @@ -0,0 +1,34 @@ +// Records the hero clip: one program compiled and run on both chips, then the tiny-tpu array +// played cycle by cycle. Run against a built site (`pnpm build && pnpm preview --port 4322`): +// node scripts/record-hero.mjs http://127.0.0.1:4322 +// Output: hero-raw.webm in . The shipped clip is trimmed from 5.5 s (past the loading +// screen) and re-encoded: ffmpeg -ss 5.5 -i hero-raw.webm -an -c:v libvpx-vp9 -crf 40 -vf scale=960:-2 public/hero.webm +import { chromium } from "@playwright/test"; +import { mkdirSync, renameSync } from "node:fs"; +import { join } from "node:path"; + +const [base = "http://127.0.0.1:4322", outDir = "hero-out"] = process.argv.slice(2); +mkdirSync(outDir, { recursive: true }); + +const browser = await chromium.launch(); +const context = await browser.newContext({ + viewport: { width: 1280, height: 960 }, + recordVideo: { dir: outDir, size: { width: 1280, height: 960 } }, +}); +const page = await context.newPage(); +await page.goto(`${base}/app`); +await page.getByRole("button", { name: "Compile & run" }).click(); +await page.getByText("bit-exact").first().waitFor({ timeout: 150_000 }); +await page.getByRole("tab", { name: "tiny-tpu" }).click(); +await page.getByRole("img", { name: /^PE\[0\]\[0\] / }).first().scrollIntoViewIfNeeded(); +await page.getByRole("button", { name: "Play" }).click(); +await page.waitForTimeout(6000); +await page.getByRole("tab", { name: "Run & compare" }).click(); +await page.waitForTimeout(3000); +const video = page.video(); +await context.close(); +await browser.close(); +const path = await video.path(); +const target = join(outDir, "hero-raw.webm"); +renameSync(path, target); +console.log(target); diff --git a/web/src/pages/index.astro b/web/src/pages/index.astro index 4a2b153..abb2ee6 100644 --- a/web/src/pages/index.astro +++ b/web/src/pages/index.astro @@ -14,6 +14,22 @@ import Base from "../layouts/Base.astro";

Read the docs

+
+ +
+ C = relu(A @ B) compiled to both chips. The array is tiny-tpu's real RTL, cycle by cycle, from the Compiler Explorer. +
+
+

What is checked, on every commit