# Project export: TCM Coach

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: TCM Coach uses AR to guide hand acupressure placement and computer vision to give real-time feedback on pressing rhythm, accuracy, and stability.
- Devpost: https://devpost.com/software/acuguide
- GitHub: https://github.com/kany-e/AcuGuide
- Video: https://www.youtube.com/embed/Xj5hOPyIVpU?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — kany-e (11 commits), Iridium0317 (5 commits), Claude Sonnet 4.6 (1M context) (4 commits), RIceRRRRR (1 commits)

## Devpost submission (written by the team)

### Overview

Interactive guidance for TCM self-care routines.

### Inspiration

Interactive TCM Coach was inspired by a real self-care problem from our team: someone wanted to try hand acupressure for period discomfort, but did not know whether the position, rhythm, or movement was correct. Static diagrams and videos were not enough. We wanted to build a system that gives immediate visual feedback while keeping clear safety boundaries. What It Does Interactive TCM Coach uses AR to guide users toward the correct hand acupressure area, then uses computer vision to analyze pressing rhythm, accuracy, and stability in real time. Instead of only showing where to press, it helps users understand whether they are doing the routine correctly. The demo also includes a front-end 3D body model with meridian visualization, so users can see the broader route/context of a point instead of only seeing a flat instruction. How We Built It We built a web-based demo with React, camera input, MediaPipe hand tracking, AR-style overlays, front-end 3D modeling, and computer vision logic for hand position and pressing frequency. We also created a video-to-JSON pipeline to extract fingertip position and rhythm features from training videos, which can support future model training. Challenges We Ran Into Fine-tuning a CV model to recognize exact acupressure position was too large for the hackathon scope. Our workaround was to use AR target circles to mark the intended area, so the software does not need to fully identify the acupoint from scratch. It only needs to check whether the user's finger reaches the marked region. Overlapping fingers made fingertip-level tracking noisy. Frequency and stability detection were also inconsistent: sometimes the signal worked, but sometimes it broke when the hand moved quickly or the finger was partially hidden. Since AR handles position guidance, we simplified frequency detection by measuring motion around the target region, such as centroid motion frequency, instead of requiring perfect fingertip landmark detection every frame. Fitting long meridian paths onto a 3D human model was difficult because the line has to follow the body surface instead of floating through the model. Our workaround was to split each long meridian into smaller segments, then fit and adjust the segments one by one. iOS camera access was also tricky during development because Safari is strict about camera permissions and secure origins. We had to adjust the local setup so the phone could open the demo with camera access instead of only testing on desktop. Accomplishments That We're Proud Of We are proud that we kept the idea small enough to actually demo. Instead of trying to build a huge health AI product, we focused on one simple user pain point: people can read acupressure instructions online, but they still do not know if they are pressing in the right area. We are also proud of the workaround we found for the vision problem. Exact acupoint detection is hard, especially in a hackathon, so we used hand landmarks and rough target regions to make the guidance understandable enough for a working demo. What We Learned We learned a lot about a new kind of software engineering workflow with AI. The challenge is not just writing code faster, but learning how to collaborate, define success, keep the right context, and design useful evaluation systems while the project is moving quickly. We also learned how to get the key information needed to contribute even when we did not fully understand every part of the project. Instead of trying to know everything first, we focused on the important constraints, acceptance criteria, and failure cases, then used those to guide the build. What's Next for Interactive TCM Coach Session: Expand from one-time pressing feedback into guided course-style sessions, where users complete structured routines over time instead of a single action. LLM: Add an assistant with RAG over curated TCM and safety content, so users can ask self-care questions while the system stays grounded in reviewed sources. More Content: Support more acupressure points, routines, and hand/wrist regions, with AR guidance, computer vision feedback, and progress summaries across sessions.

## README (from the GitHub repository)

# AcuGuide — Native iOS (SwiftUI)

