diff --git a/example-apps/dashmint-lab/.prettierignore b/example-apps/dashmint-lab/.prettierignore index 007ea8a..7f89e0c 100644 --- a/example-apps/dashmint-lab/.prettierignore +++ b/example-apps/dashmint-lab/.prettierignore @@ -1,3 +1,4 @@ dist node_modules coverage +public/dashmint-lite.html diff --git a/example-apps/dashmint-lab/CLAUDE.md b/example-apps/dashmint-lab/CLAUDE.md index 6d96e55..d266f93 100644 --- a/example-apps/dashmint-lab/CLAUDE.md +++ b/example-apps/dashmint-lab/CLAUDE.md @@ -21,6 +21,7 @@ React + TypeScript + Vite app for minting, viewing, transferring, and trading NF - **[src/data/starterPack.ts](src/data/starterPack.ts)** — shared card pool and `drawStarterPack()` Fisher-Yates shuffle. Injectable RNG for deterministic tests. - **[src/lib/](src/lib/)** — pure utilities: [rarity.ts](src/lib/rarity.ts) (tier from atk+def), [format.ts](src/lib/format.ts), [explorer.ts](src/lib/explorer.ts) (Platform Explorer URLs), [cardArt.ts](src/lib/cardArt.ts) (theme/palette recipe — presentation only, not Platform-relevant). - **[src/styles/globals.css](src/styles/globals.css)** — Tailwind v4 import + rarity tokens. +- **[public/dashmint-lite.html](public/dashmint-lite.html)** — single-file zero-build companion. Read-only Browse cards (with Marketplace-only toggle), loads `@dashevo/evo-sdk` from `esm.sh`, and ships alongside the React app at `<...>/dashmint-lab/dashmint-lite.html` (Vite copies `public/*` into `dist/`). Intentionally self-contained as a learning reference — don't import app code into it. - **[test/](test/)** — Vitest + Testing Library. All test files live here per the `include` pattern in [vite.config.ts](vite.config.ts) and are named after the subject under test (e.g. `CardTile.test.tsx`, `SessionContext.test.tsx`). Default env is `node`; tests that need DOM opt in with `// @vitest-environment jsdom`. ## SDK Patterns diff --git a/example-apps/dashmint-lab/public/dashmint-lite.html b/example-apps/dashmint-lab/public/dashmint-lite.html new file mode 100644 index 0000000..41de9f9 --- /dev/null +++ b/example-apps/dashmint-lab/public/dashmint-lite.html @@ -0,0 +1,324 @@ + + + + + + + DashMint Lite + + + + + + +
+ + +
+
+

Browse cards

+

Read-only listing of NFT cards from the card data contract on testnet.

+
+ + + +
+
+
+ +

+ Contract: +

