# Spec Kit Overview

本文件用于简短记录当前项目每个 spec 的核心内容摘要，便于快速总览与追踪变更。

## Client Bootstrap

- **目标**：初始化正念 APP 客户端工程规范，确保可运行、可切环境、可多语言扩展、目录标准化、可 EAS 打包
- **核心范围**：客户端工程骨架与文档（`client/README.md`）、环境变量约定、i18n（CN/EN/ES/PT/TC）、推送/小组件/卡片滑动的一期能力约束
- **主要约定**：
  - 环境：`.env.dev` / `.env.prod`，推荐 `EXPO_PUBLIC_` 前缀
  - 语言码：`zh-CN/en/es/pt/zh-TW`
  - 目录：`app/`（可选 expo-router）+ `src/`（features/services/store/utils 等）+ `assets/`
  - 打包：EAS Build，dev/prod bundle id 约定
- **阶段产物**：
  - `spec_kit/Client Bootstrap/spec.md`
  - `spec_kit/Client Bootstrap/plan.md`
  - `spec_kit/Client Bootstrap/tasks.md`
- **已完成编码（阶段性）**：
  - Expo 工程已在 `client/` 初始化，并完成 `pnpm install`
  - i18n 基座已接入：5 份语言资源 + 设备语言优先/设置可切换/持久化 + 入口初始化
  - 推荐 Feed 请求链路增加调试日志（仅开发环境）：打印 Feed API 请求头与请求体，便于联调排查
  - 环境变量注入增加调试日志（仅开发环境）：打印参与 `API_BASE_URL` 计算的 `EXPO_PUBLIC_*` 与最终解析结果，便于定位“打到哪个后端”

## Client User Identity

- **目标**：在无账号体系或账号可选的前提下，为客户端建立稳定的 `client_user_id`，用于与 APNs/FCM 的 Push Token 做绑定，支撑精准推送与 Token 变更更新
- **关键结论**：`client_user_id` 使用**随机 UUID（默认 UUID v4）**合适；将其视为不透明字符串，不使用可追踪设备硬件标识
- **阶段产物**：
  - `spec_kit/Client User Identity/spec.md`

## App Push

- **目标**：实现“每日提醒”推送闭环：首次进入 Onboarding 可选择每日推送次数（0～5，可跳过），个人主页每日提醒弹窗可改次数/关闭；客户端使用首次生成 UUID（`client_user_id`）与后端关联；后端按用户配置定时推送
- **关键选型**：客户端继续使用 `expo-notifications`；后端使用 Expo Push Service + 定时任务（Celery/等价方案）按用户偏好发送
- **阶段产物**：
  - `spec_kit/App Push/spec.md`
  - `spec_kit/App Push/plan.md`
  - `spec_kit/App Push/tasks.md`
- **已完成编码（阶段性）**：
  - 客户端：新增 `client_user_id`（UUID v4）生成与持久化；每日提醒次数范围修正为 **0～5**（0 表示关闭）
  - 客户端：Onboarding 结束页（每日提醒）在用户选择次数 > 0 时**直接触发系统权限申请**；授权后获取 Expo Push Token 并调用后端 `register/preferences`（移除单独的 push 引导页）
  - 客户端：Onboarding 问卷完成后“开通推送权限”流程增加 **loading 态**（完成按钮转圈 + 全页禁用交互，避免重复触发/重复上报）
  - 客户端：个人主页“每日提醒”弹窗移除测试模式强制无权限逻辑，改为真实读取系统权限；并在开关/点击 OK 时同步后端偏好
  - 客户端：新增推送接口封装 `client/src/services/pushApi.ts`（token 获取、register/preferences/get、自动上报时区与 locale，并携带用户画像供后端 Push 模板使用）
  - 后端：新增 Push 数据模型 + Alembic 迁移（`push_tokens` / `push_preferences` / `push_send_log`）
  - 后端：新增 Push API（`/v1/push/register`、`/v1/push/preferences`、`/v1/push/test`（dev）），并新增基础限流
  - 后端：实现 Celery 定时任务
    - 每日生成计划（按用户时区在 9:00～24:00 均匀分段随机抖动生成 0～5 个时间点）
    - ETA 发送任务（幂等：同一用户同一天同一 slot 只发一次；用户中途关闭/降次数会跳过）
    - 发送文案复用推荐模块 `scene="push"`（降风险）
  - 后端：`pytest` 全量通过（27 passed）