A camera-guided **acupressure coach** for safe, non-diagnostic self-care, in an ink-and-gold
palette: a **3D body atlas** of 33 sourced points across fourteen meridians, an **AR coaching
window** (Vision hand-pose, 8 camera-coached points), a **guided timer** for everything else, and a
**fully on-device AI chat coach**.

> Status: **builds, tests, and ships green.** The Xcode project is generated from `project.yml`
> (XcodeGen). `make build` (generic iOS device, unsigned) and `make test` both pass — **136 tests,
> 0 failures**. Every branch is gated by `.github/workflows/merge-gate.yml` before it may merge.
>
> **Not yet verified on real hardware:** the meridian rendering and the hand-detection feel. Camera
> and Vision hand-pose do not run in the Simulator, so no amount of test coverage speaks to them.

## Repository layout
The iOS app **is** this repository — it lives at the root.

```
AcuGuide/               # app sources (SwiftUI, Vision, SceneKit) — 51 files
AcuGuideTests/          # unit tests — 11 files, 136 tests
project.yml             # XcodeGen spec — the project is generated, never hand-assembled
Makefile                # make project / build / test
scripts/                # safety_scan.py (banned-claim scanner), pick_simulator.sh
.github/workflows/      # merge-gate.yml — the required check for anything landing on main
claude-deliverables/    # CV research, acupoint sources, and the replay fixtures the tests bundle
tools/voice/            # offline Kokoro renderer for the pre-rendered voice clips
store/metadata/         # App Store listing copy (en-US, zh-Hans) — scanned by the safety gate
licenses/               # full third-party license texts (OFL 1.1)
docs/                   # privacy policy, release checklist, pre-release vision, icon drafts
archive/                # superseded work, kept for reference — nothing here is built (see below)
CLAUDE.md               # working agreement + the traps that bite (read this before changing things)
acuguide-dashboard.html # project dashboard, regenerated from live git state
```

`archive/` holds the pre-iOS work: `web-camera-coach/` (the React + MediaPipe browser prototype
and its build config), `MaiApp/` (the three.js 诗词山河 meridian atlas the visual design came from),
`demo-app/` (the original vanilla-JS prototype), plus the `hackathon-md/` and `product/` planning
docs and the old web-era `README-web-apps.md`. **Nothing in `archive/` is part of the build.**

## What's in the app
Three tabs — **Atlas · Practice · Coach AI** (`RootView.swift`).

| Area | Files | What it does |
|---|---|---|
| **Atlas** | `Body3DView`, `HandModel3DView`, `Meridians`, `Acupoints` | SceneKit body (GLTFKit2), drill down body → hand → back; 33 bilingual points on 14 meridians, each with location, plain-language "how to find it", traditional-use text and read-aloud |
| **Camera coach** | `ARCoachView`, `CameraCoach`, `Coach`, `HandModel`, `CameraGate`, `CameraSetupCard` | Forced safety gate → first-run setup card → find-your-spot locate step → live overlay (ring / press dot / feedback) → recap. Position + hold + steadiness; **no cadence** |
| **Guided timer** | `TimerSession`, `SessionUI`, `Routines` | The camera-free path for the other 25 points, plus multi-step routines |
| **Calibration** | `PointCalibration`, "Your spots" in `RootView` | Saves *your* spot per point as a delta in the canonical hand frame; re-findable and resettable |
| **Chat coach** | `ChatView`, `ChatLLM` | Bilingual wellness Q&A over the atlas. Crisis and red-flag routing run **first**, before any answer |
| **History** | `PracticeStore`, `HistoryView` | Local-only practice log with export (ShareLink) and in-app deletion |
| **Voice** | `Speech` (incl. `AtlasSpeaker`), `VoiceClips`, `LocateVoice` | Pre-rendered clip playback, spoken cues, read-aloud, hands-free "this is my spot" |
| **Settings & reference** | `Settings`, `SourcesView`, `CreditsView` (holds `PrivacyView`), `OnboardingView` | Language, on-device AI toggle, daily reminder, sources, credits, privacy |

