# CLAUDE.md — hmwebviewer (Three.js 3D Viewer)

Hybrid SSR+CSR web 3D model viewer. Spec: `3d_viewer_architecture_spec.pdf`.

## ⚠️ First action on EVERY session / agent start

Read these two files before doing anything:
1. [`PLAN.md`](./PLAN.md) — what work remains, who owns what, task breakdown.
2. [`PROGRESS.md`](./PROGRESS.md) — what is done, current state, blockers, decisions log.

If either file is missing or empty, treat the project as not started and bootstrap from the spec.

## What we are building

A browser 3D model viewer with **two load paths**:

- **Path A — Server asset (SSR + CSR)**: server ships a pre-rendered 360° animated WebP/WebM placeholder + HTML/CSS skeleton. Three.js loads + decodes the real Draco/KTX2 model in the background. On ready, fade WebP → canvas (opacity 0.5s).
- **Path B — Local file (Drag & Drop, CSR only)**: user drops a `.glb`/`.gltf`. `URL.createObjectURL(file)` → `GLTFLoader.load` → decode → add to scene → `URL.revokeObjectURL`.

Target: perceived load < 3s, no frame drops, smooth GPU.

## Locked technical decisions

These are decided. Do not relitigate without explicit user approval.

- **Three.js** + `GLTFLoader` + `DRACOLoader` + `KTX2Loader` on ONE shared loader instance each (multiple DRACOLoader instances crash — three.js #22445).
- **Geometry**: Draco. **Textures**: KTX2 / Basis Universal. Both compatible on same loader.
- **Decoder hosting**: CDN by default; copy WASM to `public/draco` + `public/basis` if offline/CSP required. Pin decoder version to the three.js version in use.
- **Local file**: `createObjectURL` + revoke after load. For repeat/cached loads, `FileReader`→`ArrayBuffer`→`GLTFLoader.parse()`.
- **Build**: Vite. Draco worker spawns via BLOB URL — decoder files must be reachable at the configured path at runtime.
- **Asset pre-processing (offline)**: `gltf-transform` (Draco + KTX2) — preferred over `gltf-pipeline`. Meshopt is a viable Draco alternative.
- **Pre-render pipeline (offline)**: Blender headless CLI 360° turntable frames → assemble to animated WebP (alpha) / WebM VP9. OR Puppeteer + headless Three.js `page.screenshot({type:'webp'})` per rotation step.
- **Renderer**: WebGLRenderer (stable baseline). WebGPU renderer = future option, not now.

Full rationale in user memory: `~/.claude/projects/d--MYCLAUDE-PROJECT-hmwebviewer/memory/`.

## Architecture map (target)

```
src/
  viewer/
    ThreeDViewer.ts        # core class: init, loadServerAsset, loadLocalFile, executeHydration, toggleUI
    loaders.ts             # singleton GLTFLoader + DRACOLoader + KTX2Loader setup
    hydration.ts           # WebP placeholder -> canvas fade coordination
  dnd/
    dropzone.ts            # HTML5 drag&drop, file validation, createObjectURL/revoke
  ui/
    progress.ts            # load progress bar
  scenes/                  # per-model scene configs
public/
  draco/                   # decoder WASM (if bundled)
  basis/                   # KTX2 decoder WASM (if bundled)
  previews/                # pre-rendered WebP/WebM placeholders
tools/
  preprocess.mjs           # gltf-transform Draco+KTX2 pipeline
  prerender/               # Blender/Puppeteer turntable -> WebP
```

## How to work here (rules)

- **Surgical changes.** Touch only what a task requires. Match existing style.
- **Simplicity first.** Minimum code that solves the task. No speculative features/config.
- **Verify before claiming done.** Run the relevant check (build / typecheck / load test) and quote real output.
- **Keep PLAN.md + PROGRESS.md current.** Update the task status when you start/finish a unit of work. Other agents depend on it.
- **Memory is durable; PLAN/PROGRESS are working state.** Stable decisions → memory (via the memory dir). Transient task state → PLAN/PROGRESS.

## Commands (see `.claude/commands/`)

- `/bootstrap` — scaffold project from spec (run once, when PLAN says not started).
- `/next-task` — read PLAN + PROGRESS, pick the next ready task, assign self.
- `/report` — append current status to PROGRESS.md.
- `/optimize-asset <file>` — run gltf-transform Draco+KTX2 on an asset.
- `/verify-viewer` — build + load smoke test.

## Agents (see `.claude/agents/`)

Specialized subagents for parallelizable work — `viewer-core`, `dnd-handler`, `asset-pipeline`, `hydration`, `reviewer`. Invoke via the Agent tool / Task delegation. Details in each agent file.

## Reinforced structure — agent ↔ skill ↔ command ↔ task map

| PLAN task(s) | Owning agent | Skill to load | Command |
|---|---|---|---|
| P0 | task-lead → setup | threejs-viewer | `/bootstrap` |
| P1-1..P1-4 | viewer-core | threejs-viewer | `/verify-viewer` |
| P2-1,P2-2 | dnd-handler | threejs-viewer | `/verify-viewer` |
| P3-1..P3-3 | hydration | threejs-viewer | `/verify-viewer` |
| P4-1..P4-3 | asset-pipeline | threejs-viewer | `/optimize-asset` |
| P5-* | reviewer | threejs-viewer | `/verify-viewer`, `/report` |
| (any) | task-lead | task-graph | `/next-task`, `/report` |

**Load the `threejs-viewer` skill** whenever touching runtime viewer/dnd/asset/hydration code — it holds the locked patterns (singleton loaders, Blob URL lifecycle, hydration gating). **Load `task-graph`** when picking up or dispatching work.

## Runbook — how a work session goes

1. SessionStart hook reminds you. Read `PLAN.md` + `PROGRESS.md`.
2. Run `/next-task` (or have `task-lead` agent do it) → get the next ready task + owning agent.
3. Dispatch the owning agent with task ID, scope, acceptance criteria. Dispatch independent agents in ONE message (parallel).
4. On completion: owning agent flips PLAN row → `done`, runs `/report` to append PROGRESS.md.
5. `reviewer` runs after functional waves; `/verify-viewer` gates release.

## Hooks
- `SessionStart` → `.claude/hooks/session-start.sh` prints the "read PLAN + PROGRESS" reminder.
- `progress-nudge.sh` exists under `.claude/hooks/` for PostToolUse nudge to update PROGRESS after runtime edits — add its `PostToolUse` entry to `settings.json` if desired (not auto-wired).
