Files
mindfulness/spec_kit/overview.md
雷汀岚 e552e22de9 chore: App 名稱 Hey Mama → Dear Mama + 相關修復
- 品牌與顯示:app.json、Info.plist、package scheme、iOS 產物 DearMama.app
- 協議與條款:隱私協議/使用條款全文、設計文檔、server legal_docs + legal API、push 預設 title
- 多語言:all.json / zh-TW / zh-CN / en / es / pt 的 consent、widget 標題與引導文案
- 小工具:EmotionWidget.swift 品牌文案、Dear Mama.xcscheme
- CocoaPods:project.pbxproj objectVersion 70→56 以通過 pod install
- Metro:react-native-text-size 用 require + extraNodeModules 解析
- textWrap:measureWidthImpl 改為 require 載入 react-native-text-size

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-10 16:16:02 +08:00

285 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Spec Kit Overview
本文件用于简短记录当前项目每个 spec 的核心内容摘要,便于快速总览与追踪变更。
## Client Bootstrap
- **目标**:初始化正念 APP 客户端工程规范,确保可运行、可切环境、可多语言扩展、目录标准化、可 EAS 打包
- **核心范围**:客户端工程骨架与文档(`client/README.md`、环境变量约定、i18nCN/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 Builddev/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 可选择每日推送次数05可跳过个人主页每日提醒弹窗可改次数/关闭;客户端使用首次生成 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生成与持久化每日提醒次数范围修正为 **05**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:0024:00 均匀分段随机抖动生成 05 个时间点)
- 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:*`
- **主要约定**
- MySQLprod=`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 35 页可跳过 + 每日提醒可跳过/可关闭)与主应用壳(点赞/讨厌、收藏夹入口、通用设置入口)
- **核心范围**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 配置不一致:为主 AppDebug与 Widget ExtensionDebug/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 `Dear Mama` 的 Archive Post-actions 自动补齐 `.xcarchive/Info.plist``ApplicationProperties`,并在缺失时补齐 `Name`/`SchemeName` + 自检提示(根治 Organizer 无法识别主 App、无法分发/上传 TestFlight 的问题)
- Widget 名称与描述支持多语言TC/EN默认 ENWidget Extension 增加 `Localizable.strings``en.lproj` / `zh-Hant.lproj``EmotionWidget.swift` 使用本地化 key 作为 `.configurationDisplayName/.description`
- 个人主页弹窗:小工具入口**暂时隐藏**锁屏小工具说明;桌面小工具引导弹窗标题(繁中/TC更新为“**如何加入小工具**”(并统一弹窗标题使用该文案);品牌文案改为 **Dear 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-segmentationTC 字符簇分割)
- **已完成编码(阶段性)**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-appAPP 搜索器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-widgetWIDGET 搜索器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-testsGolden 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`
- **已完成编码(阶段性)**integrationHome / 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`
- **接入情况**
- HomeAPP已在 `client/app/(app)/home.tsx` 接入 `wrapText()` 渲染 `wrappedText`(含 `\n`
- iOS WidgetWidgetKitApp 侧在写入 `widget.dailyReco.v1` 缓存时,额外生成 `wrapped_text_by_family`small/medium/large并写入 App GroupWidget 侧按 `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_score01 连续、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 Theme5 套)+ Neutral1 套),按 `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`