## Project Bootstrap

- **目标**：完成项目仓库初始化与工程约定落地，明确 client/server 结构、dev/pro 环境隔离、MySQL/Redis 资源命名与访问边界
- **核心范围**：根目录结构与文档、dev/pro 配置策略、MySQL schema 命名（`mindfulness_dev`/`mindfulness`）、Redis ACL + key 前缀隔离（`dev:*`/`pro:*`）
- **主要约定**：
  - MySQL：prod=`mindfulness`，dev=`mindfulness_dev`，统一 `utf8mb4` 与 snake_case
  - Redis：单实例，通过 ACL 限制不同用户只能访问对应前缀；应用侧强制 key 使用 `dev:`/`pro:` 前缀
  - 安全：真实 IP/账号/密码/Token 不写入仓库，仅提供 `.env.example` 结构
- **阶段产物**：
  - `spec_kit/Project Bootstrap/spec.md`
- **近期变更**：
  - 新增后端镜像构建基础：`server/Dockerfile`、`server/.dockerignore`
  - 新增后端镜像打包与推送工作流：`.gitea/workflows/server-build.yml`（自动递增 semver tag 并推送到 Docker Hub）
  - 新增后端蓝绿部署工作流：`.gitea/workflows/server-deploy.yml`（SSH 上机 + Docker 蓝绿启动 + Nginx upstream 切流 + 健康检查与失败回滚）
  - 后端配置缺失时报错增强：`app/core/config.py` 将缺失的必填环境变量用中文提示并给出注入方式；补充 `server/env.example` 与 Docker 运行说明

## Onboarding App Shell

- **目标**：落地首次进入体验（Onboarding 3–5 页可跳过 + 每日提醒可跳过/可关闭）与主应用壳（点赞/讨厌、收藏夹入口、通用设置入口）
- **核心范围**：Onboarding 多页流程、主界面反应操作（Like/Dislike）、收藏夹、设置页（版本、iOS 小组件入口/说明）
- **阶段产物**：
  - `spec_kit/Onboarding App Shell/spec.md`
  - `spec_kit/Onboarding App Shell/plan.md`
  - `spec_kit/Onboarding App Shell/tasks.md`

## iOS Widget

- **目标**：实现真正的 iOS 桌面小组件（WidgetKit Extension），展示美观文案并与 App 共享数据
- **核心范围**：WidgetKit 扩展、App Group 数据共享、尺寸适配（Small/Medium/Large）、点击跳转回 App
- **阶段产物**：
  - `spec_kit/iOS Widget/spec.md`
  - `spec_kit/iOS Widget/plan.md`
  - `spec_kit/iOS Widget/tasks.md`
