Skip to content
Open
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
129 changes: 58 additions & 71 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,98 +1,85 @@
# vinext-starter
# Anatomy Atelier

A clean full-stack starter running on
[vinext](https://github.com/cloudflare/vinext), with optional Cloudflare D1 and
Drizzle support.
An interactive 3D anatomy atlas — nine organs you can turn, section and read
about, presented as illustrated specimens rather than clinical renders.

## Prerequisites

- Node.js `>=22.13.0`

## Quick Start
## Quick start

```bash
npm install
npm run dev
npm run build
```

This starter does not use `wrangler.jsonc`.
## Commands

## Included Shape
- `npm run dev` — start the local development server
- `npm run build` — build the worker bundle with vinext
- `npm run build:next` — plain `next build`, used by the Vercel deployment
- `npm start` — serve the built worker
- `npm test` — build, then assert the rendered HTML
- `npm run lint` — ESLint over the project

- edit site code under `app/`
- `.openai/hosting.json` declares optional Sites D1 and R2 bindings
- `vite.config.ts` simulates declared bindings for local development
- `db/schema.ts` starts intentionally empty
- `examples/d1/` contains an optional D1 example surface
- `drizzle.config.ts` supports local migration generation when needed
## How it fits together

## Workspace Auth Headers
Everything on screen is driven by one data file. [`app/lib/anatomy-data.ts`](app/lib/anatomy-data.ts)
holds all nine organs — copy, accent colour, model path, and hand-authored
hotspot coordinates. Adding an organ means adding an entry there and dropping
its assets in `public/`.

OpenAI workspace sites can read the current user's email from
`oai-authenticated-user-email`.

SIWC-authenticated workspace sites may also receive
`oai-authenticated-user-full-name` when the user's SIWC profile has a non-empty
`name` claim. The full-name value is percent-encoded UTF-8 and is accompanied by
`oai-authenticated-user-full-name-encoding: percent-encoded-utf-8`.

Treat the full name as optional and fall back to email when it is absent:

```tsx
import { headers } from "next/headers";

export default async function Home() {
const requestHeaders = await headers();
const email = requestHeaders.get("oai-authenticated-user-email");
const encodedFullName = requestHeaders.get("oai-authenticated-user-full-name");
const fullName =
encodedFullName &&
requestHeaders.get("oai-authenticated-user-full-name-encoding") ===
"percent-encoded-utf-8"
? decodeURIComponent(encodedFullName)
: null;

const displayName = fullName ?? email;
// ...
}
```
app/
page.tsx renders <AnatomyApp />
components/
AnatomyApp.tsx shell: library, info panel, cards, modals
OrganViewer.tsx React side of the 3D viewer and its tools
lib/
anatomy-data.ts the nine organs — single source of truth
three/
viewer.ts renderer, camera, lighting, tools, render loop
loaders.ts GLB loading, normalisation, LRU cache
hotspots.ts in-scene label dots and surface snapping
dispose.ts geometry/material/texture teardown
worker/index.ts Cloudflare worker entry and image optimisation
public/models/*.glb one model per organ
public/anatomy/<organ>/ thumbnail, illustration, tissue plate, comparison
```

## Optional Dispatch-Owned ChatGPT Sign-In
### The viewer

Import the ready-to-use helpers from `app/chatgpt-auth.ts` when the site needs
optional or required ChatGPT sign-in:
`AnatomyViewer` owns the WebGL scene directly rather than through a React
renderer, so a spinning model costs no React renders. Two things are worth
knowing before changing it:

- Use `getChatGPTUser()` for optional signed-in UI.
- Use `requireChatGPTUser(returnTo)` for server-rendered pages that should send
anonymous visitors through Sign in with ChatGPT.
- Use `chatGPTSignInPath(returnTo)` and `chatGPTSignOutPath(returnTo)` for
browser links or actions.
- Pass a same-origin relative `returnTo` path for the destination after sign-in
or sign-out. The helper validates and safely encodes it.
- Mark protected pages with `export const dynamic = "force-dynamic"` because
they depend on per-request identity headers.
- **It draws on demand.** The loop only renders when something has actually
moved, tracked by a `dirty` flag plus a `busyUntil` window for animations. It
also stops entirely when the viewer scrolls out of view or the tab is hidden.
Anything that changes the scene must set `dirty`.
- **Organs are normalised into a fixed cube** (`FIT_SIZE`) by their longest
axis, which is what lets hotspot coordinates in `anatomy-data.ts` mean the
same thing for every model.

Dispatch owns `/signin-with-chatgpt`, `/signout-with-chatgpt`, `/callback`, the
OAuth cookies, and identity header injection. Do not implement app routes for
those reserved paths. Routes that do not import and call the helper remain
anonymous-compatible.
Hotspot dots are sprites inside the scene, not DOM overlays. They are snapped
onto the mesh surface once at load by a single pass over the vertices, filtered
by direction before distance so a dot cannot snap through to the far side.

SIWC establishes identity only; it does not prove workspace membership. Use the
Sites hosting platform's access policy controls for workspace-wide restrictions,
or enforce explicit server-side membership or allowlist checks.
## Deployment

Use SIWC for account pages, user-specific dashboards, saved records, and write
actions tied to the current ChatGPT user. Leave public content anonymous.
Two targets are configured:

## Useful Commands
- **Vercel** — [`vercel.json`](vercel.json) overrides the build to `next build`
- **Cloudflare Workers** — `npm run build` via vinext, with local bindings
simulated in [`vite.config.ts`](vite.config.ts)

- `npm run dev`: start local development
- `npm run build`: verify the vinext build output
- `npm test`: build the starter and verify its rendered loading skeleton
- `npm run db:generate`: generate Drizzle migrations after schema changes
## Assets

## Learn More
3D models live in `public/models/` as `.glb`, decoded with meshopt.
`public/draco/` ships the Draco decoder for models that need it.

- [vinext Documentation](https://github.com/cloudflare/vinext)
- [Drizzle D1 Guide](https://orm.drizzle.team/docs/get-started/d1-new)
Per-organ artwork lives in `public/anatomy/<organ>/` as `thumb`, `organ`,
`microscopic`, `compare` and `location` WebP files. Organs without the painted
set fall back to their accent glyph — see the `illustrated` flag in
`anatomy-data.ts`.
86 changes: 0 additions & 86 deletions app/chatgpt-auth.ts

This file was deleted.

8 changes: 0 additions & 8 deletions app/lib/three/tsl-materials.ts

This file was deleted.

14 changes: 0 additions & 14 deletions db/index.ts

This file was deleted.

4 changes: 0 additions & 4 deletions db/schema.ts

This file was deleted.

7 changes: 0 additions & 7 deletions drizzle.config.ts

This file was deleted.

5 changes: 0 additions & 5 deletions drizzle/meta/_journal.json

This file was deleted.

58 changes: 0 additions & 58 deletions examples/d1/app/api/notes/route.ts

This file was deleted.

9 changes: 0 additions & 9 deletions examples/d1/db/schema.ts

This file was deleted.

Loading