Skip to content

feat(overview): 「近 7 天」脱离历史 200 条上限,并支持近 7/30 天 × 条数/字数/时长切换 - #909

Merged
H-Chris233 merged 7 commits into
Open-Less:betafrom
bigsongeth:feat/overview-period-metrics
Aug 5, 2026
Merged

feat(overview): 「近 7 天」脱离历史 200 条上限,并支持近 7/30 天 × 条数/字数/时长切换#909
H-Chris233 merged 7 commits into
Open-Less:betafrom
bigsongeth:feat/overview-period-metrics

Conversation

@bigsongeth

@bigsongethbigsongeth commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

User description

问题:概览页的「近 7 天」会说谎

「近 7 天」柱状图从 list_history() 现算,而历史有 200 条硬上限。日均上百次听写的用户,两三天就把上周挤没了。

实测本机真实数据(history.json 正好卡在 200 条):

日期近 7 天显示年度活动实际
07-29040
07-300118
07-310108
08-01037
08-021044
08-03158156
08-043232

前四天全画成 0,而同一个页面上方的年度热力图那四天是亮的。同一页两块数据自相矛盾,用户没法判断该信哪个。

改动

1. 换数据源

「近 7 天」改读 activity 存储 —— 它保留两年、只存聚合数字、与历史保留策略解耦,本来就是年度热力图的数据源。改完之后柱子和格子同源,对得上。

2. activity 记录字数与时长

activity 此前每天只存一个条数。要支持「近 30 天说了多少字」,得先扩成 {count, chars, durationMs}。写入点手边就有 polishedraw.duration_ms,口径刻意与已有 UI 对齐:

  • chars = 最终插入文本的 Unicode 字符数,与历史详情页的「N 字」一致
  • durationMs = 录音时长,不含识别/润色耗时,与详情页「录音 x.x 秒」同源

磁盘格式向后兼容:老用户的 activity.json 全是裸数字({"2026-08-01": 5}),读不回来的话年度热力图会一次性清空,会被当成数据丢失。用 untagged enum 兼容,四个单测钉住契约:纯旧格式、纯新格式、新旧混排(升级当天必然出现)、缺字段的对象。

旧日期没有字数/时长,读回 0 —— 诚实缺省,好过整天丢掉条数。

存储代价可忽略:只存聚合数字、不存文本,一天一行,两年上限 731 行。

3. 周期 × 指标切换

卡片标题位变成「近 7 天 / 近 30 天」,右侧原来的「条数 / 天」变成「条数 / 字数 / 时长」。

卡片顶部显示周期总计(大字)+ 日均:想知道「这个月总共说了多少字」是要一个数,不是在 30 根柱子里目测求和。30 天模式下柱子只有几像素宽,逐柱数字会糊成一片,改为悬浮显示、横轴只标首/中/末三个日期。

4. ⌘R 刷新

历史页早就有这个键,概览页没有 —— 想看到新数据只能切到别的页再切回来触发重挂载。一次刷新本页三份数据(历史、活动、凭据),preventDefault 拦掉 webview 默认的整页 reload。

