From 6f74c8002555ea0a534e48081faa733948229dc4 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Sun, 12 Jul 2026 17:07:08 +0800 Subject: [PATCH] =?UTF-8?q?docs(plan):=20=E8=B4=A6=E6=88=B7=20IA=20?= =?UTF-8?q?=E9=87=8D=E6=9E=84=20Flutter=20=E5=AE=9E=E7=8E=B0=E8=AE=A1?= =?UTF-8?q?=E5=88=92(8=20=E4=BB=BB=E5=8A=A1,TDD,=E5=90=AB=20Spec=20?= =?UTF-8?q?=E2=91=A1=E2=91=A2=E5=8D=A0=E4=BD=8D=E6=8E=A5=E5=8F=A3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u --- .../plans/2026-07-12-account-ia-reorg.md | 416 ++++++++++++++++++ 1 file changed, 416 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-12-account-ia-reorg.md diff --git a/docs/superpowers/plans/2026-07-12-account-ia-reorg.md b/docs/superpowers/plans/2026-07-12-account-ia-reorg.md new file mode 100644 index 0000000..5317ee9 --- /dev/null +++ b/docs/superpowers/plans/2026-07-12-account-ia-reorg.md @@ -0,0 +1,416 @@ +# 账户/设置/联系 信息架构重构 — Flutter 实现计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 把已评审的 IA(`docs/account-ia-reorg-design.html`)落成 Flutter:账户/设置/联系去重归位,Account 提为桌面一级项,顶栏加通知铃铛,账户页加邀请入口,并为 Spec ②推广激励 / ③系统通知 留占位屏与接口。 + +**Architecture:** 复用现有 Riverpod + shell(desktop/mobile/tablet) + `open(NavView, mobilePage)` 下钻模式。多数是改造现有页面(account_page/settings_page/account_screens)+ 导航(navigation_provider/desktop_shell);新建仅 2 个占位屏(InviteScreen/NotificationsScreen)+ 铃铛 widget。邀请奖励引擎、通知下发机制**不在本计划**(各自 Spec)。 + +**Tech Stack:** Flutter / Dart · flutter_riverpod 2.x · 现有 `PangolinIcons`(生成,勿手改) · `AppText` 抽象基类 + 6 语言 strings。 + +## Global Constraints + +- **l10n 完整性闸**:`app_text.dart` 每加一个抽象 getter,必须在 6 份 `strings_{zh,en,ja,ko,ru,es}.dart` 全部实现,否则 `node tools/check-l1-sync.mjs` red。默认语言英文。 +- **图标单源**:`client/lib/widgets/pangolin_icons.dart` 是生成文件**勿手改**;真源 `design/prototype/icons.js`。本计划所需图标(bell/gift/crown/ticket/monitorSmartphone/users/settings/messageCircle)均已在。 +- **颜色单源**:禁裸 `Color(0x)`/`Colors.x`,走 `context.pangolin` 语义色;例外加 `// ds-ignore: 理由`。 +- **验收**:每任务后 `cd client && flutter analyze`(0 error) + 相关 `flutter test` 绿;改到有 golden 的屏随功能 `--update-goldens` 重录。 +- **占位屏边界**:InviteScreen/NotificationsScreen 本计划只做**静态占位 UI + 导航接线**;数据/奖励/下发逻辑属 Spec ②③,用 `// TODO(spec-2)` / `// TODO(spec-3)` 标注接口点。 + +--- + +### Task 1: 导航枚举加 invite / notifications + +**Files:** +- Modify: `client/lib/state/navigation_provider.dart:7,10` + +**Interfaces:** +- Produces: `NavView.invite`、`NavView.notifications` 枚举值;`kAccountSubViews` 含 `invite`。 + +- [ ] **Step 1: 加枚举值 + 子页登记** + +`navigation_provider.dart` 第 7 行 `enum NavView` 末尾加 `, invite, notifications`: +```dart +enum NavView { connect, servers, stats, account, contact, settings, plans, redeem, devices, purchase, payment, orders, invite, notifications } +``` +第 10 行 `kAccountSubViews` 加 `invite`(notifications 走全屏,不入子页返回集): +```dart +const Set kAccountSubViews = {NavView.plans, NavView.redeem, NavView.devices, NavView.purchase, NavView.payment, NavView.orders, NavView.invite}; +``` + +- [ ] **Step 2: 编译校验** + +Run: `cd client && flutter analyze lib/state/navigation_provider.dart` +Expected: 出现「missing case NavView.invite/notifications」类 error(desktop_shell 的穷举 switch 未覆盖)——**预期**,Task 5 修复。仅确认本文件无语法 error。 + +- [ ] **Step 3: Commit** + +```bash +git add client/lib/state/navigation_provider.dart +git commit -m "feat(nav): NavView 加 invite/notifications + kAccountSubViews 收录 invite" +``` + +--- + +### Task 2: l10n — 邀请 + 通知 + IA 归组文案 + +**Files:** +- Modify: `client/lib/l10n/app_text.dart`(抽象 getter) +- Modify: `client/lib/l10n/strings_{zh,en,ja,ko,ru,es}.dart`(6 份实现) + +**Interfaces:** +- Produces getter(供后续任务用):`inviteEntry, inviteEntrySub, inviteTitle, inviteCodeLabel, inviteLinkLabel, inviteCopyCode, inviteCopyLink, inviteRewardsTitle, inviteRewardRegister, inviteRewardFirstBuy, inviteRewardJoinTg, inviteProgressTitle`;`notifTitle, notifEmpty, notifTypeNews, notifTypeFeature, notifTypeImportant`;`settingsGroupConn, settingsGroupAppearance, settingsGroupAbout`。 + +- [ ] **Step 1: app_text.dart 加抽象 getter** + +在 `app_text.dart` 的「购买/支付」段后加一段(照现有注释风格): +```dart + // ── 邀请好友(Spec ②占位) ── + String get inviteEntry; // 邀请好友得会员 / Invite friends, earn membership + String get inviteEntrySub; // 好友注册 · 双方都得会员天数 / Friend signs up · both earn days + String get inviteTitle; // 邀请好友 / Invite friends + String get inviteCodeLabel; // 邀请码 / Invite code + String get inviteLinkLabel; // 邀请链接 / Invite link + String get inviteCopyCode; // 复制邀请码 / Copy code + String get inviteCopyLink; // 复制链接 / Copy link + String get inviteRewardsTitle; // 奖励规则 / Rewards + String get inviteRewardRegister; // 好友注册 · 双方各得 3 天 / Sign up · +3 days each + String get inviteRewardFirstBuy; // 好友首购 · 双方各得 15 天 / First purchase · +15 days each + String get inviteRewardJoinTg; // 加入 TG 频道 · 得 3 天 / Join TG · +3 days + String get inviteProgressTitle; // 我的奖励 / My rewards + // ── 通知(Spec ③占位) ── + String get notifTitle; // 通知 / Notifications + String get notifEmpty; // 暂无通知 / No notifications + String get notifTypeNews; // 新闻 / News + String get notifTypeFeature; // 功能 / Feature + String get notifTypeImportant; // 重要 / Important + // ── 设置分组 ── + String get settingsGroupConn; // 连接 / Connection + String get settingsGroupAppearance;// 外观 / Appearance + String get settingsGroupAbout; // 关于 / About +``` + +- [ ] **Step 2: 6 份 strings 各实现(带 @override)** + +每份 `strings_.dart` 在 `qrNotSupported` 之后加对应 20 个 `@override String get X => '<译文>';`。译文(zh 见注释;en 见注释;ja/ko/ru/es 按语义翻译,与已有条目风格一致)。 + +- [ ] **Step 3: 跑 l10n 闸** + +Run: `cd /Users/wangjia/code/pangolin/.claude/worktrees/pay-v2-integration && node tools/check-l1-sync.mjs` +Expected: `✓ l10n 完整 OK —— NNN 个 getter × 6 语言全实现`(getter 数 +20)。 + +- [ ] **Step 4: analyze** + +Run: `cd client && flutter analyze lib/l10n/` +Expected: No issues found(仅 annotate_overrides 若漏 @override 会 info,补齐)。 + +- [ ] **Step 5: Commit** + +```bash +git add client/lib/l10n/ +git commit -m "feat(l10n): 邀请/通知/设置分组文案 ×6 语言(Spec ②③占位)" +``` + +--- + +### Task 3: 联系渠道单源 + Redeem 删「去哪买」 + +**Files:** +- Create: `client/lib/services/contact_channels.dart` +- Modify: `client/lib/screens/contact_page.dart:22-26` +- Modify: `client/lib/widgets/account_screens.dart:377-381,420-425,461-465` + +**Interfaces:** +- Produces: `const List kContactChannels`(single source);`class ContactChannel { IconData icon; String name; String sub; bool accent; }`。 + +- [ ] **Step 1: 建单源常量** + +`contact_channels.dart`: +```dart +import 'package:flutter/widgets.dart'; +import '../widgets/pangolin_icons.dart'; + +class ContactChannel { + const ContactChannel({required this.icon, required this.name, required this.sub, this.accent = false}); + final IconData icon; + final String name; // 展示名(Telegram 用字面量;邮箱/官网用 l10n,在页面侧传入) + final String sub; // handle:@x / a@b / domain(launchChannel 据此推导 URL) + final bool accent; +} + +// name 用 key 占位,页面按 t 映射;Telegram 固定字面量 +const List<({IconData icon, String nameKey, String sub, bool accent})> kContactChannels = [ + (icon: PangolinIcons.send, nameKey: 'telegram', sub: '@pangolin_app', accent: true), + (icon: PangolinIcons.mail, nameKey: 'email', sub: 'support@yanmeiai.com', accent: false), + (icon: PangolinIcons.globe, nameKey: 'website', sub: 'pangolin.yanmeiai.com', accent: false), +]; +``` +> 说明:`nameKey` 在页面侧映射(`telegram`→字面量 'Telegram',`email`→`t.contactEmail`,`website`→`t.contactWebsite`),避免把 AppText 塞进常量。 + +- [ ] **Step 2: contact_page.dart 用单源** + +`contact_page.dart:22-26` 的三元素内联 list 换成 `kContactChannels.map(...)`,name 按 nameKey 映射。 + +- [ ] **Step 3: ContactScreen 用单源** + +`account_screens.dart:461-465`(ContactScreen 的 channels)同样换成 `kContactChannels`。 + +- [ ] **Step 4: RedeemScreen 删「去哪买」** + +`account_screens.dart`:删 `channels`(377-381)、删 build 里「去哪买」引流卡(`buyTitle`/`buySub` + channel 列表,420-425)、删 `_channel` 方法(430+)。RedeemScreen 只保留兑换码输入(386-419)。 + +- [ ] **Step 5: analyze + 手测** + +Run: `cd client && flutter analyze lib/screens/contact_page.dart lib/widgets/account_screens.dart lib/services/contact_channels.dart` +Expected: No issues found。 + +- [ ] **Step 6: Commit** + +```bash +git add client/lib/services/contact_channels.dart client/lib/screens/contact_page.dart client/lib/widgets/account_screens.dart +git commit -m "refactor(contact): 渠道抽单源常量 + RedeemScreen 删去哪买引流卡(仅留兑换码)" +``` + +--- + +### Task 4: 账户页重排 + 邀请入口 + 删偏好卡 + formFactor 门控 + +**Files:** +- Modify: `client/lib/screens/account_page.dart:52-141,381,423` +- Create: `client/lib/screens/invite_page.dart` +- Test: `client/test/widget/account_ia_test.dart` + +**Interfaces:** +- Consumes: `NavView.invite`(Task 1)、`t.inviteEntry/inviteEntrySub`(Task 2)、`open(NavView, Widget)`(现有)。 +- Produces: `class InviteScreen extends ConsumerWidget { InviteScreen({required AppText t, VoidCallback? onBack, bool embedded}); }`(占位)。 + +- [ ] **Step 1: 建 InviteScreen 占位屏** + +`invite_page.dart`:`_SubScaffold`(复用 account_screens 的骨架不便跨文件,简单自建 Scaffold/embedded)标题 `t.inviteTitle`,内容 = 邀请码卡(可复制,值写死 `PANGOLIN-DEMO`)+ 链接卡 + 奖励规则列表(register/firstBuy/joinTg 三条,用 `t.inviteReward*`)+ 「我的奖励」占位卡。全走 `context.pangolin`/`PangolinIcons.gift`。顶部 `// TODO(spec-2): 邀请码/链接由后端签发,奖励进度来自 /v1/invite`。 + +- [ ] **Step 2: 写账户页 IA 结构测试(失败)** + +`account_ia_test.dart`:desktop 态(`debugDefaultTargetPlatformOverride=windows` + 宽 surface + provider override,参考 `test/widget/account_desktop_nav_test.dart`)pump `AccountPage(isWide:true)`,断言: +```dart +// 邀请入口存在 +expect(find.text(t.inviteEntry), findsOneWidget); +// 语言/主题不在账户页(移到 Settings) +expect(find.text(t.language), findsNothing); +// 兑换在账户页(购买&订单&兑换块) +expect(find.text(t.redeemEntry), findsOneWidget); +``` + +- [ ] **Step 3: 跑测试确认失败** + +Run: `cd client && flutter test test/widget/account_ia_test.dart` +Expected: FAIL(邀请入口不存在 / 语言仍在)。 + +- [ ] **Step 4: 改 account_page.dart** + +- 购买&订单&兑换块(现 80-94):把兑换码行(现在设备卡里的 `t.redeemEntry`)并入,成为 购买套餐 / 我的订单 / 兑换码 三行。 +- 其后加**邀请卡**:`_NavRow(icon: PangolinIcons.gift, title: t.inviteEntry, sub: t.inviteEntrySub, onTap: () => open(NavView.invite, InviteScreen(t: t)))`。 +- 设备/设置/兑换/联系卡(现 97-105):兑换移走(见上);设备保持下钻行;设置/联系两行包 `if (context.formFactor == FormFactor.mobile)`(桌面侧栏已有,不重复)。 +- **删语言/主题/协议卡**(108-130)+ `_LangSwitch`(381)+ `_PangolinSwitch`(423)。 +- import `invite_page.dart` + `core/responsive/form_factor.dart`。 + +- [ ] **Step 5: 跑测试确认通过** + +Run: `cd client && flutter test test/widget/account_ia_test.dart` +Expected: PASS。 + +- [ ] **Step 6: analyze + golden 重录(账户页有 golden 则录)** + +Run: `cd client && flutter analyze lib/screens/account_page.dart lib/screens/invite_page.dart && flutter test --update-goldens 2>&1 | tail -3` + +- [ ] **Step 7: Commit** + +```bash +git add client/lib/screens/account_page.dart client/lib/screens/invite_page.dart client/test/widget/account_ia_test.dart client/test/golden/ +git commit -m "feat(account): IA 重排+邀请入口+删偏好卡+设置/联系 formFactor 门控" +``` + +--- + +### Task 5: 桌面侧栏加 Account + 顶栏铃铛 + invite 视图接线 + +**Files:** +- Modify: `client/lib/shell/desktop_shell.dart:34-40,42-55,59-88,92-96,104` +- Modify: `client/lib/shell/tablet_shell.dart:39-46` +- Create: `client/lib/widgets/notification_bell.dart` +- Modify: `client/lib/widgets/content_top_bar.dart:44-66` + +**Interfaces:** +- Consumes: `NavView.invite/notifications`(Task 1)、`InviteScreen`(Task 4)、`NotificationsScreen`(Task 6,先前向引用——本任务铃铛 onTap 先 `go(NavView.notifications)`,Task 6 补屏)。 +- Produces: `class NotificationBell extends ConsumerWidget { NotificationBell({required VoidCallback onTap, bool hasUnread}); }`。 + +- [ ] **Step 1: 建 NotificationBell widget** + +`notification_bell.dart`:`IconButton`(`PangolinIcons.bell`) + 右上未读红点(`Positioned` 小圆 `context.pangolin.danger`,`hasUnread` 才显)。`hasUnread` 本任务先写死 `true`(演示),`// TODO(spec-3): 未读态来自 notices provider`。 + +- [ ] **Step 2: 侧栏加 Account 一级项** + +`desktop_shell.dart:34-40` `items` 在 stats 后 settings 前插: +```dart +(icon: PangolinIcons.user, label: t.tabMe, view: NavView.account), +``` + +- [ ] **Step 3: 顶栏挂铃铛** + +`content_top_bar.dart` 的 trailing 区(主题切换旁)加 `NotificationBell(hasUnread: true, onTap: onBell)`;`ContentTopBar` 加 `VoidCallback? onBell` 入参。`desktop_shell.dart:104` 传 `onBell: () => go(NavView.notifications)`。 + +- [ ] **Step 4: content()/titles 补 invite + notifications** + +`desktop_shell.dart` `titles`(42-55)加 `NavView.invite: t.inviteTitle, NavView.notifications: t.notifTitle`;`content()`(59-88)加 `case NavView.invite: return InviteScreen(t: t, embedded: true);` 和 `case NavView.notifications: return NotificationsScreen(t: t, embedded: true);`(NotificationsScreen 由 Task 6 建,本步先 import 占位——若 Task 6 未完成,临时 `return const SizedBox()` 并 `// TODO`)。`backTarget()`(92-96)靠 `kAccountSubViews` 已含 invite → 自动回 account;notifications 不在集内,返回走顶栏无返回(全屏态)——给 notifications 也返回 account:在 `backTarget` 加 `if (view == NavView.notifications) return () => go(NavView.account);`。 + +- [ ] **Step 5: tablet_shell titles 补一条** + +`tablet_shell.dart:39-46` `titles` 加 `NavView.invite: t.inviteTitle, NavView.notifications: t.notifTitle`(内容走 default→AccountPage/push)。 + +- [ ] **Step 6: analyze(全绿——穷举 switch 现已覆盖)** + +Run: `cd client && flutter analyze lib/shell/ lib/widgets/notification_bell.dart lib/widgets/content_top_bar.dart` +Expected: No issues found。 + +- [ ] **Step 7: Commit** + +```bash +git add client/lib/shell/desktop_shell.dart client/lib/shell/tablet_shell.dart client/lib/widgets/notification_bell.dart client/lib/widgets/content_top_bar.dart +git commit -m "feat(shell): 桌面侧栏加 Account 一级项 + 顶栏通知铃铛 + invite/notifications 视图接线" +``` + +--- + +### Task 6: 通知占位屏 + 移动顶栏铃铛 + +**Files:** +- Create: `client/lib/screens/notifications_page.dart` +- Modify: `client/lib/widgets/app_top_bar.dart`(trailing 铃铛) +- Modify: `client/lib/screens/account_page.dart:146`(AppTopBar 传 trailing) +- Modify: `client/lib/shell/desktop_shell.dart`(把 Task 5 的占位 import 换成真 NotificationsScreen) + +**Interfaces:** +- Consumes: `t.notifTitle/notifEmpty/notifType*`(Task 2)、`NotificationBell`(Task 5)、`open(NavView.notifications, ...)`。 +- Produces: `class NotificationsScreen extends ConsumerWidget { NotificationsScreen({required AppText t, VoidCallback? onBack, bool embedded}); }`。 + +- [ ] **Step 1: 建 NotificationsScreen 占位** + +`notifications_page.dart`:标题 `t.notifTitle`,内容 = 3 条示例通知(类型 pill 用 `t.notifType*`,未读点)+ 空态 `t.notifEmpty`(数据为空时)。`// TODO(spec-3): 列表来自 GET /v1/notices,已读态本地持久化`。 + +- [ ] **Step 2: 桌面 shell 换真屏** + +`desktop_shell.dart` `content()` 的 notifications case 从占位 `SizedBox` 换成 `NotificationsScreen(t: t, embedded: true)` + import。 + +- [ ] **Step 3: 移动顶栏挂铃铛** + +`app_top_bar.dart` 加 `Widget? trailing` 入参并渲染在右侧;`account_page.dart:146` `AppTopBar(brand: t.brand, trailing: NotificationBell(hasUnread: true, onTap: () => open(NavView.notifications, NotificationsScreen(t: t))))`。 + +- [ ] **Step 4: analyze** + +Run: `cd client && flutter analyze lib/screens/notifications_page.dart lib/widgets/app_top_bar.dart lib/screens/account_page.dart lib/shell/desktop_shell.dart` +Expected: No issues found。 + +- [ ] **Step 5: Commit** + +```bash +git add client/lib/screens/notifications_page.dart client/lib/widgets/app_top_bar.dart client/lib/screens/account_page.dart client/lib/shell/desktop_shell.dart +git commit -m "feat(notif): 通知占位屏 + 移动顶栏铃铛(Spec ③接口占位)" +``` + +--- + +### Task 7: 设置页归三组 + Web 链接下沉 + +**Files:** +- Modify: `client/lib/screens/settings_page.dart:20-22,54-112` +- Test: `client/test/widget/settings_ia_test.dart` + +**Interfaces:** +- Consumes: `t.settingsGroupConn/Appearance/About`(Task 2)。 + +- [ ] **Step 1: 写设置分组测试(失败)** + +`settings_ia_test.dart`:pump `SettingsPage`(provider override),断言: +```dart +expect(find.text(t.settingsGroupConn), findsOneWidget); +expect(find.text(t.settingsGroupAppearance), findsOneWidget); +expect(find.text(t.settingsGroupAbout), findsOneWidget); +// 购买入口不在设置页(归 Account) +expect(find.text(t.purchaseTitle), findsNothing); +``` + +- [ ] **Step 2: 跑测试确认失败** + +Run: `cd client && flutter test test/widget/settings_ia_test.dart` +Expected: FAIL。 + +- [ ] **Step 3: 改 settings_page.dart** + +- 删第一张「购买套餐 + 我的订单」卡(75-89)+ 顶部 `orders_page/payment_page/purchase_page` import(20-22)+ `open()` helper(61-67)。 +- 三张卡分组:① `_SectionLabel(t.settingsGroupConn)` + 开关组(autostart/autoConnect/smartRoute/killSwitch);② `_SectionLabel(t.settingsGroupAppearance)` + 语言 + 深色;③ `_SectionLabel(t.settingsGroupAbout)` + 协议 + 检查更新 + Version + **底部** webUserCenter 行(降到最后)。 + +- [ ] **Step 4: 跑测试确认通过** + +Run: `cd client && flutter test test/widget/settings_ia_test.dart` +Expected: PASS。 + +- [ ] **Step 5: analyze + golden** + +Run: `cd client && flutter analyze lib/screens/settings_page.dart && flutter test --update-goldens 2>&1 | tail -3` + +- [ ] **Step 6: Commit** + +```bash +git add client/lib/screens/settings_page.dart client/test/widget/settings_ia_test.dart client/test/golden/ +git commit -m "feat(settings): 删购买/订单卡 + 归连接/外观/关于三组 + Web 链接下沉底部" +``` + +--- + +### Task 8: 全量验收 + 收尾 + +**Files:** 无(验收) + +- [ ] **Step 1: 全量 analyze** + +Run: `cd client && flutter analyze` +Expected: 0 error(仅 pre-existing 3 条 info)。 + +- [ ] **Step 2: 全量测试** + +Run: `cd client && flutter test 2>&1 | tail -3` +Expected: All tests passed。 + +- [ ] **Step 3: DS 双闸** + +Run: `cd /Users/wangjia/code/pangolin/.claude/worktrees/pay-v2-integration && node tools/check-l1-sync.mjs && node design/prototype/tools/check-ds.mjs` +Expected: 图标同集 OK + l10n 完整 OK + check-ds 5 项过。 + +- [ ] **Step 4: 更新决策日志** + +`docs/pay-v2-dev-log.md` 追加「IA 重构落地」step。 + +- [ ] **Step 5: Commit** + +```bash +git add docs/pay-v2-dev-log.md +git commit -m "docs(pay-v2): IA 重构落地验收记录" +``` + +--- + +## Self-Review + +**Spec coverage**(对照 `account-ia-reorg-design.html` §5 落地改动清单): +- navigation_provider 加 invite ✅ Task 1 +- desktop_shell 侧栏 Account + 顶栏铃铛 + content 分支 ✅ Task 5 +- account_page 重排/邀请/删偏好卡/formFactor 门控 ✅ Task 4 +- settings_page 归组 + Web 下沉 ✅ Task 7 +- account_screens RedeemScreen 删去哪买 ✅ Task 3 +- 顶栏铃铛槽(content_top_bar + app_top_bar)✅ Task 5/6 +- 邀请/通知占位屏 ✅ Task 4/6 +- l10n ✅ Task 2 + +**已就绪不做**(盘点确认):设备⋮菜单、登录/注册、SegSwitch、图标、支付/订单 l10n。 + +**留给后续 spec**:邀请奖励引擎/归因/防刷(Spec ②)、通知 model/provider/api/下发/已读持久化(Spec ③)——本计划仅占位屏 + `// TODO(spec-N)` 接口标注。 + +**依赖顺序**:1→2→(3,4 可并)→5→6→7→8。Task 5 前向引用 NotificationsScreen(Task 6),已注明临时占位处理。