Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
25 KiB
账户/设置/联系 信息架构重构 — 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。唯一后端改动 = 订单过期态(Task 0,display-only 零迁移)——付款/订单核心后端已完成并部署生产。邀请奖励引擎、通知下发机制不在本计划(各自 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.mjsred。默认语言英文。 - 图标单源:
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 0: 后端 — 订单过期态(display-only,零迁移)
Files:
- Modify:
server/internal/pay/handler.go(orderViewFromRow+orderPendingTTL常量) - Test:
server/internal/pay/handler_test.go
Interfaces:
-
Produces: 订单视图
status新增可能值"expired"(供前端映射)。判定:本地台账status=="created"且created_at + orderPendingTTL < now→ 视图显expired(DB 不改,与金额兜底同为展示层)。 -
Step 1: 写过期判定测试(失败)
在 handler_test.go 加:
// 老 created 单超 TTL → 列表视图显 expired;近期 created → 仍 created。
func TestList_ExpiredWhenStale(t *testing.T) {
router, st := newHandlerRig(t, nil)
ctx := context.Background()
// 直接构造两条 created 单,改 created_at 到很久以前 / 刚刚
if err := st.Insert(ctx, 1, "uuid-1", "pro_month", "old", "alipay", 2999, "CNY"); err != nil { t.Fatal(err) }
if err := st.Insert(ctx, 1, "uuid-1", "pro_month", "fresh", "alipay", 2999, "CNY"); err != nil { t.Fatal(err) }
// 把 old 的 created_at 回拨到 TTL 之前
if _, err := st.db.ExecContext(ctx,
`UPDATE pay_purchases SET created_at = ? WHERE out_trade_no = 'old'`,
time.Now().Add(-2*orderPendingTTL).UTC()); err != nil { t.Fatal(err) }
w := httptest.NewRecorder()
router.ServeHTTP(w, authed(httptest.NewRequest(http.MethodGet, "/v1/pay/orders", nil), 1))
var resp struct{ Orders []orderView `json:"orders"` }
_ = json.Unmarshal(w.Body.Bytes(), &resp)
got := map[string]string{}
for _, o := range resp.Orders { got[o.OrderNo] = o.Status }
if got["old"] != "expired" { t.Fatalf("old 应过期, got %q", got["old"]) }
if got["fresh"] != "created" { t.Fatalf("fresh 应 created, got %q", got["fresh"]) }
}
注:
st.db是Store的私有字段,测试同包(package pay)可访问。
- Step 2: 跑测试确认失败
Run: cd server && go test ./internal/pay/ -run TestList_ExpiredWhenStale
Expected: FAIL(old 仍为 created)。
- Step 3: 实现过期判定
handler.go 加常量 + 在 orderViewFromRow 里判定:
// 未付订单展示层过期窗口(近似 pay 会话时效;仅影响列表/详情显示,不改台账)。
const orderPendingTTL = 30 * time.Minute
在 orderViewFromRow 组装 v 后、return v 前加:
if row.Status == "created" && row.CreatedAt.Add(orderPendingTTL).Before(time.Now()) {
v.Status = "expired" // 展示层:台账仍 created,可继续支付/取消
}
- Step 4: 跑测试确认通过
Run: cd server && go test ./internal/pay/
Expected: ok(含新测试 + 原有全绿)。
- Step 5: Commit
git add server/internal/pay/handler.go server/internal/pay/handler_test.go
git commit -m "feat(server/pay): 订单展示层过期态(created 超 TTL 显 expired,零迁移)"
部署:本任务改 server,需重编 linux 二进制 + 部署 pangolin1(Task 8 统一部署,或单独发)。
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:
enum NavView { connect, servers, stats, account, contact, settings, plans, redeem, devices, purchase, payment, orders, invite, notifications }
第 10 行 kAccountSubViews 加 invite(notifications 走全屏,不入子页返回集):
const Set<NavView> 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
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 的「购买/支付」段后加一段(照现有注释风格):
// ── 邀请好友(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
// ── 订单过期态(Task 0 后端产出) ──
String get orderStatusExpired; // 已过期 / Expired
- Step 2: 6 份 strings 各实现(带 @override)
每份 strings_<lang>.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
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<ContactChannel> kContactChannels(single source);class ContactChannel { IconData icon; String name; String sub; bool accent; }。 -
Step 1: 建单源常量
contact_channels.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',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
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),断言:
// 邀请入口存在
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
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 前插:
(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
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
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),断言:
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_pageimport(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
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 7.5: 订单页处理 expired 态
Files:
- Modify:
client/lib/screens/orders_page.dart(状态→pill 映射)
Interfaces:
-
Consumes: 后端
status=="expired"(Task 0)、t.orderStatusExpired(Task 2)。 -
Step 1: orders_page 状态映射加 expired
orders_page.dart 的 _statusPill(created→connecting / paid→connected / canceled→neutral)加一支:
'expired' => (status: PangolinStatus.neutral, label: t.orderStatusExpired),
过期单动作沿用 created 的「继续支付」(台账仍可 retry;retry 若遇 pay 会话过期会 409→客户端已有重新下单兜底)。
- Step 2: analyze
Run: cd client && flutter analyze lib/screens/orders_page.dart
Expected: No issues found。
- Step 3: Commit
git add client/lib/screens/orders_page.dart
git commit -m "feat(client/orders): 订单列表/详情显示 expired 过期态"
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: 服务端重编 + 部署(Task 0 改了 server)
cd server && go test ./... && GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -o /tmp/pgl-new ./cmd/server
scp /tmp/pgl-new pangolin1:/tmp/pgl-new
ssh pangolin1 'cp -a /usr/local/bin/pangolin-server /usr/local/bin/pangolin-server.bak-ia && mv /tmp/pgl-new /usr/local/bin/pangolin-server && chmod +x /usr/local/bin/pangolin-server && systemctl restart pangolin-server && sleep 2 && systemctl is-active pangolin-server'
Expected: active。验证 curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/v1/pay/orders → 401(路由在)。
- Step 5: 更新决策日志
docs/pay-v2-dev-log.md 追加「IA 重构落地 + 订单过期态」step。
- Step 6: Commit
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),已注明临时占位处理。