实现注记

  • 聚合逻辑抽成 lib/activityMetrics.ts 带单测:窗口长度恒定、缺失日期补 0、跨月边界、老数据无 chars 时不产生 NaN(NaN 会把柱状图的 max 算坏)
  • 日期键一律用本地年月日拼,不能用 toISOString() —— 后者按 UTC 切日,东八区凌晨的会话会算到前一天,与后端 chrono::Local 写的键对不上
  • 活动数据的 IPC 不再按 mobile 跳过。热力图在移动端仍不渲染(fix(android): 移动端隐藏概览页「年度活动」热力图 #861),但周期指标卡要渲染,跳过 IPC 会让它在移动端永远空

验证

cargo check / cargo test --lib / npm test / tsc --noEmit 全绿;浏览器里逐个切过两个周期 × 三个指标,核对总计与逐日数值;本地装机实测。

🤖 Generated with Claude Code


PR Type

Bug fix, Enhancement, Tests


Description

  • activity 存储新增每日字数与时长聚合

  • 近 7/30 天指标改用 activity 数据源

  • 支持条数/字数/时长切换与总计、日均

  • 兼容旧格式数据,新增测试与多语文案


Diagram Walkthrough

flowchart LR
A["Daily dictation"] --> B["ActivityStore bump(count, chars, durationMs)"]
B --> C["getActivityStats"]
C --> D["Overview PeriodMetricsCard"]
D --> E["7 / 30 days × count / chars / duration"]
Loading

File Walkthrough

Relevant files
Enhancement
7 files
history.rs
Extend activity stats response with chars and duration
+9/-3
dictation.rs
Record char count and duration on session end
+10/-6
activity.rs
Expand activity store to aggregate chars and duration
+133/-9
types.rs
Add chars and duration fields to ActivityDay
+9/-1
activityMetrics.ts
Add period series builder for activity metrics
+77/-0
types.ts
Add optional chars and duration fields to ActivityDay
+4/-0
Overview.tsx
Replace week chart with switchable period metrics card
+269/-63
Localization
5 files
en.ts
Add period and metric localization strings
+15/-2
ja.ts
Add Japanese period and metric localization strings
+15/-2
ko.ts
Add Korean period and metric localization strings
+15/-2
zh-CN.ts
Add Simplified Chinese period and metric localization strings
+15/-2
zh-TW.ts
Add Traditional Chinese period and metric localization strings
+15/-2
Tests
2 files
activityMetrics.test.ts
Add tests for period series and legacy entries
+86/-0
mock-data.ts
Update mock activity days with chars and duration
+14/-1

@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

861 - Partially compliant

Compliant requirements:

  • 热力图组件在移动端仍不渲染(渲染条件保持移动端门控)。
  • 桌面端热力图渲染路径未改动,设置开关逻辑保留。

Non-compliant requirements:

  • 移动端仍然会调用 getActivityStats():本 PR 移除了原来的 if (mobile) return 守卫,并在每次挂载概览页时拉取活动数据。

Requires further human verification:

  • 需要在 Android / 窄屏真机上验证「年度活动」卡片确实不显示。
  • 需要人工验证移动端底边栏宽度与对齐、无横向溢出。
  • 需要人工验证桌面端设置开关仍能正常开/关热力图。
⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Mobile IPC regression

The previous mobile guard that skipped getActivityStats() on Android / narrow viewports was removed, so every Overview mount now performs the activity IPC request and transfers the full daily-stats payload even when the heatmap is hidden. This contradicts ticket #861's acceptance criterion that mobile must not call getActivityStats(), and reintroduces the mobile WebView cost it was meant to avoid.

const[activity,setActivity]=useState<ActivityDay[]|null>(null);const[activityError,setActivityError]=useState(false);constrefreshActivity=useCallback(()=>{setActivityError(false);getActivityStats().then(setActivity).catch(error=>{console.error('[overview] failed to load activity stats',error);setActivity(null);setActivityError(true);});},[]);useEffect(()=>{refreshActivity();},[refreshActivity]);

bigsongethand others added 7 commits August 5, 2026 23:30
activity.json 此前每天只存一个条数。概览页想给出「近 30 天说了多少字」
这类周期指标时,只能回头从 list_history() 现算 —— 而历史有 200 条硬上限,
日均上百次的用户两三天就把上周挤没了,算出来的字数与时长必然偏低。
把单日值从裸数字扩成 {count, chars, durationMs}:
- chars 口径 = 最终插入文本的 Unicode 字符数,与历史详情页的「N 字」一致;
- durationMs 口径 = 录音时长,不含识别/润色耗时,与详情页「录音 x.x 秒」同源。
两个口径都刻意跟已有 UI 对齐,避免同一个数在两处对不上。
磁盘格式用 untagged enum 兼容旧文件:老用户的 activity.json 全是裸数字,
读不回来的话年度热力图会一次性清空(会被当成数据丢失)。旧日期没有字数/
时长,读回 0 —— 诚实缺省,好过整天丢掉条数。四个单测钉住这个契约:纯旧
格式、纯新格式、新旧混排(升级当天必然出现)、以及缺字段的对象。
存储代价可忽略:只存聚合数字、不存文本,一天一行,两年上限 731 行。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
两件事,同一张卡:
1. 修数据源。「近 7 天」此前从 list_history() 现算,而历史有 200 条硬上限。
实测(日均约 88 次听写的真实数据):history.json 正好卡在 200 条,只装得下
两天多,于是近 7 天里前 4 天全画成 0 —— 而同一页的年度热力图上那 4 天分别是
40 / 118 / 108 / 37,亮着的。同一个页面两块数据自相矛盾。改读 activity 后
两者同源,柱子和格子对得上。
2. 加周期与指标切换。卡片标题位变成「近 7 天 / 近 30 天」,右侧原来的「条数 /
天」变成「条数 / 字数 / 时长」。月度字数是用户实际想看的数(跟按月计费的
竞品比较时要用),此前完全看不到。
卡片顶部显示周期总计(大字)+ 日均:想知道「这个月总共说了多少字」是要一个数,
不是在 30 根柱子里目测求和。30 天模式下柱子只有几像素宽,逐柱数字会糊成一片,
改为 title 悬浮显示、横轴只标首/中/末三个日期。
聚合逻辑抽成 lib/activityMetrics.ts 并带单测:窗口长度恒定、缺失日期补 0、
跨月边界、老数据无 chars/durationMs 时不产生 NaN(NaN 会把柱状图的 max 算坏)。
日期键一律用本地年月日拼,不能用 toISOString() —— 后者按 UTC 切日,东八区凌晨
的会话会算到前一天,与后端 chrono::Local 写的键对不上。
顺带:活动数据的 IPC 不再按 mobile 跳过。热力图在移动端仍不渲染(issue Open-Less#861),
但周期指标卡是要渲染的,跳过 IPC 会让它在移动端永远空。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
历史页早就有这个键(History.tsx),概览页没有 —— 想看到新数据只能切到别的
页再切回来触发重挂载。⌘R 是「重新加载」的通用直觉,两个数据页应该一致。
一次刷新本页全部三份数据:历史、活动、凭据。preventDefault 拦掉 webview
默认的整页 reload,只重发 IPC,不让整个前端重挂载。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
finalText.length 按 UTF-16 码元计数,emoji / CJK 扩展 B 等增补平面字符
会被双算;后端 activity 的 chars 用 polished.chars().count()(Unicode 标量),
概览页「字数」指标也以此为准。改用 Array.from(...).length 后三处同口径。
概览页顶部「今日字数」此前用 finalText.length 按 UTF-16 码元计数,emoji / CJK 扩展 B 等增补平面字符会被双算;历史详情页(commit 70464a1)与后端 activity 的 chars 已是 Unicode 标量口径。抽成 lib/unicode.ts 的 countCodePoints 让两处前端共用,消除第三处口径漂移。
⌘R 一次触发历史、活动、凭据三份刷新,历史与活动此前没有 credentialsRequestSeq 那样的防竞态:快速连按可能让较早的慢响应后到并覆盖新数据。沿用同一模式,请求前自增序号、响应校验一致才 setState。
SegmentedToggle 是可点击按钮,与 _atoms 中 Btn 的 cursor: pointer 交互约定保持一致(此前为 default,看起来像不可点)。
@H-Chris233
H-Chris233force-pushed the feat/overview-period-metrics branch from 70464a1 to 97b5461CompareAugust 5, 2026 15:31
@H-Chris233
H-Chris233 merged commit 27198b9 into Open-Less:betaAug 5, 2026
4 checks passed
bigsongeth added a commit to bigsongeth/openless that referenced this pull request Aug 5, 2026
beta 一次进了 13 个提交,其中三条正是本地 daily 上那批私货合上游了
(Open-Less#910 历史落点+重试、Open-Less#908 翻译、Open-Less#909 概览),daily 的 patch queue 相应缩小。
冲突只有 selection.rs 一处,而且 HEAD 侧是空的:beta 的选区润色 macOS 移植
(Open-Less#926)在同一位置加了 current_front_app_pid 与 Windows/其他平台的
current_front_app,本分支在附近加了 bundle_id_for_pid(闸门按元素归属判定用)。
两者互不相干,取 beta 侧即可。
解完核对:bundle_id_for_pid 仍在(1 处),current_front_app 三个定义分属
macOS / Windows / 其他平台且 cfg 互斥,current_front_app_pid 无重复。
验证:cargo check 干净、cargo test --lib 1032 passed(beta 带来 30 个新用例)、
tsc --noEmit 干净、npm test 通过。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bigsongeth@H-Chris233