## Setup (Xcode, on your Mac)
The project is **generated from `project.yml`** — no hand-assembly. You need
[XcodeGen](https://github.com/yonaskolb/XcodeGen): `brew install xcodegen`.

1. **Generate + open:**
   ```bash
   make project          # = xcodegen generate  → AcuGuide.xcodeproj (git-ignored)
   open AcuGuide.xcodeproj
   ```
   Bundle id `app.acuguide.ios`, deployment target **iOS 16.0**, SwiftUI lifecycle,
   portrait-locked. The camera usage string and `AccentColor`/`AppIcon` assets are baked in.
2. **Build / test from the CLI** (no Xcode UI needed):
   ```bash
   make build            # xcodebuild build for a generic iOS device (signing off)
   make test             # xcodebuild test (auto-picks a simulator; override with SIM="iPhone 16")
   ```
   `make test` is the gate — CI re-runs it on a different toolchain plus a device build, the stress
   suite five times, and the whole suite twice to catch `UserDefaults` state leaks.
3. **Signing:** set your team on the `AcuGuide` target to run on a physical device.
4. **3D models:** loaded at runtime from `AcuGuide/Resources/*.glb` via the **GLTFKit2** Swift
   package — no usdz conversion. `make project` resolves the package (network needed once); if an
   SPM cleanup nukes the artifact, re-resolve before assuming the code broke. A capsule fallback
   shows only if an asset is missing.
5. **Chat coach:** fully **offline**. On iOS 26+ with Apple Intelligence it answers free-form
   questions using Apple's on-device `FoundationModels`; elsewhere it falls back to the built-in
   bilingual atlas helper. No API key, no network, no accounts. Red-flag symptoms → stop-and-seek
   care; crisis messages route to real help and never to acupressure.
6. **Run on a real device** — camera and Vision hand-pose don't work in the Simulator.

## Voice
Every spoken line is a fixed string, so the whole script is **pre-rendered offline** into **102
bilingual AAC clips** (`AcuGuide/VoiceClips/`, ~5.3 MB, Kokoro / Apache-2.0; pipeline in
`tools/voice/`). Playback is the primary path; `AVSpeechSynthesizer` is the fallback for any line
that has drifted from its clip.

> **The clip key is `sha256("<locale>|<normalized text>")`** — so *rewording any spoken string
> orphans its clip* and silently drops that line to the robotic fallback. `VoiceScriptTests` fails
> in both directions (missing clip / orphaned clip); when it does, re-render with
> `tools/voice/render_voice.py`. See `CLAUDE.md` before touching spoken copy.

Cues fire on phase change only; there's a mute toggle. `.ambient` session for coach cues (respects
the silent switch), `.playback` for read-aloud. **Haptics** (`CoreHaptics`, `UIFeedbackGenerator`
fallback): a tick on first entering the target, a success pattern at COMPLETE; nothing during
NO_HAND / WRONG_FACE.

## Safety (immutable — enforced by tests and CI, not convention)
- No **treat / cure / heal / diagnose** copy anywhere, in either language. `scripts/safety_scan.py`
  scans Swift string literals *and* the store metadata; it runs in CI and locally.
- The safety gate before the camera is **forced** — it cannot be skipped, and it stays scrollable so
  large Dynamic Type can never push its only exit off-screen.
- "Felt worse" after a routine → **stop guidance, never "continue"** (including for legacy stored
  keys).
- Each point's own caution is shown **where the press happens**, not only on the atlas card.
- **LI4 is excluded entirely** (pregnancy-contraindicated) — which is *why* the app needs no
  pregnancy screening. CI fails if it reappears.
- Crisis and red-flag routing run before any chat answer.
- Acupoint data is sourced and adversarially verified — see `claude-deliverables/references/` and
  the in-app Sources screen.

