From 59348d924c1b13fd8d435f59e7bafa157a0d5bf3 Mon Sep 17 00:00:00 2001 From: Suleiman Shahbari Date: Sun, 28 Jun 2026 17:10:30 +0300 Subject: [PATCH 1/2] chore: remove the benchmark from gemstack (relocated to vike-data) The AI-vs-Next.js benchmark is moving to suleimansh/vike-data, where the extension family it now measures (vike-auth, vike-data, vike-notifications, vike-stripe) lives. Removes the benchmarks/ harness and both bench-app-* examples, plus the better-sqlite3/sharp build approvals only they needed. examples/mcp-quickstart and all packages are untouched; build stays green. --- benchmarks/README.md | 67 - benchmarks/spec/product.md | 55 - benchmarks/spec/task-001-tags.md | 32 - benchmarks/tasks/task-001-tags/accept.mjs | 88 - examples/bench-app-gemstack/.gitignore | 9 - examples/bench-app-gemstack/README.md | 77 - examples/bench-app-gemstack/package.json | 30 - examples/bench-app-gemstack/pages/+config.ts | 8 - examples/bench-app-gemstack/pages/api.ts | 49 - .../bench-app-gemstack/pages/index/+Page.tsx | 99 - .../bench-app-gemstack/pages/login/+Page.tsx | 53 - .../pages/note/@id/+Page.tsx | 50 - examples/bench-app-gemstack/server/ai.ts | 86 - examples/bench-app-gemstack/server/api.ts | 105 -- examples/bench-app-gemstack/server/db.ts | 104 -- examples/bench-app-gemstack/server/index.ts | 44 - examples/bench-app-gemstack/tsconfig.json | 13 - examples/bench-app-gemstack/vite.config.ts | 9 - examples/bench-app-next/.gitignore | 9 - examples/bench-app-next/.npmrc | 6 - examples/bench-app-next/README.md | 66 - .../bench-app-next/app/api/login/route.ts | 27 - .../bench-app-next/app/api/logout/route.ts | 8 - .../app/api/notes/[id]/route.ts | 26 - .../app/api/notes/[id]/summarize/route.ts | 19 - .../bench-app-next/app/api/notes/route.ts | 32 - .../bench-app-next/app/create-note-form.tsx | 45 - examples/bench-app-next/app/layout.tsx | 24 - .../bench-app-next/app/login/login-form.tsx | 48 - examples/bench-app-next/app/login/page.tsx | 16 - examples/bench-app-next/app/logout-button.tsx | 17 - examples/bench-app-next/app/note-actions.tsx | 28 - .../bench-app-next/app/notes/[id]/page.tsx | 33 - examples/bench-app-next/app/page.tsx | 47 - examples/bench-app-next/lib/auth.ts | 41 - examples/bench-app-next/lib/db.ts | 42 - examples/bench-app-next/lib/notes.ts | 58 - examples/bench-app-next/lib/summarize.ts | 17 - examples/bench-app-next/next.config.mjs | 8 - examples/bench-app-next/package.json | 26 - examples/bench-app-next/tsconfig.json | 21 - pnpm-lock.yaml | 1661 +---------------- pnpm-workspace.yaml | 3 - 43 files changed, 14 insertions(+), 3292 deletions(-) delete mode 100644 benchmarks/README.md delete mode 100644 benchmarks/spec/product.md delete mode 100644 benchmarks/spec/task-001-tags.md delete mode 100644 benchmarks/tasks/task-001-tags/accept.mjs delete mode 100644 examples/bench-app-gemstack/.gitignore delete mode 100644 examples/bench-app-gemstack/README.md delete mode 100644 examples/bench-app-gemstack/package.json delete mode 100644 examples/bench-app-gemstack/pages/+config.ts delete mode 100644 examples/bench-app-gemstack/pages/api.ts delete mode 100644 examples/bench-app-gemstack/pages/index/+Page.tsx delete mode 100644 examples/bench-app-gemstack/pages/login/+Page.tsx delete mode 100644 examples/bench-app-gemstack/pages/note/@id/+Page.tsx delete mode 100644 examples/bench-app-gemstack/server/ai.ts delete mode 100644 examples/bench-app-gemstack/server/api.ts delete mode 100644 examples/bench-app-gemstack/server/db.ts delete mode 100644 examples/bench-app-gemstack/server/index.ts delete mode 100644 examples/bench-app-gemstack/tsconfig.json delete mode 100644 examples/bench-app-gemstack/vite.config.ts delete mode 100644 examples/bench-app-next/.gitignore delete mode 100644 examples/bench-app-next/.npmrc delete mode 100644 examples/bench-app-next/README.md delete mode 100644 examples/bench-app-next/app/api/login/route.ts delete mode 100644 examples/bench-app-next/app/api/logout/route.ts delete mode 100644 examples/bench-app-next/app/api/notes/[id]/route.ts delete mode 100644 examples/bench-app-next/app/api/notes/[id]/summarize/route.ts delete mode 100644 examples/bench-app-next/app/api/notes/route.ts delete mode 100644 examples/bench-app-next/app/create-note-form.tsx delete mode 100644 examples/bench-app-next/app/layout.tsx delete mode 100644 examples/bench-app-next/app/login/login-form.tsx delete mode 100644 examples/bench-app-next/app/login/page.tsx delete mode 100644 examples/bench-app-next/app/logout-button.tsx delete mode 100644 examples/bench-app-next/app/note-actions.tsx delete mode 100644 examples/bench-app-next/app/notes/[id]/page.tsx delete mode 100644 examples/bench-app-next/app/page.tsx delete mode 100644 examples/bench-app-next/lib/auth.ts delete mode 100644 examples/bench-app-next/lib/db.ts delete mode 100644 examples/bench-app-next/lib/notes.ts delete mode 100644 examples/bench-app-next/lib/summarize.ts delete mode 100644 examples/bench-app-next/next.config.mjs delete mode 100644 examples/bench-app-next/package.json delete mode 100644 examples/bench-app-next/tsconfig.json diff --git a/benchmarks/README.md b/benchmarks/README.md deleted file mode 100644 index a20253d..0000000 --- a/benchmarks/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# GemStack AI benchmark: "our AI" vs Next.js - -Tracking issue: [#75](https://github.com/gemstack-land/gemstack/issues/75). This is the harness for measuring how an AI coding agent performs with the GemStack orchestration layer in reach versus a vanilla Next.js app, on two metrics: - -1. **Time-to-task** - wall clock from task start to the acceptance script passing. -2. **Human interventions** - count of times a human had to step in (see the rubric below). - -This is **not** the self-healing loop. It measures an AI agent building and changing apps. - -## Layout - -``` -benchmarks/ - README.md <- you are here - spec/ - product.md <- the product surface both apps implement (shared HTTP contract) - task-001-tags.md <- the Phase 0 task + acceptance criteria - tasks/ - task-001-tags/ - accept.mjs <- contract-level acceptance script (BASE_URL env, exit 0 = pass) -examples/ - bench-app-next/ <- Next.js baseline app (vanilla) - bench-app-gemstack/ <- Vike + React app wired with @gemstack/ai-* -``` - -Both apps implement the **same HTTP contract** (`spec/product.md`), so a single acceptance script runs against either by pointing `BASE_URL` at the running server. - -## Phases - -- **Phase 0** ([#78](https://github.com/gemstack-land/gemstack/issues/78)) - one task, both apps, manual stopwatch + manual intervention tally. Proves the method and the rubric. **(this directory)** -- **Phase 1** ([#79](https://github.com/gemstack-land/gemstack/issues/79)) - semi-automated runner over a 3 to 5 task set. -- **Phase 2** ([#80](https://github.com/gemstack-land/gemstack/issues/80)) - full suite, aggregator, committed baseline. - -## Running Phase 0 by hand - -For each app (`bench-app-next`, `bench-app-gemstack`): - -1. Reset the app to its starting commit (clean baseline). -2. Start the dev server, note the URL. -3. Start a stopwatch. Give the agent the task prompt from `spec/task-001-tags.md`. -4. Let the agent work. Tally every **human intervention** (rubric below). -5. After each agent step, run the acceptance script: `BASE_URL= node benchmarks/tasks/task-001-tags/accept.mjs`. Exit 0 means done; stop the stopwatch. -6. Record seconds, intervention count, and status (pass / DNF) in a run log. - -Stop at acceptance pass, or at the hard timeout / max-intervention cap (record as DNF). - -## Intervention rubric - -Counts as **one human intervention**: - -- a manual code correction by a human -- unblocking a stuck agent with a hint -- a clarification the agent had to ask before it could proceed -- an approval gate that required a human -- a manual retry / rerun a human had to trigger - -Does **not** count (this is the point of the orchestration layer): - -- the agent's own internal retries, planning, and autopilot worker dispatch -- skill / MCP tool calls the agent makes autonomously - -## Fairness rules - -- Same agent, same model, same harness on both sides. -- Both apps start from a clean, functionally-equivalent baseline implementing the contract. -- The acceptance gate is objective (the script's exit code); no human judgement. -- The Next.js app must be idiomatic, not a strawman. diff --git a/benchmarks/spec/product.md b/benchmarks/spec/product.md deleted file mode 100644 index 6c3bfcb..0000000 --- a/benchmarks/spec/product.md +++ /dev/null @@ -1,55 +0,0 @@ -# Product spec: the "Notes" app (shared by both benchmark apps) - -Both `bench-app-next` (Next.js) and `bench-app-gemstack` (Vike + React + `@gemstack/ai-*`) implement the **same product** and the **same HTTP contract**. Equivalence is what makes the comparison fair; the contract is what lets one acceptance script run against both. - -## Surface - -A single-user notes app: - -- **Auth** - email + password sign-in for one seeded user. A session cookie guards the app and the API. -- **CRUD resource: `notes`** - fields `id`, `title`, `body`, `createdAt`. List, create, view, delete. -- **AI feature: summarize** - produce a one-sentence summary of a note's body. - - GemStack app: via `@gemstack/ai-sdk` (the orchestration layer in reach). - - Next.js app: a vanilla inline provider call. - - Both default to a **deterministic stub model** (no network, no API key) so the baseline is reproducible. The stub returns the first sentence of the body, trimmed to <= 140 chars. Real providers are a later, opt-in concern. - -## Storage - -SQLite via `better-sqlite3` (already allowed in the workspace), one file per app, seeded on first boot. Same schema both sides: - -```sql -CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE NOT NULL, password TEXT NOT NULL); -CREATE TABLE notes ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - title TEXT NOT NULL, - body TEXT NOT NULL, - summary TEXT, - created_at TEXT NOT NULL -); -``` - -Seed user: `demo@example.com` / `password`. - -## HTTP contract (identical on both apps) - -All endpoints return JSON. Auth endpoints set / clear a `session` cookie; protected endpoints require it and return `401` without it. - -| Method | Path | Body | Success | Notes | -|---|---|---|---|---| -| POST | `/api/login` | `{ email, password }` | `200 { ok: true }` + `session` cookie | `401` on bad creds | -| POST | `/api/logout` | - | `200 { ok: true }` | clears cookie | -| GET | `/api/notes` | - | `200 { notes: Note[] }` | newest first | -| POST | `/api/notes` | `{ title, body }` | `201 { note: Note }` | | -| GET | `/api/notes/:id` | - | `200 { note: Note }` | `404` if absent | -| DELETE | `/api/notes/:id` | - | `200 { ok: true }` | | -| POST | `/api/notes/:id/summarize` | - | `200 { note: Note }` | sets `summary` | - -`Note` shape: `{ id: number, title: string, body: string, summary: string | null, createdAt: string }`. - -## UI - -Minimal but real React pages (server-rendered on both): a login page, a notes list (with a create form and per-note delete + summarize buttons), and a note detail page. Parity of surface matters more than polish. - -## Baseline = starting commit - -The committed state of each app is the Phase 0 **starting point**. Tasks (e.g. `task-001-tags`) ask the agent to extend it; the acceptance script verifies the result against the contract. diff --git a/benchmarks/spec/task-001-tags.md b/benchmarks/spec/task-001-tags.md deleted file mode 100644 index 7a2c048..0000000 --- a/benchmarks/spec/task-001-tags.md +++ /dev/null @@ -1,32 +0,0 @@ -# Task 001: add tags to notes - -A representative full-stack feature: it touches the data model, the HTTP contract, and the UI. The same prompt is given to the agent on both apps. - -## Agent prompt (verbatim, given on both apps) - -> Add tagging to notes. A note can have zero or more tags (short text labels). Update the create form so a user can enter comma-separated tags when creating a note. Show each note's tags in the list and on the detail page. Add the ability to list notes filtered to a single tag. Keep the existing HTTP contract working and extend it as described in the acceptance criteria. - -## Required contract changes - -- `Note` gains `tags: string[]` (empty array when none). -- `POST /api/notes` accepts an optional `tags: string[]` in the body and persists it. -- `GET /api/notes` accepts an optional `?tag=` query param; when present, only notes carrying that exact tag are returned. -- `GET /api/notes/:id` includes `tags`. - -## Acceptance criteria (checked by `tasks/task-001-tags/accept.mjs`) - -1. Log in as the seeded user. -2. Create note A with `tags: ["work", "urgent"]`. -3. Create note B with `tags: ["home"]`. -4. `GET /api/notes?tag=work` returns A and not B. -5. `GET /api/notes?tag=home` returns B and not A. -6. `GET /api/notes/` includes `tags` containing `work` and `urgent`. -7. `GET /api/notes` (no filter) returns both, each with a `tags` array. - -The script exits `0` only when all checks pass. Any non-zero exit is a fail. - -## Guardrails - -- Hard timeout: 30 minutes wall clock. -- Max interventions before DNF: 5. -- A UI must exist for entering and displaying tags (spot-checked by the human), but the automated gate is the contract above. diff --git a/benchmarks/tasks/task-001-tags/accept.mjs b/benchmarks/tasks/task-001-tags/accept.mjs deleted file mode 100644 index 94f14c9..0000000 --- a/benchmarks/tasks/task-001-tags/accept.mjs +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env node -// Contract-level acceptance check for task-001-tags. -// Runs against a running benchmark app. Usage: BASE_URL=http://localhost:3000 node accept.mjs -// Exit 0 = all checks pass; non-zero = fail. - -const BASE = (process.env.BASE_URL || 'http://localhost:3000').replace(/\/$/, '') - -let cookie = '' -let failures = 0 - -function check(label, cond) { - if (cond) { - console.log(` ok ${label}`) - } else { - console.log(` FAIL ${label}`) - failures++ - } -} - -async function req(method, path, body) { - const headers = { 'content-type': 'application/json' } - if (cookie) headers.cookie = cookie - const res = await fetch(`${BASE}${path}`, { - method, - headers, - body: body === undefined ? undefined : JSON.stringify(body), - }) - const setCookie = res.headers.get('set-cookie') - if (setCookie) cookie = setCookie.split(';')[0] - let json = null - try { - json = await res.json() - } catch { - /* non-JSON body */ - } - return { status: res.status, json } -} - -async function main() { - console.log(`acceptance: task-001-tags against ${BASE}`) - - // 1. login - const login = await req('POST', '/api/login', { email: 'demo@example.com', password: 'password' }) - check('login returns 200', login.status === 200) - check('login set a session cookie', cookie.length > 0) - - // 2 + 3. create two tagged notes - const a = await req('POST', '/api/notes', { title: 'Note A', body: 'Body A.', tags: ['work', 'urgent'] }) - check('create A returns 201', a.status === 201) - const b = await req('POST', '/api/notes', { title: 'Note B', body: 'Body B.', tags: ['home'] }) - check('create B returns 201', b.status === 201) - const aId = a.json?.note?.id - const bId = b.json?.note?.id - check('A has an id', typeof aId === 'number') - check('B has an id', typeof bId === 'number') - - // 4. filter by "work" -> A only - const work = await req('GET', '/api/notes?tag=work') - const workIds = (work.json?.notes || []).map((n) => n.id) - check('?tag=work returns A', workIds.includes(aId)) - check('?tag=work excludes B', !workIds.includes(bId)) - - // 5. filter by "home" -> B only - const home = await req('GET', '/api/notes?tag=home') - const homeIds = (home.json?.notes || []).map((n) => n.id) - check('?tag=home returns B', homeIds.includes(bId)) - check('?tag=home excludes A', !homeIds.includes(aId)) - - // 6. detail includes tags - const detail = await req('GET', `/api/notes/${aId}`) - const tags = detail.json?.note?.tags || [] - check('detail A includes tag work', tags.includes('work')) - check('detail A includes tag urgent', tags.includes('urgent')) - - // 7. unfiltered list returns both, each with a tags array - const all = await req('GET', '/api/notes') - const allNotes = all.json?.notes || [] - check('unfiltered list includes A and B', allNotes.some((n) => n.id === aId) && allNotes.some((n) => n.id === bId)) - check('every note has a tags array', allNotes.every((n) => Array.isArray(n.tags))) - - console.log(failures === 0 ? '\nPASS' : `\nFAIL (${failures} check(s) failed)`) - process.exit(failures === 0 ? 0 : 1) -} - -main().catch((err) => { - console.error('acceptance crashed:', err) - process.exit(2) -}) diff --git a/examples/bench-app-gemstack/.gitignore b/examples/bench-app-gemstack/.gitignore deleted file mode 100644 index 46c3982..0000000 --- a/examples/bench-app-gemstack/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Local SQLite database (seeded on first boot) -data/ -*.sqlite -*.sqlite-journal -*.sqlite-wal -*.sqlite-shm - -# Vite / Vike build artifacts -dist/ diff --git a/examples/bench-app-gemstack/README.md b/examples/bench-app-gemstack/README.md deleted file mode 100644 index 93deaa4..0000000 --- a/examples/bench-app-gemstack/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# bench-app-gemstack - -The **GemStack** side of the AI benchmark (see `benchmarks/`). A minimal but real -**Vike + React (SSR)** Notes app whose **AI summarize** feature is wired through -[`@gemstack/ai-sdk`](../../packages/ai-sdk) — the orchestration layer "in reach". - -Its twin, `bench-app-next`, implements the **same product and the same HTTP -contract** (`benchmarks/spec/product.md`) with a vanilla inline provider call, so -one acceptance script runs against either by pointing `BASE_URL` at the server. - -> Baseline scope: notes have `id`, `title`, `body`, `summary`, `createdAt` only. -> Tags are a later agent task and are intentionally **not** implemented here. - -## Run - -From the repo root, build the SDK once, then start the app: - -```bash -pnpm --filter @gemstack/ai-sdk build # the app imports the SDK's dist -cd examples/bench-app-gemstack -pnpm dev # http://localhost:3100 -``` - -Fixed port **3100** (override with `PORT`). It differs from the Next.js sibling's -3000 so both can run at once. The server is Express in Vite middleware mode: it -serves `/api/*` directly and hands every other route to Vike for React SSR. - -Seed user: `demo@example.com` / `password` (seeded into SQLite on first boot). - -## HTTP contract - -JSON everywhere. Auth endpoints set/clear a `session` cookie; protected -endpoints require it and return `401` without it. - -| Method | Path | Body | Success | -|---|---|---|---| -| POST | `/api/login` | `{ email, password }` | `200 { ok: true }` + cookie (`401` on bad creds) | -| POST | `/api/logout` | – | `200 { ok: true }` | -| GET | `/api/notes` | – | `200 { notes: Note[] }` (newest first) | -| POST | `/api/notes` | `{ title, body }` | `201 { note: Note }` | -| GET | `/api/notes/:id` | – | `200 { note: Note }` (`404` if absent) | -| DELETE | `/api/notes/:id` | – | `200 { ok: true }` | -| POST | `/api/notes/:id/summarize` | – | `200 { note: Note }` (sets `summary`) | - -`Note` = `{ id: number, title: string, body: string, summary: string | null, createdAt: string }`. - -## How summarize uses `@gemstack/ai-sdk` - -`server/ai.ts` registers a **deterministic stub provider** on the SDK's provider -seam (`AiRegistry.register` with a `ProviderFactory` / `ProviderAdapter`) and sets -it as the default model. `summarize()` then calls the SDK facade, `AI.prompt(body, -{ model: 'stub/summarize-v1' })` — so the path runs through the GemStack agent -loop, not a direct model call. The stub computes the result from the prompt it -receives (first sentence of the body, trimmed to ≤ 140 chars): no network, no API -key, fully reproducible. Swapping in a real provider later is a one-line change to -the model string. - -## Storage - -`better-sqlite3`, one file at `data/bench.sqlite` (git-ignored), created and -seeded on first boot. Schema matches the spec (`users`, `notes`). - -## Layout - -``` -server/ - index.ts Express + Vite-middleware dev server (API + Vike SSR catch-all) - api.ts the HTTP contract (session-cookie auth) - db.ts better-sqlite3 schema, seed, queries - ai.ts @gemstack/ai-sdk stub provider + summarize() -pages/ - +config.ts vike-react config - index/+Page.tsx notes list (create form, per-note delete + summarize) - login/+Page.tsx sign-in - note/@id/+Page.tsx note detail - api.ts client-side fetch wrapper over the contract -``` diff --git a/examples/bench-app-gemstack/package.json b/examples/bench-app-gemstack/package.json deleted file mode 100644 index c551898..0000000 --- a/examples/bench-app-gemstack/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "@gemstack/example-bench-app-gemstack", - "version": "0.0.0", - "private": true, - "description": "Benchmark baseline: a Vike + React (SSR) Notes app whose AI summarize feature is wired through @gemstack/ai-sdk. Twin of bench-app-next.", - "type": "module", - "scripts": { - "dev": "tsx server/index.ts" - }, - "dependencies": { - "@gemstack/ai-sdk": "workspace:^", - "better-sqlite3": "^12.11.1", - "express": "^5.2.1", - "react": "^19.2.0", - "react-dom": "^19.2.0", - "vike": "^0.4.260", - "vike-react": "^0.6.25" - }, - "devDependencies": { - "@types/better-sqlite3": "^7.6.13", - "@types/express": "^5.0.6", - "@types/node": "^20.0.0", - "@types/react": "^19.2.0", - "@types/react-dom": "^19.2.0", - "@vitejs/plugin-react": "^5.2.0", - "tsx": "^4.22.4", - "typescript": "^5.4.0", - "vite": "^7.3.6" - } -} diff --git a/examples/bench-app-gemstack/pages/+config.ts b/examples/bench-app-gemstack/pages/+config.ts deleted file mode 100644 index bd38f40..0000000 --- a/examples/bench-app-gemstack/pages/+config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import vikeReact from 'vike-react/config' -import type { Config } from 'vike/types' - -// SSR React app powered by vike-react. Client routing on by default. -export default { - extends: vikeReact, - title: 'Notes — bench-app-gemstack', -} satisfies Config diff --git a/examples/bench-app-gemstack/pages/api.ts b/examples/bench-app-gemstack/pages/api.ts deleted file mode 100644 index e782e91..0000000 --- a/examples/bench-app-gemstack/pages/api.ts +++ /dev/null @@ -1,49 +0,0 @@ -// Tiny client-side wrapper over the HTTP contract used by the React pages. -export interface Note { - id: number - title: string - body: string - summary: string | null - createdAt: string -} - -async function json(res: Response): Promise { - if (!res.ok) throw new Error(`${res.status} ${res.statusText}`) - return res.json() as Promise -} - -export const api = { - async login(email: string, password: string): Promise { - const res = await fetch('/api/login', { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ email, password }), - }) - if (!res.ok) throw new Error('Invalid credentials') - }, - async logout(): Promise { - await fetch('/api/logout', { method: 'POST' }) - }, - async list(): Promise { - const { notes } = await json<{ notes: Note[] }>(await fetch('/api/notes')) - return notes - }, - async create(title: string, body: string): Promise { - const res = await fetch('/api/notes', { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ title, body }), - }) - return (await json<{ note: Note }>(res)).note - }, - async get(id: number): Promise { - return (await json<{ note: Note }>(await fetch(`/api/notes/${id}`))).note - }, - async remove(id: number): Promise { - await fetch(`/api/notes/${id}`, { method: 'DELETE' }) - }, - async summarize(id: number): Promise { - const res = await fetch(`/api/notes/${id}/summarize`, { method: 'POST' }) - return (await json<{ note: Note }>(res)).note - }, -} diff --git a/examples/bench-app-gemstack/pages/index/+Page.tsx b/examples/bench-app-gemstack/pages/index/+Page.tsx deleted file mode 100644 index 089f9ac..0000000 --- a/examples/bench-app-gemstack/pages/index/+Page.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { useEffect, useState } from 'react' -import { api, type Note } from '../api.js' - -export default function NotesPage() { - const [notes, setNotes] = useState([]) - const [title, setTitle] = useState('') - const [body, setBody] = useState('') - const [loading, setLoading] = useState(true) - - async function refresh() { - try { - setNotes(await api.list()) - } catch { - window.location.href = '/login' - } finally { - setLoading(false) - } - } - - useEffect(() => { - void refresh() - }, []) - - async function onCreate(e: React.FormEvent) { - e.preventDefault() - if (!title.trim() || !body.trim()) return - await api.create(title, body) - setTitle('') - setBody('') - await refresh() - } - - async function onDelete(id: number) { - await api.remove(id) - await refresh() - } - - async function onSummarize(id: number) { - await api.summarize(id) - await refresh() - } - - async function onLogout() { - await api.logout() - window.location.href = '/login' - } - - return ( -
-
-

Notes

- -
- -
- setTitle(e.target.value)} - style={{ display: 'block', width: '100%', marginBottom: 8 }} - /> -