- **近期变更**：
  - iOS 主 App Bundle ID 已统一为 `com.damer.mindfulness`，Widget Extension 为 `com.damer.mindfulness.emotionwidget`
  - 修复 iOS 签名 Team 配置不一致：为主 App（Debug）与 Widget Extension（Debug/Release）显式补齐 `DEVELOPMENT_TEAM=WS92GPX9H2`，避免打包/上传过程中回退到默认 Team 导致显示 “Other Team”
  - iOS 构建号已提升到 `2`，并将 `client/ios/client/Info.plist` 改为自动跟随 `MARKETING_VERSION` / `CURRENT_PROJECT_VERSION`
  - 推送 entitlements 的 `aps-environment` 已切到 `production`（用于 TestFlight/线上包）
  - 清理未接入编译的 WidgetKit 骨架残留：移除磁盘上的 `client/ios/MindfulnessWidget/` 文件，并从 `client/ios/client.xcodeproj/project.pbxproj` 删除对应工程引用（避免 Xcode 显示幽灵文件）
  - 修复 Xcode Archive 偶发显示 “Generic Xcode Archive”：在共享 scheme `Hey Mama` 的 Archive Post-actions 自动补齐 `.xcarchive/Info.plist` 的 `ApplicationProperties`，并在缺失时补齐 `Name`/`SchemeName` + 自检提示（根治 Organizer 无法识别主 App、无法分发/上传 TestFlight 的问题）
  - Widget 名称与描述支持多语言（TC/EN，默认 EN）：Widget Extension 增加 `Localizable.strings`（`en.lproj` / `zh-Hant.lproj`），`EmotionWidget.swift` 使用本地化 key 作为 `.configurationDisplayName/.description`
  - 个人主页弹窗：小工具入口**暂时隐藏**锁屏小工具说明；桌面小工具引导弹窗标题（繁中/TC）更新为“**如何加入小工具**”（并统一弹窗标题使用该文案）；品牌文案“正念”改为 **Hey Mama**（含引导搜索词与 Widget 标题）

## Text Wrap

- **目标**：将 Home 与 Widget 的文案换行算法从 UI 中独立成可复用模块，输出稳定、可控、可解释的换行结果（同输入同输出）
- **核心范围**：`wrapText()` 纯函数入口、TC/EN token 口径与索引体系、候选断点生成、DP/Beam 搜索、硬约束/评分/tie-break、溢出与兜底、debug meta 与 Golden Cases
- **阶段产物**：
  - `spec_kit/Text Wrap/spec.md`
- **已完成编码（阶段性）**：core-contract（核心口径与契约）
- **已完成编码（阶段性）**：grapheme-segmentation（TC 字符簇分割）
- **已完成编码（阶段性）**：width-measurement（宽度测量与降级）
  - **修复**：`measureSliceWidthCached` 的切片缓存 key 增加 `sliceText` hash，避免不同文本的相同 `(start,end)` 发生串缓存
- **已完成编码（阶段性）**：breakpoint-candidates（候选断点生成与裁剪）
  - **变更文件**：
    - `client/src/features/textWrap/breakpoints/types.ts`
    - `client/src/features/textWrap/breakpoints/enCandidates.ts`
    - `client/src/features/textWrap/breakpoints/tcCandidates.ts`
    - `client/src/features/textWrap/breakpoints/filterAndDedup.ts`
    - `client/src/features/textWrap/breakpoints/generateBreakpoints.ts`
    - `client/src/features/textWrap/breakpoints/index.ts`
    - `client/src/features/textWrap/breakpoints/__tests__/generateBreakpoints.test.ts`
    - `spec_kit/Text Wrap/modules/breakpoint-candidates/plan.md`
    - `spec_kit/Text Wrap/modules/breakpoint-candidates/tasks.md`
- **已完成编码（阶段性）**：scoring-tiebreak（评分模型与确定性裁决）
  - **变更文件**：
    - `client/src/features/textWrap/scoring/types.ts`
    - `client/src/features/textWrap/scoring/weights.ts`
    - `client/src/features/textWrap/scoring/lexicons.ts`
    - `client/src/features/textWrap/scoring/phraseMatch.ts`
    - `client/src/features/textWrap/scoring/score.ts`
    - `client/src/features/textWrap/scoring/tieKey.ts`
    - `client/src/features/textWrap/scoring/index.ts`
    - `client/src/features/textWrap/scoring/__tests__/scoringTiebreak.test.ts`
    - `spec_kit/Text Wrap/modules/scoring-tiebreak/plan.md`
    - `spec_kit/Text Wrap/modules/scoring-tiebreak/tasks.md`
