diff --git a/package.json b/package.json index 2f33a1b..09b9243 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "type": "module", "workspaces": [ "theme", - "bot" + "bot", + "web" ], "scripts": { "build": "npm run build --workspace theme", @@ -16,6 +17,11 @@ "bot:typecheck": "npm run typecheck -w bot", "bot:dev": "npm run dev -w bot", "bot:deploy": "npm run deploy -w bot", - "bot:commands": "npm run commands -w bot" + "bot:commands": "npm run commands -w bot", + "web:test": "npm test -w web", + "web:typecheck": "npm run typecheck -w web", + "web:dev": "npm run dev -w web", + "web:build": "npm run build -w web", + "web:capture": "npm run capture -w web" } } diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..7336934 --- /dev/null +++ b/web/index.html @@ -0,0 +1,12 @@ + + + + + + araharah + + +
+ + + diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..5d8f052 --- /dev/null +++ b/web/package.json @@ -0,0 +1,21 @@ +{ + "name": "@araharah/web", + "private": true, + "type": "module", + "scripts": { + "test": "node test/runner.mjs", + "typecheck": "tsc --noEmit", + "dev": "vite", + "build": "vite build", + "capture": "node scripts/capture.mjs" + }, + "dependencies": { + "maplibre-gl": "^6.15.0", + "@araharah/theme": "*" + }, + "devDependencies": { + "vite": "^8.3.5", + "typescript": "^5.9.3", + "playwright": "^1.50.0" + } +} diff --git a/web/src/main.ts b/web/src/main.ts new file mode 100644 index 0000000..686efa1 --- /dev/null +++ b/web/src/main.ts @@ -0,0 +1,10 @@ +/* + * main.ts — the Mini App entry module (D-01, D-02, D-11). + * -------------------------------------------------------- + * The one-and-only DOM entry: web/index.html loads it as `type="module"` + * into `
`. This plan (03-01) scaffolds the workspace; the + * Telegram WebApp binding behind the narrow TelegramWebAppLike interface + * and the map shell mount here in plans 03-02 and 03-03. Kept + * dependency-free until those plans introduce their modules. + */ +export {}; diff --git a/web/test/harness.mjs b/web/test/harness.mjs new file mode 100644 index 0000000..bebcfdc --- /dev/null +++ b/web/test/harness.mjs @@ -0,0 +1,10 @@ +/* + * harness.mjs — zero-dependency test registry (D-12). + * Suites import { test } and register async cases; runner.mjs drains the registry. + * Same hand-rolled shape as theme/scripts/audit.test.mjs (Phase 1 precedent). + */ +const tests = []; +export function test(name, fn) { + tests.push({ name, fn }); +} +export const registry = tests; diff --git a/web/test/runner.mjs b/web/test/runner.mjs new file mode 100644 index 0000000..333fe2f --- /dev/null +++ b/web/test/runner.mjs @@ -0,0 +1,47 @@ +#!/usr/bin/env node +/* + * runner.mjs — hand-rolled zero-dependency test runner (D-02, D-11). + * + * Auto-discovers *.test.mjs in this directory (sorted), dynamic-imports + * them, and drains the harness registry. A suite that fails to LOAD is + * recorded as one failing case — the run never crashes mid-way. + * Same hand-rolled shape as theme/scripts/audit.test.mjs (Phase 1) + * and bot/test/ (Phase 2) — copied verbatim, workspace-agnostic. + */ +import { readdirSync } from "node:fs"; +import { registry } from "./harness.mjs"; + +const dir = import.meta.dirname; +const suites = readdirSync(dir) + .filter((f) => f.endsWith(".test.mjs")) + .sort(); + +for (const file of suites) { + try { + await import(`./${file}`); + } catch (loadErr) { + registry.push({ + name: `suite load failed: ${file}`, + fn: () => { + throw loadErr; + }, + }); + } +} + +let passedCount = 0; +let failedCount = 0; +for (const { name, fn } of registry) { + try { + await fn(); + passedCount++; + console.log(`PASS ${name}`); + } catch (err) { + failedCount++; + console.log(`FAIL ${name}`); + console.error(` ${err && err.stack ? err.stack : String(err)}`); + } +} + +console.log(`[runner] ${passedCount} passed, ${failedCount} failed`); +process.exitCode = failedCount > 0 ? 1 : 0; diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 0000000..44e869e --- /dev/null +++ b/web/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2023", + "lib": ["ES2023", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "types": ["vite/client"], + "strict": true, + "noUncheckedIndexedAccess": true, + "verbatimModuleSyntax": true, + "allowImportingTsExtensions": true, + "noEmit": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["src"] +} diff --git a/web/vite.config.ts b/web/vite.config.ts new file mode 100644 index 0000000..65f294e --- /dev/null +++ b/web/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from "vite"; + +export default defineConfig({ + base: "/", + build: { + outDir: "dist", + emptyOutDir: true, + target: "es2022", + }, + server: { + port: 5173, + strictPort: true, + }, +});