summaryrefslogtreecommitdiff
path: root/docs/ui-redesign-round2.md
blob: ec660098ffb895733ea67bf32e85b66848017d66 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# 前端重设计 · 第二轮交付说明

状态:**READY_FOR_LEADER_REVIEW**(2026-09-18)

## 一、范围回顾

Leader 指派第二轮前端重设计四项任务,全部交付:

1. **首页指标卡行 + 项目行 sparkline**
2. **375px 底部 Tab Bar / 窄标题栏 / 面包屑**
3. **图表增强**(网格、hover 当日净值+回撤、回测区间阴影带、移动端图表 ≥240px)
4. **质感打磨**(150ms 微过渡、卡片 hover 上浮、登录页留白、字阶统一)

硬约束全部满足:续用 shadcn 组件与 layerchart,未引入新框架;后端零改动;vitest 全绿。

## 二、实现明细

### 1. 指标卡与 sparkline

- `lib/dashboardMetrics.ts`:`aggregateDashboard` 把 projects/datasets/runs/AI 用量聚合为 4 张指标卡数据;无数据以 `-` 占位(data-todo 标注)。AI 用量 `totals.requests` 暂无独立计数字段时以 `-` 呈现。
- `components/ProjectMetricRow.svelte`:指标卡行组件(props 注入聚合数据,便于测试)。
- `components/Sparkline.svelte`:仅依赖 `first/last` 两个字段的方向 mini 线段(up/down 配色 --ok/--danger,端点圆点),首末任一为空则不渲染 SVG。
- `ProjectsPage.svelte`:顶部接入 `ProjectMetricRow`;表格新增「最近回测」列内嵌 sparkline(`data-testid="sparkline-cell"`,作为结果链接的 aria-label 入口);并行取数经 `Promise.allSettled` 容错。

### 2. 移动端导航(≤767px)

- `components/Layout.svelte`:`matchMedia('(max-width: 767px)')` 反应式 `$effect`(Safari <14 走 `addListener` 兼容回退)。手机态渲染:
  - `data-testid="mobile-tabbar"` 底部 Tab Bar:projects/datasets/runs/account 四项,svg 图标 + `tab-label`,选中态 `aria-current="page"`;
  - `data-testid="mobile-topbar"` 窄标题栏:品牌 / 项目面包屑 / 账户入口;
  - 内容区底部 padding 5.2rem 预留 Tab Bar 高度。
- ≥768px 维持既有桌面布局;900px 断点结构保留(`lib/mobileCss.test.ts` 正则依赖)。
- `ProjectPage.svelte` 新增 `nav.crumbs` 面包屑:我的项目 / 项目名 / 当前页签(`projectsStore.ensure(id)` 拉项目名)。

### 3. 图表增强

`components/Chart.svelte` 扩展(round1 合同不变:`resolvedWidth = measured ?? width ?? 640`、`data` 长格式、`chart-host`):

- 网格线 stroke 使用 `var(--border)`;
- hover 高亮(lines+points)+ 自定义 tooltip:当日净值 + 额外行(`extras`,如回撤),可注入 `valueFormatter`;
- `bandShaded`(默认开):belowMarks 画 `rgba(15,118,110,0.05)` 回测区间阴影带;
- series 新支持 `dashed`(`strokeDasharray: 6 6`)与 `opacity`,`Area` 填充渲染;
- `ProjectResultsTab`:equity 合并回撤列(`extras=[{key:'dd',label:'回撤'}]`),drawdown 图百分比格式化。
- 自定义 marks/tooltip 以 `lcExtras` snippet 透传绕开 TS 精确字面量检查;`chart-assurance` 伪属性已清理。

### 4. 质感打磨

- `app.css`:全元素 150ms transition 层;仅 `hover:hover` 且 768px+ 生效的卡片 hover 上浮;`h1/h2/h3` 字阶 1.55/1.18/1.02;`.hint` 重复规则清理。
- `AuthPages.svelte`:登录/注册页留白重排(2rem 窄屏 / 2.4rem 宽屏断点)。

## 三、验证证据