## Licensing
AcuGuide's own source is **proprietary — all rights reserved** ([LICENSE](LICENSE)). The repo is
public so the source can be read and reviewed; it is not open-sourc

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 120 recognized source files, 491 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Swift (language) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers

## Codebase structure (from repository index)

### Files (120 of 143)

```
.gitignore
claude-deliverables/data/acuguide_hand_points.json
claude-deliverables/fixtures/fixture_1_te3_correct_good_rhythm.json
claude-deliverables/fixtures/fixture_2_te3_wrong_position.json
claude-deliverables/fixtures/fixture_3_pc6_correct_too_fast.json
claude-deliverables/fixtures/fixture_4_no_hand_then_partial.json
claude-deliverables/fixtures/fixture_5_te3_full_flow.json
claude-deliverables/fixtures/generate_fixtures.py
claude-deliverables/README.md
claude-deliverables/references/acupoint_sources_by_type.md
claude-deliverables/references/eight_points_citations.md
claude-deliverables/references/hand_wrist_8_acupoints_map.md
claude-deliverables/references/hand_wrist_acupoints_research_links.md
claude-deliverables/specs/capture_coaching_protocol.md
claude-deliverables/specs/claude_code_ar_integration_prompt.md
claude-deliverables/specs/claude_code_ar_integration_TE3_prompt.md
claude-deliverables/specs/claude_code_build_prompt.md
claude-deliverables/specs/claude_code_cadence_3way_prompt.md
claude-deliverables/specs/claude_code_calibrate_validate_prompt.md
claude-deliverables/specs/claude_code_diagnose_detection_prompt.md
claude-deliverables/specs/claude_code_merge_camera_into_maiapp.md
claude-deliverables/specs/claude_code_perception_prompt.md
claude-deliverables/specs/claude_code_realdata_adapter_prompt.md
claude-deliverables/specs/claude_code_validate_ab_paths_prompt.md
claude-deliverables/specs/cv_two_person_split_zh.md
claude-deliverables/specs/cv_two_person_split.md
claude-deliverables/specs/demo_day_plan.md
claude-deliverables/specs/demo_integration_spec.md
claude-deliverables/specs/min_cv_demo_scope_v2.md
claude-deliverables/specs/reshoot_shot_list.md
claude-deliverables/specs/tap_cadence_spec.md
CLAUDE.md
hackathon - md/AcuSpots.md
hackathon - md/demo-flow.md
hackathon - md/development-plan.md
hackathon - md/features/01-symptom-selection.md
hackathon - md/features/02-routine-library.md
hackathon - md/features/03-camera-hand-tracking.md
hackathon - md/features/04-visual-feedback.md
hackathon - md/features/05-ai-coach-safety.md
hackathon - md/features/06-completion-recap.md
hackathon - md/market-value-prop.md
hackathon - md/README.md
hackathon - md/requirements.md
hackathon - md/risks-and-safety.md
hackathon - md/team-roles.md
index.html
MaiApp/index.html
MaiApp/model.glb
MaiApp/package.json
MaiApp/postcss.config.js.txt
MaiApp/README.md
MaiApp/src/Ask.jsx
MaiApp/src/Body3D.jsx
MaiApp/src/Coach.jsx
MaiApp/src/data.js
MaiApp/src/HandView.jsx
MaiApp/src/main.jsx
MaiApp/src/MeridianAtlas.jsx
MaiApp/src/styles.css
MaiApp/vite.config.js
MaiApp/vite.config.js.timestamp-1782063087101-6f88bfad4f706.mjs
MaiApp/vite.config.js.timestamp-1782063097211-10258749b548a.mjs
MaiApp/vite.config.js.timestamp-1782063117510-a4401295f217c.mjs
MaiApp/vite.config.js.timestamp-1782063137269-437192f4e4ecc.mjs
MaiApp/vite.config.js.timestamp-1782063254175-e99052b5564db.mjs
MaiApp/vite.config.js.timestamp-1782063273453-3d747eaf6971f.mjs
MaiApp/vite.config.js.timestamp-1782063450484-e854b33d16771.mjs
package.json
postcss.config.js
product/acceptance-checklist.md
product/collaterals/01-one-pager.md
product/collaterals/02-pitch-script.md
product/collaterals/03-demo-runbook.md
product/collaterals/04-devpost-final.md
product/collaterals/05-ui-copy.md
product/collaterals/06-slide-outline.md
product/collaterals/07-judge-qa-cards.md
product/collaterals/08-booth-and-sponsor-talk-tracks.md
product/collaterals/09-demo-video-shot-list.md
product/collaterals/10-final-submission-checklist.md
product/collaterals/README.md
product/content-routines.md
product/demo-script.md
product/devpost-draft.md
product/devpost-submission.md
product/judge-qa.md
product/product-brief.md
product/product-owner-plan.md
product/README.md
product/safety-copy.md
product/source-notes.md
product/user-personas.md
product/value-prop.md
README.md
src/App.tsx
src/data/acupoints.json
src/hooks/useCoachingState.ts
src/hooks/useHandClassifier.ts
src/hooks/useMediaPipe.ts
src/hooks/usePressDetection.ts
src/hooks/useTTS.ts
src/index.css
src/main.tsx
src/pages/AboutPage.tsx
src/pages/AskPage.tsx
src/pages/CameraPage.tsx
src/pages/HomePage.tsx
src/pages/RecapPage.tsx
src/pages/RoutinePage.tsx
src/pages/SafetyPage.tsx
src/types/index.ts
src/utils/drawOverlay.ts
src/utils/geometry.ts
src/utils/landmarks.ts
src/utils/oneEuro.ts
src/vite-env.d.ts
tailwind.config.js
tsconfig.app.json
tsconfig.json
[23 more files omitted for size]
```

