chore: 导入清理前的完整工程状态(含 Capacitor 原生壳,作为回滚备份点)
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
# 注册来源记录与发布
|
||||
|
||||
Cook 在文档初次进入时记录来源。`firstTouch` 保存在当前域名的 localStorage,30 天后重新采集;`registerTouch` 保存在当前标签的 sessionStorage,每次文档进入更新。SPA 内跳页不会把原落地渠道改成站内页面。`/auth/*` 回调不采集,以免把统一登录、GitHub 或微信授权页计入获客来源。存储被禁用时,本次文档以内存降级,来源失败不会阻止登录。
|
||||
|
||||
可读存储被清空或损坏时,以当前存储状态为准,不能从旧内存恢复已删除的来源。访问存储被拒绝时使用经过校验的内存上下文;若已知新写入失败且存储仍保留完全相同的旧值,使用当前文档的新来源,防止旧活动回流。可观察的删除或值变化会清除此降级状态。首次与本次入口超过 30 天均失效,不长期保留旧活动。
|
||||
|
||||
来源只携带落地域名、路径、外部 referrer 域名以及 `utm_source`、`utm_medium`、`utm_campaign`。`yunle.fun`、其全部子域、`cook.yunyoujun.cn` 和当前域名的站内跳转不会计入外部 referrer,避免 SSO 或 Apps 往返被统计成获客渠道。查询、hash 和凭证不会写入来源记录。没有 referrer/UTM 表示来源缺失,不能据此断言为直接访问。
|
||||
|
||||
登录前读取同一份 `{ version: 1, firstTouch, registerTouch }`,同时覆盖 Web 顶层重定向、Apps host bridge 和 Cook 原生浏览器。来源为客户端上报数据;统一登录服务依据 Registry 验证应用入口并依据 Auth 判断账号是否真正新注册,不能按 Cook 首次登录或公开资料创建时间判断。后续登录不会覆盖账号已保存的注册来源。
|
||||
|
||||
## 本地联调
|
||||
|
||||
新协议在 `@yunlefun/sso@0.7.0`,正式依赖使用 npm registry 的锁定版本。需要联调尚未发布的改动时,可用本地 `YunLeFun/ylf` checkout 的实际构建/打包产物验证:
|
||||
|
||||
```sh
|
||||
node scripts/qa/verify-acquisition-sdk.mjs --sdk-root=/path/to/ylf
|
||||
# 可选:同时验证当前应用的构建与类型(包括已有工作区改动)
|
||||
node scripts/qa/verify-acquisition-sdk.mjs --sdk-root=/path/to/ylf --build --typecheck
|
||||
```
|
||||
|
||||
脚本构建 SDK 并生成临时 tarball,短暂切换已有 node_modules 的 SSO 软链接,运行定向测试和 lint,最后恢复旧软链接。它不修改 package.json、lockfile 或 pnpm override,也不发布 npm。运行时避免并发安装依赖。
|
||||
|
||||
## 发布顺序
|
||||
|
||||
1. 先发布账户私有来源集合/写入 API 与统一登录、SSO 服务端(它们可先接收可选字段并继续兼容旧客户端)。
|
||||
2. 从 `YunLeFun/ylf` 发布已验证的 `@yunlefun/sso@0.7.0`;该版本给现有 PKCE 协议增加可选来源字段。
|
||||
3. 在 Cook 解析并更新 0.7.0 的 lockfile,复核保留当前其他依赖改动,再执行 frozen install、typecheck 和构建并发布。
|
||||
4. 发布 Apps 的来源透传代码;原生“使用其他账号”分支还需要新的 iOS/Android 容器版本。
|
||||
|
||||
`@yunlefun/sso@0.7.0` 已由公开的 `YunLeFun/ylf` 仓库通过 OIDC 发布,附带 GitHub Actions provenance。Cook 的 lockfile 使用实际 registry integrity,安装与发布不依赖临时本地路径或 override。`minimumReleaseAgeExclude` 只为已核验的自有版本 `@yunlefun/sso@0.7.0` 提供精确例外,其余依赖继续遵循 24 小时等待和供应链策略。
|
||||
|
||||
Cook 主域名 `cook.yunyoujun.cn` 对应 EdgeOne Upload 项目 `yunyoujun-cook`,发布时使用该项目名;`cook.yunle.fun` 是另一个 Git 集成项目。原生容器与应用商店发布仍按各自流程验收。
|
||||
|
||||
历史用户最多保留来自 SSO 时间关联的应用入口推断;不能把 GA 总体流量比例回填为某个 UID 的外部渠道。各域名、不同浏览器和被清除的存储也无法恢复此前未采集的触达。
|
||||
@@ -0,0 +1,64 @@
|
||||
# Microsoft Clarity 网站统计
|
||||
|
||||
Cook 使用现有 Microsoft Clarity 项目,提供访问分析、热力图、会话回放和关键点击事件,无需部署统计服务器或数据库。
|
||||
|
||||
- 项目:**Cook**
|
||||
- 网站:`https://cook.yunyoujun.cn`
|
||||
- Project ID:`ks1ebw03i5`(公开标识,不是管理密钥)
|
||||
- [仪表板](https://clarity.microsoft.com/projects/view/ks1ebw03i5/dashboard)
|
||||
- [项目设置](https://clarity.microsoft.com/projects/view/ks1ebw03i5/settings#overview)
|
||||
|
||||
## 配置与发布
|
||||
|
||||
默认配置已经指向上述项目。分叉部署者应填写自己的项目 ID 和域名:
|
||||
|
||||
```dotenv
|
||||
NUXT_PUBLIC_CLARITY_PROJECT_ID=ks1ebw03i5
|
||||
NUXT_PUBLIC_CLARITY_DOMAINS=cook.yunyoujun.cn,cook.yunle.fun
|
||||
```
|
||||
|
||||
显式设置空项目 ID 可关闭统计。开发模式、未授权域名、原生 Capacitor WebView、Do Not Track 和 Global Privacy Control 开启的浏览器不加载统计。页面就绪后异步加载 Clarity,不代理或打包第三方脚本。
|
||||
|
||||
静态托管须在**构建前**设置配置,执行 `pnpm build` 并发布 `dist`。容器启动后修改环境变量不会改变静态资源。Docker 支持同名 `--build-arg`,GitHub Docker 工作流读取同名 Repository variables,未设置时使用 Cook 默认值。工作流中需要禁用时可将项目 ID 设置为 `disabled`,它无法通过 ID 校验。
|
||||
|
||||
原 Google Tag Manager 配置已移除;不再加载 Umami。前端切换不会取消已有 Vercel 或数据库资源的账单。
|
||||
|
||||
旧版 PWA 的 Workbox 可能持续返回 2025 年缓存页面。`apps/web/public/sw.js` 保留原脚本地址,接管后注销旧 worker 并刷新受控页面,不删除 localStorage、IndexedDB 或用户菜谱。即使关闭 PWA 模块,也不要删除这个迁移脚本,否则老访客可能无法收到新页面和统计代码。
|
||||
|
||||
## 事件与回放
|
||||
|
||||
| 事件 | 操作 |
|
||||
| -------------------- | ------------------------------------------------------- |
|
||||
| `click_stuff` | 旧事件:点选或取消预设食材按钮(保留历史口径) |
|
||||
| `click_tool` | 旧事件:点选或取消厨具按钮 |
|
||||
| `click_dish` | 旧事件:详情或旧标签中的外部做法点击,不是打开详情 |
|
||||
| `view_cook` | 做菜页进入可见状态,Ionic 缓存的后台页面不计入 |
|
||||
| `select_ingredients` | 可见做菜页从空选材变为非空;也包含进入时恢复的已有食材 |
|
||||
| `view_results` | 已展开、进入视口、加载完成且存在推荐结果 |
|
||||
| `results_empty` | 已展开且加载完成,但整个当前筛选没有推荐结果 |
|
||||
| `view_recipe_detail` | 菜谱详情弹层已展示(Ionic did-present),不是仅点击列表 |
|
||||
| `click_recipe_video` | 点击外部菜谱做法,覆盖详情、菜谱库、收藏、历史及旧标签 |
|
||||
|
||||
主漏斗按顺序配置为 `view_cook → select_ingredients → view_results → view_recipe_detail → click_recipe_video`。厨具是可选筛选,不作为必经步骤;仅选厨具、菜谱库等直接访问路径另行分析。视频事件表示点击意图,不表示外站打开或播放成功。
|
||||
|
||||
有效选材从共享选择状态采集,因此按钮、搜索输入、快捷示例和「再补一样」都覆盖。已有食材上继续添加、重复添加或普通重渲染不会重复触发;清空后重新选择会再触发一次。切回可见做菜页会重新开始一条路径,并包括保留的选材。结果事件只在可见状态变化时发出,同为非空结果的连续调整不重复上报;重新展开会再次上报。空结果与加载中、加载失败、折叠状态分开处理。Clarity 智能事件卡展示包含事件的会话数,不能直接当作顺序转化率。
|
||||
|
||||
事件仅发送固定名称,不附带用户 ID、邮箱、自由输入、食材名称或自建菜谱内容。Clarity 的事件 API 不接收 Umami 风格的事件属性;访问和 SPA 页面分析由 Clarity 自身采集,不再手动补发 PV。
|
||||
|
||||
`body` 设置 `data-clarity-mask="true"`,遮罩回放中的文本,包括个人资料和自建菜谱。遮罩不是禁止所有数据采集:Clarity 仍会接收页面 URL、交互及技术信息。URL 中不要携带个人内容;项目设置中还应配置 URL 查询参数遮罩(如 `ticket`、`code`、`state`、`access_token`)。
|
||||
|
||||
直接进入 `/auth` 或携带常见凭证参数的页面时不加载脚本。已加载的页面导航到这类 URL 前调用 Clarity `stop`,并在本次文档生命周期内停止业务事件;重新加载普通页面后恢复。脚本加载或事件发送失败不会阻断 Cook 操作。
|
||||
|
||||
代码不调用 `identify`,也不会替访客发送“已同意”的信号。有同意管理需求的部署应连接真实的用户选择;Clarity 对 EEA、英国、瑞士的访问有同意信号要求,未提供时部分功能受限。站点隐私说明应包含实际使用的统计服务。
|
||||
|
||||
## 验收
|
||||
|
||||
1. 部署后在允许的生产域名访问首页,确认 `https://www.clarity.ms/tag/ks1ebw03i5` 只加载一次。
|
||||
2. 检查 Clarity 采集请求成功及负载中的固定事件名,再在后台录制时间线确认新漏斗事件;仪表板汇总可能延迟,HTTP 204 不代表汇总已经展示。
|
||||
3. 切换页面、选择食材、打开菜谱,验证功能正常;阻断 Clarity 域名后重复验证。
|
||||
4. 查看一条回放,确认账号和自建内容被遮罩;登录回调入口不应加载脚本。
|
||||
5. 中国大陆访问效果需要用目标运营商网络实测,不能用本机可访问代替全部用户的可达性结论。
|
||||
|
||||
Clarity 官方当前提供免费服务。普通回放保留 30 天,标记或收藏的会话及热力图可保留 9 个月;单项目每日最多保存 10 万条会话回放,超过后可能抽样。它适合行为分析,不替代长期原始事件仓库。
|
||||
|
||||
参考:[定价](https://clarity.microsoft.com/pricing)、[客户端 API](https://learn.microsoft.com/en-us/clarity/setup-and-installation/clarity-api)、[保留周期](https://learn.microsoft.com/en-gb/clarity/data-retention)、[Cookie 与同意信号](https://learn.microsoft.com/en-us/clarity/setup-and-installation/clarity-cookies)。
|
||||
@@ -0,0 +1,33 @@
|
||||
# APP
|
||||
|
||||
考虑到跨平台和开发效率,计划使用:
|
||||
|
||||
- [Capacitor](https://capacitorjs.com/)
|
||||
- [Ionic](https://ionicframework.com/)
|
||||
- [Nuxt Ionic](https://ionic.nuxtjs.org/)
|
||||
|
||||
## 云乐坊 SSO 回调
|
||||
|
||||
移动端使用系统浏览器打开云乐坊授权页,并通过已验证的 HTTPS Universal Links / App Links
|
||||
返回 `https://cook.yunyoujun.cn/auth/callback?platform=native`。不要改用自定义 URL Scheme;
|
||||
PKCE 能阻止授权码被旁路窃取,但不能阻止恶意 App 主动冒充公开客户端发起授权。
|
||||
|
||||
iOS 的 Associated Domains 与站点 `apple-app-site-association` 已纳入项目。Android 上架前还需用
|
||||
Google Play App Signing 的发布证书 SHA-256 生成站点文件
|
||||
`apps/web/public/.well-known/assetlinks.json`,关系必须限定为:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "cn.yunyoujun.cook",
|
||||
"sha256_cert_fingerprints": ["<PLAY_APP_SIGNING_SHA256>"]
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
不要把本机 debug keystore 指纹发布到生产站点。完成证书配置后,需在真机验证已登录快捷授权、
|
||||
显式同意、取消、返回键、超时、伪造回调和授权码重放。
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
# Cook CLI
|
||||
|
||||
Cook CLI 是一个命令行工具,用于管理菜谱数据。
|
||||
|
||||
## 安装
|
||||
|
||||
项目依赖已在 workspace 中配置,无需单独安装。
|
||||
|
||||
## 数据源
|
||||
|
||||
菜谱数据保存在本地 `packages/data/recipe.csv`,直接用编辑器或 Excel 维护,提交 PR 即可。
|
||||
|
||||
## 命令
|
||||
|
||||
### convert
|
||||
|
||||
将本地 CSV 数据转换为 JSON 格式。
|
||||
|
||||
```bash
|
||||
pnpm convert
|
||||
```
|
||||
|
||||
**功能:**
|
||||
|
||||
- 读取 `packages/data/recipe.csv`
|
||||
- 转换为 JSON 格式
|
||||
- 生成 `packages/data/recipe.json`
|
||||
|
||||
**使用场景:**
|
||||
|
||||
- 手动编辑 CSV 后需要重新生成 JSON
|
||||
- 确保 CSV 和 JSON 数据同步
|
||||
|
||||
## 工作流程
|
||||
|
||||
### 更新菜谱数据
|
||||
|
||||
```bash
|
||||
# 1. 编辑 packages/data/recipe.csv
|
||||
|
||||
# 2. 转换为 JSON
|
||||
pnpm convert
|
||||
|
||||
# 3. 启动开发服务器查看效果
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## 数据格式
|
||||
|
||||
### CSV 格式
|
||||
|
||||
```csv
|
||||
name,stuff,bv,difficulty,tags,methods,tools,id
|
||||
宫保鸡丁,鸡肉、花生米,BV1234567890,简单,川菜、家常菜,炒、煎,一口大锅,0
|
||||
```
|
||||
|
||||
### JSON 格式
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "宫保鸡丁",
|
||||
"stuff": ["鸡肉", "花生米"],
|
||||
"bv": "BV1234567890",
|
||||
"difficulty": "简单",
|
||||
"tags": ["川菜", "家常菜"],
|
||||
"methods": ["炒", "煎"],
|
||||
"tools": ["一口大锅"],
|
||||
"id": 0
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 故障排查
|
||||
|
||||
### CSV 解析错误
|
||||
|
||||
**错误信息:**
|
||||
|
||||
```
|
||||
Error: Failed to parse CSV
|
||||
```
|
||||
|
||||
**解决方案:**
|
||||
|
||||
1. 检查 CSV 文件格式是否正确
|
||||
2. 确认字段分隔符为逗号
|
||||
3. 检查是否有未闭合的引号
|
||||
4. 验证编码是否为 UTF-8
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [开发指南](/dev/app)
|
||||
- [使用说明](/guide/getting-started)
|
||||
|
||||
## 测试
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
# 运行所有测试
|
||||
pnpm test
|
||||
|
||||
# 运行 CLI 相关测试
|
||||
pnpm test packages/cook
|
||||
|
||||
# 运行特定测试文件
|
||||
pnpm test packages/cook/src/utils/csv.test.ts
|
||||
```
|
||||
|
||||
### 测试覆盖
|
||||
|
||||
CLI 工具包含以下测试:
|
||||
|
||||
- **CSV 解析测试** (`csv.test.ts`)
|
||||
- ✅ BV 号清洗和 URL 处理
|
||||
- ✅ CSV 格式解析
|
||||
- ✅ 空值和边界情况处理
|
||||
- ✅ 数组字段过滤空值
|
||||
- ✅ CSV 往返一致性(parse → stringify → parse)
|
||||
@@ -0,0 +1,103 @@
|
||||
# 菜谱数据
|
||||
|
||||
## 数据流
|
||||
|
||||
```
|
||||
packages/data/recipe.csv ──pnpm convert──▶ recipe.json / 分片 ──▶ IndexedDB ──▶ 筛选
|
||||
▲
|
||||
应用内反馈 / Issue / AI PR ┘ CI:pnpm validate 每周:pnpm check:videos
|
||||
```
|
||||
|
||||
`packages/data/recipe.csv` 是构建时实际使用的数据,直接在本地维护。
|
||||
|
||||
## 数据规则
|
||||
|
||||
列为 `name,stuff,bv,difficulty,tags,methods,tools,id`。
|
||||
|
||||
| 字段 | 规则 |
|
||||
| ------------- | --------------------------------------------------------------------------------------------- |
|
||||
| name | 必填 |
|
||||
| stuff | 必填;只能使用 `packages/data/food.json` 里的食材名,别名(如「虾仁」)要写成标准名(「虾」) |
|
||||
| bv | B 站 BV 号或 av 号 |
|
||||
| difficulty | 简单 / 普通 / 困难,或留空 |
|
||||
| methods、tags | 自由填写 |
|
||||
| tools | 只能使用 `packages/data/food.json` 里的厨具名 |
|
||||
| id | 必填,非负整数且唯一;新菜谱用当前最大 id + 1 |
|
||||
|
||||
单元格内多个值用顿号「、」分隔,不要用逗号或斜杠。
|
||||
|
||||
**id 一旦发布就不能修改或复用。** 收藏、用户反馈和筛选信号都按 id 记录(`apps/web/app/utils/db.ts` 用它作 IndexedDB 主键)。可以改菜名、换视频,也可以调整行的顺序;删除一道菜只会让收藏了它的用户丢失这一条,其他菜不受影响。被删掉的 id 不要再给新菜用。
|
||||
|
||||
## 校验
|
||||
|
||||
```bash
|
||||
pnpm validate # 有错误时退出码为 1,CI 中会在对应行标注
|
||||
pnpm validate --json # 输出 JSON,供 AI / 脚本使用
|
||||
```
|
||||
|
||||
错误会让 CI 失败:缺 id 或 id 重复、未知食材或厨具、分隔符错误、难度取值不对、视频号格式不对。
|
||||
警告只做提示:同名菜谱、完全重复的行。同时会统计缺难度、缺做法的比例。
|
||||
|
||||
PR 上 CI 还会运行 `pnpm validate --base <目标分支>`:同一个 id 的菜名和视频都变了(像是把 id 挪给了另一道菜)或复用了已删除的 id 会报错,删除菜谱会给出警告。
|
||||
|
||||
想用新的食材,先在 `packages/data/food.json` 里加上,再在菜谱里使用。
|
||||
|
||||
## 反馈闭环
|
||||
|
||||
1. **用户反馈**:菜谱详情底部点「反馈一下」,选问题类型(视频打不开、食材或厨具不对、难度或做法不对、其他)即可提交,不需要 GitHub,也不强制登录。数据进 CloudBase 的 `cook-feedback` 云函数;提交失败时引导用户去 GitHub Issue 表单(`.github/ISSUE_TEMPLATE/recipe.yml`,已预填菜名和视频)。
|
||||
2. **筛选信号**:应用匿名记录两类聚合数据,只有食材组合、厨具、模式和菜谱 id,不含账号:
|
||||
- 停在一组食材上却没有任何结果(说明缺这类菜,或别名没对上);
|
||||
- 在某组食材下点开了第几个结果(排得太靠后说明排序可以改进)。
|
||||
3. **失效巡检**:`Check Videos` workflow 每周一调用 B 站接口检查全部视频,把失效列表写进带 `dead-video` 标签的 issue(已有就更新)。本地可运行 `pnpm check:videos`。
|
||||
4. **AI 定期审查**:仓库里的 `recipe-data-review` skill(`skills/recipe-data-review/SKILL.md`)描述了完整流程:拉取应用内反馈和筛选信号、处理 `recipe-data` / `dead-video` issue、补全缺失字段、按评测结果优化筛选,跑校验,最后开一个 PR。它不绑定模型,Codex、Claude Code 或接了 DeepSeek 的 Agent 都能跑,在本地按计划执行即可,见下方「本地定时运行」。
|
||||
5. **按主题补充**:`recipe-sourcing` skill(`skills/recipe-sourcing/SKILL.md`)描述了从 B 站、小红书等平台按主题搜罗菜谱的流程:网页搜索找 BV 号、逐个打开视频页核对、按数据规则追加并开 PR。
|
||||
6. **合并发布**:维护者审核 PR,合并后照常构建发布,`Closes #n` 自动关闭 issue。
|
||||
|
||||
### 本地定时运行
|
||||
|
||||
skill 通过软链接暴露给不同 Agent:`.agents/skills/`(Codex)、`.claude/skills/`(Claude Code)、`.codebuddy/skills/`。需要本机装好并登录 `gh`。
|
||||
|
||||
```bash
|
||||
# Codex
|
||||
codex exec --full-auto "使用 recipe-data-review skill 做一次菜谱数据审查"
|
||||
|
||||
# DeepSeek:用支持自定义 API 地址的 Agent(如 Claude Code 按 DeepSeek 文档配置 ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN)
|
||||
claude -p "使用 recipe-data-review skill 做一次菜谱数据审查" --permission-mode acceptEdits
|
||||
```
|
||||
|
||||
每周跑一次即可,例如 crontab(周一 21:00,放在每周视频巡检之后):
|
||||
|
||||
```cron
|
||||
0 21 * * 1 cd ~/repos/cook && codex exec --full-auto "使用 recipe-data-review skill 做一次菜谱数据审查" >> ~/.cook-review.log 2>&1
|
||||
```
|
||||
|
||||
Agent 只开 PR,不合并;没有改动时不开 PR。
|
||||
|
||||
### 拉取反馈与筛选评测
|
||||
|
||||
```bash
|
||||
pnpm feedback:pull # 汇总到 .cook/feedback/summary.md,并更新 test/fixtures/search-eval.json
|
||||
pnpm eval:search # 用真实信号评估当前排序:前 10 命中率、MRR、仍无结果的组合
|
||||
```
|
||||
|
||||
`test/fixtures/search-eval.json` 是匿名聚合后的评测样例,会提交到仓库。AI 修改 `packages/data/aliases.json`(别名)或 `recommend.ts`(排序)时,必须在 PR 中写明修改前后的 `pnpm eval:search --json` 指标,且 `pnpm test` 通过。
|
||||
|
||||
### Web 和 iOS 共用的推荐用例
|
||||
|
||||
`packages/data/fixtures/recommend.json` 记录了一组筛选条件和当前推荐逻辑给出的结果(菜谱 id、顺序和缺少的食材)。Web 的 `test/fixtures.test.ts` 和 iOS 的 CookCore 都要和它完全一致。
|
||||
|
||||
改了 `recipe.csv`、`aliases.json` 或推荐逻辑后运行 `pnpm fixtures` 重新生成,并在 PR 里看结果的变化是否符合预期;忘了运行时 `pnpm test` 会失败。用例的输入在 `scripts/lib/fixtures.ts`。
|
||||
|
||||
### CloudBase 配置(一次性)
|
||||
|
||||
1. 在 CloudBase 控制台新建三个集合,权限都设为「仅管理端可读写」:`cook_recipe_feedback`、`cook_search_signals`、`cook_feedback_quota`。
|
||||
2. 部署云函数:`tcb fn deploy cook-feedback`(配置见 `cloudbaserc.json`)。
|
||||
3. 给 `cook-feedback` 设置环境变量 `COOK_FEEDBACK_EXPORT_TOKEN`(一串足够长的随机字符),并开启 HTTP 访问服务,得到一个访问地址。
|
||||
4. 让未登录用户也能调用 `cook-feedback`:在控制台开启匿名登录或允许未登录访问该函数。否则只有登录用户能提交,未登录用户会被引导去 GitHub。
|
||||
5. 本机 `.env` 填 `COOK_FEEDBACK_URL`(第 3 步的地址)和 `COOK_FEEDBACK_TOKEN`(同一个口令),之后就能 `pnpm feedback:pull`。
|
||||
|
||||
云函数对每个设备(或账号)每天最多接受 200 次提交;同一个人对同一道菜的同一类问题每天只记一次。
|
||||
|
||||
## 性能
|
||||
|
||||
筛选目前在内存里对全部菜谱逐条计算(`apps/web/app/composables/store/recipe.ts`),600 条约 1ms,上万条之内都不是瓶颈;首次加载在超过 1000 条后自动分片并按 hash 增量更新。数据量明显变大时,优先在 `convert` 阶段生成「食材 → 菜谱 id」倒排索引。
|
||||
@@ -0,0 +1,58 @@
|
||||
# iOS App 打磨目标
|
||||
|
||||
目标:做出一个能在 App Store 上架、值得用户付费的「食用手册」iOS App,同时 Web 继续可用。背景和取舍见 [0003 原生 iOS App 与 monorepo 结构](/decisions/0003-native-ios-monorepo)。
|
||||
|
||||
每个阶段完成验收项后才进入下一阶段。数字类指标是起始目标,做出第一版后按实测调整。
|
||||
|
||||
## M0 仓库改成 monorepo
|
||||
|
||||
- [x] 把 Nuxt 应用搬到 `apps/web`、菜谱 CSV 和生成的 JSON 搬到 `packages/data`,只改路径不改逻辑。根目录的 `pnpm build` 仍然输出到 `dist/`,CI、EdgeOne、Netlify、Vercel、Docker、Capacitor 不用改配置。
|
||||
- [x] 食材词典、常备调料、别名改成 `packages/data` 里的 JSON(`food.json`、`pantry.json`、`aliases.json`),Web 和 iOS 读同一份。
|
||||
- [x] 两端共用的推荐用例 `packages/data/fixtures/recommend.json`,`pnpm fixtures` 生成,`pnpm test` 检查是否过期。`packages/cook` 的推荐逻辑本身不依赖 Vue / Node,不再单独拆 `packages/core`。
|
||||
|
||||
验收:`pnpm lint`、`typecheck`、`test`、`build` 全绿;线上 Web 部署后行为不变;`pnpm eval:search` 指标与搬迁前一致。
|
||||
|
||||
## M1 数据与核心逻辑两端一致
|
||||
|
||||
- JSON 加 `schemaVersion`、`dataVersion` 和 JSON Schema。
|
||||
- 共享用例补上相克判断和搜索;收集到真实筛选信号后,把 `test/fixtures/search-eval.json` 也纳入。
|
||||
- `apps/ios/CookCore`(Swift Package)实现菜谱加载、推荐、搜索、相克判断。
|
||||
|
||||
验收:TS 和 Swift 跑同一份用例全部通过;CI 两边都跑(Swift 部分可先用 Linux 上的 `swift test`,或 macOS runner)。
|
||||
|
||||
## M2 iOS 第一版(可上 TestFlight)
|
||||
|
||||
- 食材选择 → 推荐结果、菜谱详情(B 站视频跳转)、随机一道菜、收藏、浏览历史、设置。
|
||||
- 只用系统控件搭界面:`TabView`、`NavigationStack`、`List`、sheet,自动获得 Liquid Glass。
|
||||
- SwiftData 本地存储,CloudKit 同步收藏和历史。
|
||||
- 深色模式、动态字体、VoiceOver、横竖屏和 iPad 基本可用。
|
||||
|
||||
验收:核心流程不依赖网络;冷启动到可点击不超过 1 秒(iPhone 12 级别设备);滚动无明显掉帧;VoiceOver 能走完「选食材 → 打开菜谱」;TestFlight 内测无崩溃。
|
||||
|
||||
## M3 打磨
|
||||
|
||||
- Widget:「今天吃什么」随机推荐、最近收藏。
|
||||
- App Intents / 快捷指令 / Siri:「我有土豆和鸡蛋,能做什么」。
|
||||
- Spotlight 索引菜谱,系统分享面板,触感反馈,空状态和引导页。
|
||||
- 菜谱数据在线更新(按 `dataVersion` 从 CDN 拉取),失败时用内置数据。
|
||||
- 从 Capacitor 版迁移:首次启动导入旧版收藏和历史(方案待验证,备选是旧版先同步到云端)。
|
||||
|
||||
验收:每项功能有截图走查记录;旧版用户升级后收藏不丢;崩溃率和卡顿通过 Xcode Organizer 观察一周无异常。
|
||||
|
||||
## M4 会员与上架
|
||||
|
||||
- StoreKit 2 会员:CloudBase 跨平台同步,以及之后确定的高级功能;恢复购买;Web 已购会员登录后解锁。
|
||||
- 付费墙、隐私清单、App Store 截图和文案、审核备注。
|
||||
- 以更新的方式替换现有 `cn.yunyoujun.cook` 商店条目。
|
||||
|
||||
验收:沙盒环境走通购买、续订、退订、恢复;审核通过并上架。
|
||||
|
||||
## M5 收尾
|
||||
|
||||
- 移除 Capacitor(`ios/`、`capacitor.config.ts`、`@capacitor/*`、`nativeSso` 等);`android/` 去留单独决定。
|
||||
- 更新 #98 的阶段 4 和 `docs/dev/app.md`。
|
||||
|
||||
## 分工说明
|
||||
|
||||
- 云端 Claude 可以完成 M0、M1 的 TS 部分、Swift 代码编写和纯逻辑测试。
|
||||
- Xcode 构建、模拟器和真机调试、TestFlight、App Store Connect 需要在你的 Mac 上进行(可以用 Remote Control 让 Claude 在你的电脑上跑)。
|
||||
Reference in New Issue
Block a user