- **已完成编码（阶段性）**：search-engine-app（APP 搜索器：DP + TopK）
  - **变更文件**：
    - `client/src/features/textWrap/searchApp/types.ts`
    - `client/src/features/textWrap/searchApp/topK.ts`
    - `client/src/features/textWrap/searchApp/constraints.ts`
    - `client/src/features/textWrap/searchApp/dpTopK.ts`
    - `client/src/features/textWrap/searchApp/index.ts`
    - `client/src/features/textWrap/searchApp/__tests__/searchApp.test.ts`
    - `spec_kit/Text Wrap/modules/search-engine-app/plan.md`
    - `spec_kit/Text Wrap/modules/search-engine-app/tasks.md`
    - `client/src/features/textWrap/measure/measureSliceWidthCached.ts`
- **已完成编码（阶段性）**：search-engine-widget（WIDGET 搜索器：Beam Search）
  - **变更文件**：
    - `client/src/features/textWrap/searchWidget/types.ts`
    - `client/src/features/textWrap/searchWidget/topK.ts`
    - `client/src/features/textWrap/searchWidget/constraints.ts`
    - `client/src/features/textWrap/searchWidget/beam.ts`
    - `client/src/features/textWrap/searchWidget/index.ts`
    - `client/src/features/textWrap/searchWidget/__tests__/searchWidget.test.ts`
    - `spec_kit/Text Wrap/modules/search-engine-widget/plan.md`
    - `spec_kit/Text Wrap/modules/search-engine-widget/tasks.md`
- **已完成编码（阶段性）**：overflow-fallback（溢出与兜底）
  - **变更文件**：
    - `client/src/features/textWrap/overflow/types.ts`
    - `client/src/features/textWrap/overflow/ellipsis.ts`
    - `client/src/features/textWrap/overflow/fallback.ts`
    - `client/src/features/textWrap/overflow/index.ts`
    - `client/src/features/textWrap/overflow/__tests__/overflowFallback.test.ts`
    - `spec_kit/Text Wrap/modules/overflow-fallback/plan.md`
    - `spec_kit/Text Wrap/modules/overflow-fallback/tasks.md`
- **已完成（阶段性）**：golden-tests（Golden Cases 与性质测试）
  - **变更文件**：
    - `client/src/features/textWrap/golden/fixtures.ts`
    - `client/src/features/textWrap/golden/__tests__/golden.test.ts`
    - `spec_kit/Text Wrap/modules/golden-tests/plan.md`
    - `spec_kit/Text Wrap/modules/golden-tests/tasks.md`
- **已完成编码（阶段性）**：integration（Home / Widget 接入）
  - **变更文件**：
    - `client/src/features/textWrap/types.ts`
    - `client/src/features/textWrap/wrapText.ts`
    - `client/src/features/textWrap/index.ts`
    - `client/src/features/textWrap/__tests__/wrapText.integration.test.ts`
    - `spec_kit/Text Wrap/modules/integration/plan.md`
    - `spec_kit/Text Wrap/modules/integration/tasks.md`
  - **接入情况**：
    - Home（APP）：已在 `client/app/(app)/home.tsx` 接入 `wrapText()` 渲染 `wrappedText`（含 `\n`）
    - iOS Widget（WidgetKit）：App 侧在写入 `widget.dailyReco.v1` 缓存时，额外生成 `wrapped_text_by_family`（small/medium/large）并写入 App Group；Widget 侧按 `WidgetFamily` 优先读取该字段渲染（保证换行一致且无需在 Extension 内跑 JS）
  - **近期变更**：
    - Widget 接入：`client/src/modules/dailyWidgetReco/index.ts` 生成 `wrapped_text_by_family`；`client/ios/情绪小组件/EmotionWidget.swift` 按 family 读取；`client/app/(app)/home.tsx` 前台触发一次“尽力而为”的补齐/刷新
    - Home 排版风格微调：支持 `scoringOverrides`，在 Home 里对 TC 做“更偏好标点停顿/更好看”的权重与理想宽度微调（不影响默认 v1）