### Dependencies

- MaiApp/package.json: @react-three/drei@9.122.0, @react-three/fiber@8.18.0, @react-three/postprocessing@2.19.1, @vitejs/plugin-react@^4.3.4, react@^18.3.1, react-dom@^18.3.1, three@0.184.0, vite@^5.4.11
- package.json: @mediapipe/tasks-vision@^0.10.14, @types/node@^26.0.0, @types/react@^18.3.5, @types/react-dom@^18.3.0, @vitejs/plugin-react@^4.3.1, autoprefixer@^10.4.20, postcss@^8.4.41, react@^18.3.1, react-dom@^18.3.1, react-router-dom@^6.26.1, tailwindcss@^3.4.10, typescript@^5.5.3, vite@^5.4.2, vite-plugin-mkcert@^1.17.12

### Recent commits (newest first)

- MaiApp: restore Coach/Ask, lock-on ring + drift fixes, diagnostic recap, Too-fast banner, Ask offline point lookup, EN meridian labels, TCN Coach title
- MaiApp: add AR Coach + AI Ask views; fix styles.css/Body3D corruption; re-theme camera app; remove redundant landing/acuguide html
- Merge branch 'main' of https://github.com/kany-e/AcuGuide
- Add native SwiftUI starter: 3D atlas + AR coach (Vision hand-pose) + AI chatbot
- add md
- TE3 camera integration + MaiApp merge; videos kept out of git
- Fix WRONG_FACE detection: use per-hand MediaPipe handedness for face check
- Use face orientation to discriminate target vs pressing hand
- Fix hand face detection and simplify overlay pipeline
- Move video-to-json extractor into subfolder
- Add hackathon changes
- Add AcuGuide Hand Coach — full app initial commit
- changed title
- added json
- update with Web-App
- Rename AcuSpots to AcuSpots.md
- Rename PointLandmark to PointLandmark.json
- Rename Citations to Citations.md
- Create Citations
- Create PointLandmark dataset for acupressure coaching