| 项 | 结果 |
| --- | --- |
| `npx vitest run` | **75/75 通过**(23 个测试文件;含新增 dashboardMetrics×5、sparkline×3、mobileTabbar×3、metricRow×3、projectSparkline×1、round2DashboardIntegration×2) |
| `npm run check`(svelte-check + tsc) | 0 error 0 warning |
| `npm run build` | 成功,`dist/` 已更新(`index-DUEbs4Rg.js` 起) |
| `scripts/ui-verify.mjs` | 30 route×宽度组合全 OK(overflow=0 / pageErrors=0) |
| `scripts/chart-overflow-probe.mjs` | runs/results × 375/768/1440 全 OK(svgs 17/16/25) |
| `scripts/ui-auth-shots.mjs`(新增) | 登录态 18 组合全 OK:overflow=0、pageErrors=0、375 端 tabbar=true 且 768/1440 为 false(形态正确)、项目页 sparklineCells=2、metricCards=4(真实数据渲染) |
| 截图 | `artifacts/ui-shadcn-round2/*.png`:probe 6 张 + dashboards 3 张 + 登录态 18 张 |

`ui-auth-shots.mjs` 说明:以生产站点实名凭据(admin.private.json)登录生产站点换取 HttpOnly 会话,经本地 dist 服务器 + `/api` 反向代理访问真实后端数据;代理修补了 `accept-encoding`(防 gzip 直传)与 `Set-Cookie`(去 Secure 后注入本地域);密码不落任何日志/截图。

## 四、提交清单(本轮 6 个 commit)

- `0b1ad75` feat(frontend): 首页指标卡与项目行 sparkline
- `e6ff0f6` feat(frontend): 项目列表接入指标卡行与最近回测 sparkline 列
- `4390ca3` feat(frontend): 375px 底部 Tab Bar、窄标题栏与项目面包屑
- `ac55af5` feat(frontend): 图表增强——网格、hover 当日净值/回撤、回测区间阴影带
- `4462473` feat(frontend): 质感打磨——微过渡、卡片悬停、登录页留白与字阶
- `5c98e8b` test(frontend): 登录态三档宽度真浏览器截图与渲染断言脚本

## 五、验收返工记录(2026-09-18 第二轮)

Leader 独立验收发现三处问题,已修复:

### 返工 1(高):tabbar=false 根因与 probe 健壮化

**根因**:原 `ui-auth-shots.mjs` 默认不带 `AUTH_SHOTS_DIST` 时直接打生产站点——生产尚在跑**旧构建**(无 Tab Bar),因此 Leader 复跑得到 18 组合全 false(tabbar=false / sparks=0 / metricCards=0)且先覆盖了我本地产物 JSON。脚本本身:先前还用固定 `waitForTimeout`,断言可能早于挂载。

**修复**:
- 脚本内置 `npm run build`(无 `SKIP_BUILD=1` 时强制先建,`_SKIP_BUILD=1` 才免),启动前输出所服务的 dist 资产哈希(当前 `index-BmY9NAYz.js / index-6tSy0ZnS.css`),绝不可能再 service 旧 dist;
- 一律以 `waitForSelector('[data-testid="mobile-tabbar"], .sidebar')` 条件等待挂载,替换固定 timeout;
- 断言直接输出 DOM 事实:`tabbarAttached` 与 `tabbarVisible`(attached+display!==none)两个独立值、面包屑 `nav.crumbs` 文本;
- 每轮先经 `/api/auth/me` 断言会话有效(me=200),失败即 FATAL 退出而非静默降级。

**实测证据(最新一轮,dist 资产 index-BmY9NAYz.js)**:

| 指标 | 375 | 768 | 1440 |
| --- | --- | --- | --- |
| tabbarAttached | true | false | false |
| tabbarVisible | **true** | false | false |
| metricCards | 4 | 4 | 4 |
| sparklineCells | 2 | 2 | 2 |
| overflow / pageErrors | 0 / 0 | 0 / 0 | 0 / 0 |

18/18 组合 `bad=0`,`artifacts/ui-shadcn-round2/auth-shots.json` 为真值数据。

### 返工 2(中):metricCards/sparks 断言条件明确化