+
+
+ + + + diff --git a/example-apps/dashproof-lab/.prettierignore b/example-apps/dashproof-lab/.prettierignore index 007ea8a..6f1b734 100644 --- a/example-apps/dashproof-lab/.prettierignore +++ b/example-apps/dashproof-lab/.prettierignore @@ -1,3 +1,4 @@ dist node_modules coverage +public/dashproof-lite.html diff --git a/example-apps/dashproof-lab/CLAUDE.md b/example-apps/dashproof-lab/CLAUDE.md index b369446..7162cc1 100644 --- a/example-apps/dashproof-lab/CLAUDE.md +++ b/example-apps/dashproof-lab/CLAUDE.md @@ -31,6 +31,7 @@ Files never leave the browser. `$createdAt` from the resulting document is the p - **[src/dash/types.ts](src/dash/types.ts)** / **[src/dash/logger.ts](src/dash/logger.ts)** — shared SDK types and the `Logger` interface (`(message, level?) => void`) wired through every dash helper. - **[test/](test/)** — Vitest + Testing Library. All test files live in this flat directory per the `include` pattern in [vite.config.ts](vite.config.ts) (`test/**/*.test.{ts,tsx}`) and are named after the subject under test (e.g. `AnchorForm.test.tsx`, `SessionContext.test.tsx`, `dash.test.ts`) — they are **not** co-located next to source files, and the directory is **not** mirrored against `src/`. Default Vitest env is `node`; component tests opt into DOM with a `// @vitest-environment jsdom` pragma at the top of the file. - **[e2e/](e2e/)** — Playwright specs (`anchor`, `verify`, `history`, `theme`, `copy`) plus shared `fixtures.ts`. Driven by [playwright.config.ts](playwright.config.ts), which loads `PLATFORM_MNEMONIC` from `../../.env` (repo root, with optional `dashproof-lab/.env` override) and auto-starts `npm run dev` on port 5173. Read-only specs run in parallel; anchor-write specs are forced serial via `test.describe.configure({ mode: "serial" })` to avoid concurrent writes to the same identity. `fixtures.ts` exports a `HAS_MNEMONIC` flag so auth-gated specs `test.skip` cleanly when no mnemonic is set. +- **[public/dashproof-lite.html](public/dashproof-lite.html)** — single-file zero-build companion. Read-only Verify + History only, loads `@dashevo/evo-sdk` from `esm.sh`, and ships alongside the React app at `<...>/dashproof-lab/dashproof-lite.html` (Vite copies `public/*` into `dist/`). Intentionally self-contained as a learning reference — don't import app code into it. ## Anchor contract diff --git a/example-apps/dashproof-lab/e2e/lite.spec.ts b/example-apps/dashproof-lab/e2e/lite.spec.ts new file mode 100644 index 0000000..d9f9f0a --- /dev/null +++ b/example-apps/dashproof-lab/e2e/lite.spec.ts @@ -0,0 +1,152 @@ +import { test, expect } from "./fixtures"; +import { readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +// Vite serves files under public/ at the URL root, so the lite page lives at +// /dashproof-lite.html during dev and at //dashproof-lite.html after a +// production build. These specs use the dev server (configured in +// playwright.config.ts) so the path is just /dashproof-lite.html. +const LITE_URL = "/dashproof-lite.html"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +function loadFixtureFile(filename: string) { + const path = resolve(__dirname, "../public/example-files", filename); + return { + name: filename, + mimeType: filename.endsWith(".json") + ? "application/json" + : filename.endsWith(".csv") + ? "text/csv" + : "text/plain", + buffer: readFileSync(path), + }; +} + +test.describe("dashproof-lite (single-file companion)", () => { + test("connects to testnet and enables inputs on load", async ({ page }) => { + await page.goto(LITE_URL); + await expect(page.locator("#status")).toHaveText(/Connected to testnet/, { + timeout: 30_000, + }); + await expect(page.locator("#verify-file")).toBeEnabled(); + await expect(page.locator("#chain-id")).toBeEnabled(); + await expect(page.locator("#chain-btn")).toBeEnabled(); + }); + + test("verify match: shows anchor card for a known fixture", async ({ + page, + }) => { + await page.goto(LITE_URL); + await expect(page.locator("#status")).toHaveText(/Connected to testnet/, { + timeout: 30_000, + }); + + const fixture = loadFixtureFile("proof-fixture-01.txt"); + await page.locator("#verify-file").setInputFiles({ + name: fixture.name, + mimeType: fixture.mimeType, + buffer: fixture.buffer, + }); + + // Hash echo above the result confirms SHA-256 ran client-side. + await expect(page.locator("#verify-hash")).toContainText( + "02e4e7cd6b6c73ec895e82d5e59065f30ffbb70f03fdd7d2a575ffd0c333d414", + { timeout: 30_000 }, + ); + + // Anchor card lists the chainId and the decoded SHA-256 hex. + const card = page.locator("#verify-result .anchor"); + await expect(card).toBeVisible({ timeout: 60_000 }); + await expect(card).toContainText("demo-proof-fixture-01"); + await expect(card).toContainText( + "02e4e7cd6b6c73ec895e82d5e59065f30ffbb70f03fdd7d2a575ffd0c333d414", + ); + }); + + test("verify miss: shows not-found banner for a random file", async ({ + page, + randomFilePayload, + }) => { + await page.goto(LITE_URL); + await expect(page.locator("#status")).toHaveText(/Connected to testnet/, { + timeout: 30_000, + }); + + await page.locator("#verify-file").setInputFiles({ + name: randomFilePayload.name, + mimeType: randomFilePayload.mimeType, + buffer: randomFilePayload.buffer, + }); + + await expect( + page.locator("#verify-result .status-line.miss"), + ).toContainText("No anchor found", { timeout: 60_000 }); + }); + + test("history: lists anchors for a known chainId", async ({ page }) => { + await page.goto(LITE_URL); + await expect(page.locator("#status")).toHaveText(/Connected to testnet/, { + timeout: 30_000, + }); + + // Switch to History tab. + await page.locator('.nav-btn[data-tab="history"]').click(); + await expect(page.locator("#panel-history")).toHaveClass(/active/); + + await page.locator("#chain-id").fill("demo-proof-fixture-01"); + await page.locator("#chain-btn").click(); + + await expect(page.locator("#chain-result .summary-line")).toContainText( + /\d+ anchor\(s\) found/, + { timeout: 60_000 }, + ); + const cards = page.locator("#chain-result .anchor"); + await expect(cards.first()).toBeVisible(); + await expect(cards.first()).toContainText("demo-proof-fixture-01"); + }); + + test("history miss: shows not-found banner for an unknown chainId", async ({ + page, + }) => { + await page.goto(LITE_URL); + await expect(page.locator("#status")).toHaveText(/Connected to testnet/, { + timeout: 30_000, + }); + + await page.locator('.nav-btn[data-tab="history"]').click(); + await page + .locator("#chain-id") + .fill( + `bogus-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`, + ); + await page.locator("#chain-btn").click(); + + await expect(page.locator("#chain-result .status-line.miss")).toContainText( + "No anchors found", + { timeout: 60_000 }, + ); + }); + + test("tab switching toggles panel visibility without reload", async ({ + page, + }) => { + await page.goto(LITE_URL); + await expect(page.locator("#status")).toHaveText(/Connected to testnet/, { + timeout: 30_000, + }); + + // Verify is active on load. + await expect(page.locator("#panel-verify")).toHaveClass(/active/); + await expect(page.locator("#panel-history")).not.toHaveClass(/active/); + + await page.locator('.nav-btn[data-tab="history"]').click(); + await expect(page.locator("#panel-history")).toHaveClass(/active/); + await expect(page.locator("#panel-verify")).not.toHaveClass(/active/); + + await page.locator('.nav-btn[data-tab="verify"]').click(); + await expect(page.locator("#panel-verify")).toHaveClass(/active/); + await expect(page.locator("#panel-history")).not.toHaveClass(/active/); + }); +}); diff --git a/example-apps/dashproof-lab/public/dashproof-lite.html b/example-apps/dashproof-lab/public/dashproof-lite.html new file mode 100644 index 0000000..ef53352 --- /dev/null +++ b/example-apps/dashproof-lab/public/dashproof-lite.html @@ -0,0 +1,350 @@ + + + + + + + DashProof Lite + + + + + + +
+ + +
+ +
+

Verify a file

+

Hashes the file locally with SHA-256, then looks up the digest on-chain. The file never leaves your browser.

+ +
+
+
+ +
+

History by chainId

+

List all anchors recorded under a given chainId bucket.

+ +
+ +
+
+
+ +

+ Contract: +

+
+
+ + + +