## Key source files (fetched from GitHub, selected and truncated for size)

### CLAUDE.md

```markdown
# CLAUDE.md — AcuGuide Hand Coach (AcuGuide/ repo)

## 项目状态（截至 June 20, 2026 深夜）

React + Vite + TypeScript + TailwindCSS 应用，已可在 iPhone 上通过 HTTPS 运行。

### 已完成 ✅

| 模块 | 文件 | 状态 |
|------|------|------|
| 脚手架 | package.json / vite.config.ts / tsconfig*.json | 完成 |
| 类型定义 | src/types/index.ts | 完成 |
| 穴位数据 | src/data/acupoints.json | 完成（从 PointLandmark.json 复制） |
| Landmark 常量 | src/utils/landmarks.ts | 完成 |
| 几何工具 | src/utils/geometry.ts | 完成 |
| Canvas 绘制 | src/utils/drawOverlay.ts | 完成 |
| MediaPipe hook | src/hooks/useMediaPipe.ts | 完成，从 CDN 加载 WASM |
| 手部分类 | src/hooks/useHandClassifier.ts | 完成，dorsal/palmar 启发式 |
| 按压检测 | src/hooks/usePressDetection.ts | 完成，handSize 相对 tolerance |
| 状态机 | src/hooks/useCoachingState.ts | 完成，时间戳防抖（非 setTimeout） |
| Router | src/App.tsx | 完成 |
| HomePage | src/pages/HomePage.tsx | 完成，3 张症状卡片 |
| SafetyPage | src/pages/SafetyPage.tsx | 完成，红旗列表 + 强制确认 |
| RoutinePage | src/pages/RoutinePage.tsx | 完成，穴位详情 + Start 按钮 |
| CameraPage | src/pages/CameraPage.tsx | 完成，双摄切换 + overlay + timer ring |
| RecapPage | src/pages/RecapPage.tsx | 完成，FeelingSelector + 安全提示 |

### 已验证在 iPhone Safari 上工作 ✅

- HTTPS via mkcert（证书在 `~/.vite-plugin-mkcert/`，已在 iPhone 信任）
- 摄像头权限正常弹出
- MediaPipe 手部检测工作（灰/橙/蓝/绿圆圈状态切换）
- 前后摄像头切换（右上角按钮）
- 7 状态机转换正常

### 关键技术决策（新会话必须保留）

1. **不用 React StrictMode** — `src/main.tsx` 里已移除，因为 StrictMode 双调用 effect 会导致 iOS `getUserMedia` AbortError
2. **`video.play()` 不 await** — iOS Safari 上 await play() 会抛 AbortError，改为 `.catch(() => {})`
3. **时间戳防抖，非 setTimeout** — 状态机每帧运行，setTimeout 防抖会被每帧重置永远不触发，改用 `pendingRef.current.since` 时间戳比较
4. **摄像头初始化与 MediaPipe 分离** — 两个独立 try-catch，分别显示不同错误页
5. **`facingMode: { ideal: 'environment' }`** — 软约束，不满足时 fallback 到前置，不抛错
6. **`effect` 依赖数组为空 `[]`** — CameraPage 的摄像头 useEffect 只跑一次，MediaPipe 用 `mediaPipeStarted` ref 防重复初始化

### 还剩什么

**必做（demo 相关）：**
- [x] UI 整体视觉重设计 — 深色 Ladder 风格，lime 主色，hero card + routine stack，feedback card + progress ring
- [ ] WRONG_FACE 状态：目前状态机有这个 state 但永远不会进入，因为 useHandClassifier 在 face 不对时返回 `targetHand: null`（会进 NO_HAND 而不是 WRONG_FACE）。修法：在 useHandClassifier 区分"手在画面里但面朝错误" vs "没有手"
- [ ] 后置摄像头时 Canvas overlay 的坐标镜像：后置摄像头不镜像视频，但 MediaPipe 返回的 landmark 坐标仍然是 mirrored 的，需要确认 overlay 圆圈位置是否准确

**拉伸目标（有时间再做）：**
- [ ] TTS 语音播报（Web Speech API）
- [ ] LLM coaching 文案（`POST /api/coaching`）
- [ ] 后端 recap 摘要（`POST /api/recap`）

---

## 文件结构（实际）

```
AcuGuide/
├── src/
│   ├── App.tsx
│   ├── main.tsx                    # 无 StrictMode
│   ├── index.css                   # Tailwind directives + dark body
│   ├── vite-env.d.ts
│   ├── pages/
│   │   ├── HomePage.tsx
│   │   ├── SafetyPage.tsx
│   │   ├── RoutinePage.tsx
│   │   ├── CameraPage.tsx          # 核心页面
│   │   └── RecapPage.tsx
│   ├── hooks/
│   │   ├── useMediaPipe.ts
│   │   ├── useHandClassifier.ts
│   │   ├── usePressDetection.ts
│   │   └── useCoachingState.ts
│   ├── utils/
│   │   ├── landmarks.ts
│   │   ├── geometry.ts
│   │   └── drawOverlay.ts
│   ├── data/
│   │   └── acupoints.json
│
[truncated — 1498 more characters]
```

### product/acceptance-checklist.md

```markdown
# Product Acceptance Checklist