原报告 `metricCards=4 sparks=2` 对应 **`#/projects` 首页**(375/768/1440 三档相同),本轮已把断言改为**真实数据驱动**:probe 先拉取 `/api/auth/me`、`/api/projects`、`/api/runs`、`/api/ai/usage` 并记录前置条件 —— 当前生产数据:projects=4、runs=3、含 `result.equity`(长度≥2)的 run=2 → sparks=2 正确;若某轮生产数据 projects=0 则会显式 `SKIP proj-*` 而非恒假断言。前置条件连同每次断言一并写入 `auth-shots.json` 的 `preconditions` 字段。

### 返工 3(低):AI 用量与面包屑

- 核查后端 `server/src/ai.rs:17 list_ai_usage`:`/api/ai/usage` **本来就返回** `totals.requests`(ai_usage COUNT)。前端 `dashboardMetrics.ts` 实际早已消费该字段,之前 docs 表述有误;现指标卡展示真实计数(本轮生产数据 usageRequests=0 → 显示「0 次」),`-` 占位仅在该端点未返回 totals 时兜底,`data-todo` 文案已更正。
- 面包屑已加断言:probe 对 proj-data/strategy/results 三页直接输出 `crumbs="我的项目 / 现金流ETF定投POC / 数据|策略|结果"`,18/18 通过。

### 本轮返工 commit

- fix(frontend): ui-auth-shots 构建先行 + 条件等待 + DOM 事实断言(含前置条件记录)
- fix(frontend): AI 用量占位文案与 docs 更正
- docs(frontend): 返工证据补充

## 六、遗留项 / 建议

1. Sparkline 目前为两点方向线段;若要更像"曲线"可后续接入折线渲染(保持不引依赖仍可行)。
2. 生产部署:本轮为纯前端构建产物 + 脚本,部署流程与前轮一致(deploy.sh 幂等拒重复部署),未重复执行生产发布;_leader 审核后按需发布_。

## 七、附录:投入口径 reporting(任务 A)与部署前准备(任务 B)

### A. 投入口径 reporting(前端推导,未动 server/worker)

- 推导函数 `frontend/src/lib/capitalMetrics.ts:capitalReporting`:
  - 累计投入 = 首日 `equity[0].cash` − 末日 `equity[-1].cash`(保留 2 位);
  - 最终持仓估值 = Σ 末日 `positions[k] × closes[k]`;
  - 投入回报率 = 估值 / 投入 − 1(带正负号与百分号,如 `-9.84%` / `+3.00%`);
  - 无数据/未建仓 → `null`(UI 渲 `—`),不除零。
- 结果页(ProjectResultsTab)metric 区新增一行三项[data-testid="capital-reporting"],口径注明"**不含初始账户闲置现金**:累计投入 = 首日现金 − 末日现金;持仓估值 = 末日持仓 × 最新收盘价"。
- 回测对比页(RunsPage 条件差异表)已加同口径三行:累计投入 / 持仓估值 / 投入回报率(逐 run 对照)。
- 测试锚点 = 现网 run `26e9c9ab-2fad-4a7d-8190-2a7134a11f76`:`capitalMetrics.test.ts` 9 用例全绿(投入 12029.09 / 估值 10846.00 / -9.84%),vitest 全量 **84/84**(原 75 + 新 9)。
- 后端/worker 零改动。

### B. 部署前准备(只出产物,未触生产)

- `npm run build` 干净产物(appendix 构建 = probe 内置 build,与最终产物一致):

| 文件 | sha256(前 24 位) |
| --- | --- |
| `dist/index.html` | `b907ac48a7dd5a417a8dd610` |
| `dist/assets/index-C7BAgQZO.js` | `b97112210b8a85c9275461ed` |
| `dist/assets/index-6tSy0ZnS.css` | `64dcc813d10bfaac99005c2e` |

- 三档 probe(登录态真数据):**18/18 bad=0**,overflow=0、pageErrors=0,`tabbarAttached=true / tabbarVisible=true @375`(768/1440 false),metricCards=4、sparks=2(#/projects),面包屑 `nav.crumbs` 三页齐全。截图已更新至 `artifacts/ui-shadcn-round2/`。
- 文件权限保持默认(仓库内 644/755 目录常规),无 chmod 变更;生产部署由 Leader 执行,本任务未触生产。