# 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**: use Vite hashed assets generated from the installed Three.js loader imports. Stage only single-thread `web-ifc.wasm` under `public/web-ifc`. 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/ web-ifc/ # single-thread IFC decoder WASM samples/ # 배포 demo model previews/ # pre-rendered WebP/WebM placeholders dist/assets/ # Vite hashed Draco/Basis decoder assets 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 ` — 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).