## Product Story

- [ ] 一句话能讲清楚产品。
- [ ] 能说清楚目标用户。
- [ ] 能说清楚为什么文章、视频、chatbot 不够。
- [ ] 能说清楚 camera feedback 是 unique value。
- [ ] 能说清楚不是医疗诊断。

## Demo Readiness

- [ ] 90 秒 demo script 已定稿。
- [ ] 30 秒 backup script 已定稿。
- [ ] 主 demo 使用 tension headache。
- [ ] 经期不适作为 secondary story，不作为治疗 claim。
- [ ] Demo 失败时有 fallback script。

## In-App Copy

- [ ] 首页三个入口文案清楚。
- [ ] Safety screen 完成。
- [ ] Routine preview 文案短。
- [ ] Camera feedback 文案短。
- [ ] Recap 文案不声称治疗结果。

## Safety

- [ ] 不使用 AI doctor。
- [ ] 不使用 diagnosis。
- [ ] 不使用 treatment claim。
- [ ] 不使用 detect disease。
- [ ] Red flag stop copy 准备好。
- [ ] Pregnancy caution 准备好。

## Devpost

- [ ] Title 完成。
- [ ] Tagline 完成。
- [ ] Inspiration 完成。
- [ ] What it does 完成。
- [ ] How we built it 完成。
- [ ] Challenges 完成。
- [ ] What's next 完成。
- [ ] Screenshots 和 video checklist 准备好。

## Final Product Owner Sign-Off

Product Owner 可以 sign off 的条件：

- [ ] 一个不懂项目的人看 demo 30 秒能理解。
- [ ] 一个懂医疗风险的人看文案不会觉得在诊断。
- [ ] 一个工程同学能按文档知道该接什么页面和状态。
- [ ] 一个 pitch teammate 能按 script 讲完整。


```

### package.json

```
{
  "name": "acuguide",
  "private": true,
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@mediapipe/tasks-vision": "^0.10.14",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-router-dom": "^6.26.1"
  },
  "devDependencies": {
    "@types/node": "^26.0.0",
    "@types/react": "^18.3.5",
    "@types/react-dom": "^18.3.0",
    "@vitejs/plugin-react": "^4.3.1",
    "autoprefixer": "^10.4.20",
    "postcss": "^8.4.41",
    "tailwindcss": "^3.4.10",
    "typescript": "^5.5.3",
    "vite": "^5.4.2",
    "vite-plugin-mkcert": "^1.17.12"
  }
}

