# 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) - 客户端:新增通知点击消费链路,支持前后台/冷启动点击每日推荐 Push 后,将文案暂存并在进入 `home` 时优先展示 - 后端:每日推荐 Push payload 新增 `target_screen/home_text/content_id/deep_link`,保证客户端点击通知后可恢复首页展示上下文 ## 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 `Dear 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)更新为“**如何加入小工具**”(并统一弹窗标题使用该文案);品牌文案改为 **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-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` - `GET /v1/legal/support`(技术支持页面,提供审核可用的公开支持信息) - 协议展示策略调整:`/v1/legal/privacy`、`/v1/legal/terms`、`/v1/legal/support` 在携带 `Accept-Language` 时按语言单语展示(EN/TC),缺省时展示 EN + TC(EN 在前、TC 在后) - 协议页面语义修复:HTML 根节点 `lang` 属性不再写死,改为随页面实际语言输出(单语 en/zh-Hant;双语默认 en) - 客户端新增协议接口封装 `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`