## Splash Consent

- **目标**：实现开屏页（使用 `client/assets/images/index/` 图片资源），首次加载展示渐变同意按钮并提供隐私/协议入口
- **核心范围**：开屏 UI、首次同意状态持久化（`consent.accepted`）、点击同意后进入后续 Onboarding/App 流程
- **阶段产物**：
  - `spec_kit/Splash Consent/spec.md`
  - `spec_kit/Splash Consent/plan.md`
  - `spec_kit/Splash Consent/tasks.md`
- **近期变更**：
  - 启动流程优化：将 Expo Router 初始路由调整为协议页 `/(splash)/splash`，并在协议页已同意时直接分发到 `/(app)/home` 或 `/(onboarding)/onboarding`，避免系统开屏结束后先渲染 `index`（转圈页）再跳协议页导致的“闪一下”

## Policy Links

- **目标**：在客户端首次进入同意页底部补充「隐私协议/用户使用协议」入口，并将协议链接改为**由后端托管与按设备语言下发**（默认回退 EN）
- **核心范围**：后端提供协议链接下发接口（可配置、可多语言）；客户端按设备语言请求并展示链接，失败不阻塞“同意并继续”
- **阶段产物**：
  - `spec_kit/Policy Links/spec.md`
  - `spec_kit/Policy Links/plan.md`
  - `spec_kit/Policy Links/tasks.md`
- **已完成编码（任务清单已执行完毕）**：
  - 后端新增 `GET /v1/legal/links`（按 `Accept-Language` 分发，默认回退 EN，返回 `resolvedLang`）
  - 后端配置：`LEGAL_*` 环境变量（`server/env.example` 已补齐示例；不配置时走内置协议页兜底）
  - 后端新增内置协议内容页：
    - `GET /v1/legal/privacy`
    - `GET /v1/legal/terms`
  - 客户端新增协议接口封装 `client/src/services/legalApi.ts`
  - 客户端工程化：新增统一 HTTP 封装 `client/src/utils/http.ts`（baseURL/超时/JSON/统一错误），并将 `legalApi.ts` / `recoApi.ts` 接入
  - 客户端接入两处入口：`app/(splash)/splash.tsx`、`components/home/ProfileModal.tsx`
  - **变更文件清单**：
    - `server/app/api/v1/legal.py`
    - `server/app/core/config.py`
    - `server/app/legal_docs.py`
    - `server/app/main.py`
    - `server/env.example`
    - `server/tests/test_legal_links_unittest.py`
    - `client/src/services/legalApi.ts`
    - `client/src/services/__tests__/legalApi.test.ts`
    - `client/app/(splash)/splash.tsx`
    - `client/components/home/ProfileModal.tsx`
    - `spec_kit/Policy Links/spec.md`
    - `spec_kit/Policy Links/plan.md`
    - `spec_kit/Policy Links/tasks.md`

## Card UI

- **目标**：优化卡片页 UI 与交互，支持右上角主题切换与个人主页弹窗，并将喜欢/讨厌改为 icon + 动效
- **核心范围**：
  - 系统导航栏右上角两个圆形 icon：主题（`theme.svg`）/ 我的（`my.svg`）
  - 底部上拉弹窗（Sheet）：统一内容区背景 `#FAF3EC`，点 X 关闭
  - 主题两种模式：`scenery` / `color`（本期最少影响 Home 页面背景），并持久化 `ui.theme.mode`
  - 喜欢/讨厌按钮：使用 `like.svg` / `hate.svg`，喜欢提供“心形填满”强反馈动效（`like_filled.svg`）
  - 每日提醒弹窗：次数 +/-、Push 提醒开关、渐变 Ok 按钮，并持久化设置
- **阶段产物**：
  - `spec_kit/Card UI/spec.md`
  - `spec_kit/Card UI/plan.md`
  - `spec_kit/Card UI/tasks.md`