```

### MaiApp/package.json

```
{
  "name": "poetic-meridian-atlas",
  "private": true,
  "version": "0.2.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "three": "0.184.0",
    "@react-three/fiber": "8.18.0",
    "@react-three/drei": "9.122.0",
    "@react-three/postprocessing": "2.19.1"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^4.3.4",
    "vite": "^5.4.11"
  }
}

```

### underdevelopment/demo-app/package.json

```
{
  "name": "acuguide-demo",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "description": "AcuGuide Hand Coach demo app + rule-based CV feedback engine.",
  "scripts": {
    "test": "node --test \"cv/*.test.js\""
  }
}

```

### src/main.tsx

```typescript
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
  <App />
)

```

### src/App.tsx

```typescript
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import HomePage from './pages/HomePage'
import AboutPage from './pages/AboutPage'
import AskPage from './pages/AskPage'
import SafetyPage from './pages/SafetyPage'
import RoutinePage from './pages/RoutinePage'
import CameraPage from './pages/CameraPage'
import RecapPage from './pages/RecapPage'

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/about" element={<AboutPage />} />
        <Route path="/ask" element={<AskPage />} />
        <Route path="/safety/:symptomId" element={<SafetyPage />} />
        <Route path="/routine/:symptomId" element={<RoutinePage />} />
        <Route path="/camera/:symptomId" element={<CameraPage />} />
        <Route path="/recap" element={<RecapPage />} />
        <Route path="*" element={<Navigate to="/" replace />} />
      </Routes>
    </BrowserRouter>
  )
}

```

### MaiApp/src/main.jsx

```javascript
import { createRoot } from 'react-dom/client';
import MeridianAtlas from './MeridianAtlas.jsx';
import './styles.css';

// NOTE: React.StrictMode intentionally removed. Its double-invoke of effects
// breaks getUserMedia (camera) in the AR Coach view (AbortError on iOS Safari).
createRoot(document.getElementById('root')).render(<MeridianAtlas />);

```

### src/types/index.ts

```typescript
export type SymptomId = 'tension_headache' | 'neck_shoulder_tension' | 'menstrual_discomfort'

export type CoachingState =
  | 'NO_HAND'
  | 'WRONG_FACE'
  | 'SEARCHING'
  | 'ON_TARGET_UNSTABLE'
  | 'HOLDING'
  | 'PAUSED'
  | 'COMPLETE'

export interface Landmark {
  x: number
  y: number
  z: number
}

export interface AnchorDef {
  landmark: number
  weight: number
  name: string
}

export interface MediapipeTarget {
  anchors?: AnchorDef[]
  tolerance_radius_xHandSize: number
  stability_threshold_xHandSize: number
  press_finger_default: string
  /** Per-point pressing fingertip landmark name (e.g. 'INDEX_TIP'); defaults to thumb. */
  press_finger?: string
  /** Provenance note for a calibrated/indicative tolerance. */
  tolerance_note?: string
}

export interface CoachCopy {
  align: string
  drift: string
  hold: string
}

export interface Acupoint {
  id: string
  pinyin: string
  tcm_name: string
  meridian: string
  surface: string
  requires_hand_face: string
  anatomy: string
  mediapipe_target: MediapipeTarget
  technique: {
    contact: string
    pressure: string
    duration_s: number
    rhythm: string
    side: string
  }
  coach_copy: CoachCopy
  contraindications: string[]
  safety_flags: string[]
}

export interface SessionStats {
  holdTimeMs: number
  stabilityPct: number
  // Position-hold steadiness (offset variance), NOT press cadence. Cadence is NO-GO
  // and is intentionally not estimated or shown this round.
  positionSteadiness: 'steady' | 'variable'
}

export type FeelingOption = 'relief' | 'no_change' | 'worse'

```

### postcss.config.js

```javascript
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

```

[104 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]