- **已完成编码（阶段性）**：
  - 已接入 `react-native-svg-transformer` + `client/metro.config.js`
  - 新增 `SheetModal`、`ThemeModal`、`ProfileModal` 并在 Home 中接入
  - 新增 `DailyReminderModal` 并从个人主页弹窗打开
  - Home：右上角 icon 按钮、主题切换背景、喜欢/讨厌 icon + 动效
  - **修复**：收藏（“我的喜欢”）不展示后端推荐文案的问题——收藏时持久化写入 `text` 快照，并在 `ProfileModal` 的 Favorites 页面同时兼容 Mock 与推荐缓存内容回填

## User Profile Scoring

- **目标**：将 Onboarding 问卷答案映射为标准化“用户画像 U”，并输出硬规则与置信度元信息，供推荐/Push 等模块统一复用
- **核心范围**：mom_stage（离散）、emotion_score（0–1 连续）、context（离散）、need（离散）四维度；以及 `profile_version/source/generated_at/confidence`（V1.1）
- **主要约定**：
  - 输出结构稳定可版本化，便于规则演进
  - 支持题目可跳过：新增 `profile_answered` 标记各题是否作答；字段缺失时由推荐算法走“通用值 + 不确定性惩罚”
  - mom_stage 跳过按安全策略视为 `unknown`（用于避免冒犯），emotion/context/need 跳过不强行填默认值
  - 置信度 `conf_U` = 时间衰减 × 作答完整度（答得越少越不确定），用于推送场景降个性化/降风险
  - 硬规则优先于任何分数计算：`unknown` 仅在“育儿压力且强个性化（personalization_power=1）”时禁推；`block_health_medical` 全场景硬过滤
- **测试覆盖**：提供“输入题目答案 → 输出用户画像”的回归用例，覆盖字段映射、置信度衰减与硬规则触发（可选但建议）
- **阶段产物**：
  - `spec_kit/User Profile Scoring/spec.md`
- **已完成编码（阶段性）**：
  - 客户端 Onboarding 完成时已收集问卷答案并生成用户画像，写入本地存储供推荐/Push/Widget 复用

## SuixinTheme

- **目标**：在 Home 现有「风景 / 纯色」主题基础上新增「随心」主题，背景颜色根据用户问卷画像个性化推荐
- **核心范围**：复用既有 Base Theme（5 套）+ Neutral（1 套），按 `need/stage/emotion/confidence` 选色并做 session 锁定（Theme Lock），支持 TC/EN 文案
- **阶段产物**：
  - `spec_kit/SuixinTheme/spec.md`
  - `spec_kit/SuixinTheme/plan.md`
  - `spec_kit/SuixinTheme/tasks.md`
- **已完成编码（全部）**：
  - 客户端新增第三主题 `suixin`（随心/Ease），与现有主题切换入口一致
  - Home：冷启动会话内锁定 Base Theme；切换文案时在同主题内线性插值输出纯色背景，并持久化 `ui.theme.suixin.state`
  - i18n：新增 `theme.suixin`（TC/EN）
  - 测试：新增随心模块单测（Vitest）并通过；`tsc --noEmit` 通过
- **变更文件**：
  - `client/src/storage/appStorage.ts`
  - `client/app/(app)/home.tsx`
  - `client/components/home/ThemeModal.tsx`
  - `client/src/i18n/locales/all.json`
  - `client/src/utils/bootSession.ts`
  - `client/src/features/suixinTheme/palette.ts`
  - `client/src/features/suixinTheme/colorMath.ts`
  - `client/src/features/suixinTheme/progress.ts`
  - `client/src/features/suixinTheme/pickTheme.ts`
  - `client/src/features/suixinTheme/index.ts`
  - `client/src/features/suixinTheme/__tests__/suixinTheme.test.ts`
  - `client/app/_layout.tsx`
