Ctrl K
ComunidadGuías…

CHANGELOG.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# 更新日志

## [0.11.28] - 2026-11-10

### 体验优化

- **统计页月份切换定位优化**:月份胶囊固定在页面正中,右侧「回到本月」简化为「本月」并改为绝对定位;按钮出现/消失不再把月份胶囊顶得左右移动

## [0.11.27] - 2026-11-10

### 体验优化

- **统计页月份切换改为整体胶囊控件**:箭头与月份收进同一个 `bgAlt` 胶囊(与日期面板共用),整组居中显示,「回到本月」紧邻其右;不再出现两个圆形按钮分散在页面两侧的松散感,选中月份也更像一条明确的标题

## [0.11.26] - 2026-11-10

### 体验优化

- **日期面板 UI 打磨**:头部固定展示完整选中日期(不再与底部「今天」重复);已选今天时隐藏无效的「今天」按钮;日期胶囊增加下拉箭头提示可点;未来日期改为更清晰的禁用灰;卡片底部留白与顶部平衡
- **统计页月份切换与日期面板统一**:抽出共用 `MonthSwitcher`(32px 圆形箭头 + 紧凑月份标签),月份条改为左对齐;右侧「本月」弱化为「回到本月」文本动作;总览卡补充「收支总览 · 时间范围」标题,滚动后不丢失月份语境
- **月份切换加载反馈**:查询期间月份条右侧显示「加载中…」,总览数字轻微变淡,数据返回后恢复

## [0.11.25] - 2026-11-09

### 新功能

- **统计页支持切换历史月份**:月度视图新增月份导航条(`‹ 2026年11月 ›`),可逐月回看任意历史月份;翻到过去月份时右侧出现「本月」快捷返回,未来月份不可选。总览、分类占比、分类排行、成员统计、支出趋势与分类下钻均按所选月份重算
- 预算对比仅在当前月显示:预算只保存了当前月额度,用历史月份支出对比会误导

## [0.11.24] - 2026-11-09

### 体验优化

- **日期面板改为居中卡片**:此前是贴底全宽面板,视觉上偏低且笨重。现改为浮层卡片,宽度与记账页卡片一致(左右各留 16),圆角/描边同款,并加柔和阴影;整体垂直居中,日历行高略收紧,比例更协调

## [0.11.23] - 2026-11-09

### 体验优化

- **日期选择器风格统一**:此前调用的系统日历是白底 Material 弹窗,与暖色圆角界面割裂。现改为应用内自绘的日历底部面板:暖白卡片、圆角、靛蓝主色选中态,「今天」带描边标记;只能翻到当前月、未来日期置灰不可点;面板内选中后点「确定」才生效,点遮罩/取消不修改原日期

### 工程

- 移除 `@react-native-community/datetimepicker` 原生依赖,日期面板改为纯 JS 实现(减少原生模块与包体)

## [0.11.22] - 2026-11-09

### 新功能

- **记账可选择日期**:记账页收支切换下方新增日期胶囊,默认「今天」;点一下拉起系统日历,可选择过去某天补记(不允许未来日期,避免污染本月/近 7 天统计)。非今天补记时保存提示会带上日期(如「已记 10月25日 支出 ¥12.00」);记完自动回到今天,**连记模式保留所选日期**,便于连续补录同一天的多笔
- **编辑记录可改日期**:明细页 / 统计分类下钻的编辑页新增「记录日期」胶囊,保存后记录按新日期归入明细与统计;时分秒沿用原记录(同日排序不乱),若改到更晚日期导致时间超前,则收敛到当前时刻

### 工程

- 新增 `@react-native-community/datetimepicker`(Expo SDK 56 匹配版本 9.1.0),使用系统原生日期选择器;日期标签工具 `formatDateLabel` / `mergeDateKeepTime` 补充 4 项单测(共 36 项)

## [0.11.21] - 2026-10-26

一轮代码审查后的数据一致性与交互修复。

### Bug 修复

- **合并导入产生重复记录**:合并策略此前直接 INSERT,同一份备份导入两次(或导入本机已有数据)会生成同 uuid 的两行——明细与月度合计翻倍,同步也只更新其中一行。现按 `uuid + 当前账本` 去重(含备份文件内部的重复 uuid),导入结果改为显示「实际新增」条数
- **备份携带同步水位**:脱敏规则只匹配固定键名,漏掉了按账本动态拼接的 `sync.last_push_at.<ledgerId>` / `sync.last_pull_at.<ledgerId>`,水位会随备份导出;导入到另一台设备后跳过区间内的家人变更或本地未传变更。现改为按 `sync.` 前缀统一剔除
- **设备时钟偏快导致家人编辑长期不生效**:服务端 push 时已把时间钳到服务端时刻,但本地行仍是未来值,pull 的 `远端时间 > 本地时间` 永远为假;且 push 水位被顶到未来后,本地新记账再也传不上去。现本地时间超前时以服务端版本为准,同步结束把本地未来时间回落到服务端时间、并把异常水位拉回,下一轮自动补传
- **周期记账备注被输入法挡住**:备注是表单最后一项,Dialog 窗口在 Android 15+ edge-to-edge 下不随键盘压缩,一弹键盘就盖住。现把备注上移到金额下方(名称 → 金额 → 备注 → 类型/分类/频率),编辑时始终可见
- **明细/统计页成员筛选残留**:正在筛选的成员被移出家庭或自己退出登录后,筛选胶囊整行隐藏但筛选 id 仍在,列表/合计一直查空且无处复位。现随成员列表变化自动回到「全部」

### 体验

- **记录金额可点击编辑**:此前编辑热区只覆盖图标与分类信息,点金额没有反应;现金额也进入编辑页(删除按钮仍独立在外,不影响无障碍操作)

## [0.11.20] - 2026-10-22

### 修复(收入脱敏的漏口)

- **切到后台也会重新遮上收入**:0.11.19 只在"换 Tab 再回统计页"时重新遮,所以停在统计页直接切 App/下拉通知栏/进多任务,回来数字还亮着。现在监听 `AppState`,只要离开 `active`(含 `inactive`,正是系统截取多任务预览的那一刻)就收回成 `¥ ****`

## [0.11.19] - 2026-10-22

### 新增(统计页收入脱敏)

- **统计页的「收入」默认脱敏显示为 `¥ ****`**,点一下出真数、再点收回(和支付宝/钱迹一类的金额隐私做法一致);标签旁带一把小锁:🔒 已遮、🔓 已显示
- 只在收入那一格,支出/预算/待报销照常显示;**切走再回统计页会自动重新遮上**,否则遮一次就永久失效
- 读屏会念「收入已脱敏,点击显示实际金额」/「收入 ¥xxx,点击隐藏」,不靠颜色和小图标传达状态
- 这是本地显示层的遮罩,不影响数据与同步;不做持久开关——默认就是遮的

## [0.11.18] - 2026-10-17

### 回退

- **撤销 0.11.17 的备注框改动**(`f307ae3`),记账页与编辑页都回到之前的样子:「+ 添加备注」的热区高度、`keyboardDidShow` 后补 `paddingBottom` 再滚到底、备注框的「完成」键全部撤掉
- 撤掉的直接原因是它在记账页做了**重复的键盘补偿**:记账页那张卡是「上半 ScrollView + 卡底固定数字键盘」,卡底外面已经包着 `KeyboardAvoidingView`;再给滚动内容加一个键盘高的 `paddingBottom` 并强制 `scrollToEnd`,等于把金额区和数字键盘一起顶出可视范围,看起来就是"改坏了"
- 因此**「点备注框不顺畅」这个问题目前是未修复状态**(0.11.17 之前什么样,现在还是什么样)。下一版要动它得换路子:只补一层(要么交给 `KeyboardAvoidingView`,要么自己算高度但不叠加),并且不能把数字键盘滚出屏幕

## [0.11.17] - 2026-10-17

### 优化(触感反馈——第二次调振动,这次换了路子)

上一版只是把波形调轻,你没说清楚"哪里难受"也没关系——问题本来就不在大小上:expo-haptics 在 Android 上走的是**直接驱动马达打自定义波形**(`Vibrator.vibrate(createWaveform(...))`,轻档=50ms@30),而不是系统触感通道。Android 的触感设计原则把这类效果单列为「蜂鸣(buzz):噪杂、尖锐、穿透性强,振动停了还留余震」,举的坏例子恰恰是"按键按下后振动时间过长";expo 自己的 API 声明也写着"Vibrator API is not recommended for haptics feedback, use performAndroidHapticsAsync"。所以在它上面怎么调参数都只是选"哪种蜂鸣"。

- **Android 改走 `HapticFeedbackConstants`**(`View.performHapticFeedback`):波形由厂商按你这台设备调校、跟随系统触感开关、机型不支持时平台自己回退;iOS 继续用 UISelection / UINotification。
- **新增档位:设置 → 偏好 → 触感反馈 = 关闭 / 精简(默认)/ 完整**,选完当场给一段样本,不用等下个版本才能试。
- 默认「精简」**数字键盘按键、分类与收支切换不再振动**,只在记一笔成功、失败或删除时反馈一下——这是 Things 3、Cash App 一路的克制:确认只出现在有意义的状态变化上。
- 想要按键反馈就切「完整」,那里用的是 `SEGMENT_FREQUENT_TICK`(系统定义:"预期极轻,连续触发也不难受;设备做不出足够轻的振动时干脆就不振"),并加 25ms 去重,避免同一次点击连发两下叠成嗡声。

### 修复(记账时的备注框)

- **点不中的第一半是热区太小**:「+ 添加备注」实际可点区域不到 20dp 高,第一下经常点空。现在 40dp 最小高 + 上下各 12dp 的 `hitSlop`。
- **点完输入框被键盘盖住**:原先在 `onFocus` 时滚到底,那一刻键盘还没弹起、滚的位置没意义;Android 15+ 全屏模式下系统也不再压缩窗口,卡底的数字键盘和它上面的备注行一起被键盘挡住,看起来就像"点了没反应",得再点一次。现在改成监听 `keyboardDidShow` 拿真实高度 → 给内容补同高的 `paddingBottom` → 延一帧再滚到底,备注行精确停在键盘上沿(与编辑页已验证的做法对齐)。
- 备注框加「完成」键:写完按一下直接收起键盘回到数字键盘。编辑页(长按记录)同步以上三项。

## [0.11.16] - 2026-10-14

### 优化(报销页显示方式)

- **去掉套在条目外面的大框**:原先「待报销合计 + 一键全部核销 + 所有记录」整块被一张卡包住,每条记录又在卡里各带一个框,两层框叠着显重。现在汇总单独一张小卡,记录列表和明细页一样直接铺在页面上(每条一格)

### 新增(报销页按成员筛选)

- 顶部加「👨‍👩‍👧 全部 / 😎 成员A / …」筛选条(多成员账本才显示,与统计页同款交互);**待报销合计、笔数、列表都跟着筛选走**,全家一起报账时能分开看每个人垫了多少
- **「一键全部核销」只核销当前筛选出来的那些**:选中成员时按钮变成「核销 TA 的」,确认框也写明是谁的多少笔——批量写会同步到全家设备,作用范围必须和屏幕上看到的条目一致
- 选中的成员被移出家庭或退出登录时自动回到「全部」,不会停在一个查无此人的空列表上
- 数据层 `getReimbursableSummary` / `getReimbursableRecords` / `markAllReimbursed` 统一加可选 `userId`(0=全部),SQL 已用独立数据库验证过筛选与批量更新的作用范围

## [0.11.15] - 2026-10-14

### 优化

0.11.14 把核销按钮放到图标下面之后,行内出现了新的不对齐:图标和按钮被捆成一列,右侧的分类名与金额相对这一整列**垂直居中**,于是"交通"掉到了中间、日期行又压在按钮下面。本版改成**两行按行对齐**:

- 第一行=图标 + 分类/记账人/状态徽章 + 金额(分类行回到上方,与图标顶对齐)
- 第二行=核销/撤销按钮(行首,与图标同宽 40dp、同左边缘)+ 日期 + 备注,两者同一水平线
- 没有核销按钮的页面(明细、统计分类明细)第二行仍按原缩进对齐分类文字,观感不变

## [0.11.14] - 2026-10-14

### 修复

- **报销页窄屏重叠**:「核销」按钮盖在「待报销」徽章上(只剩个"消"字露出来)。原因是主行右侧要同时放「核销 + 金额」,左侧又要放「分类名 + 记账人 + 状态徽章」,在 393dp 宽的屏上总宽超出一行 —— RN 默认不裁剪溢出的子节点,于是按钮直接压在徽章上面
- 按「核销放到分类图标下面」的思路改:**图标自成一列,核销/撤销按钮就在图标正下方**(与图标同宽 40dp,左边缘对齐),右侧动作区只剩金额,主行腾出约 68dp,实测这一屏留出 37dp 余量
- 另给分类行加 `flexWrap`:更窄的屏(或系统大字号)下徽章会换到第二行,而不是再次溢出压到金额上
- 编辑热区相应变成「分类信息 + 日期备注」两块(图标不再可点,核销与删除仍各自独立可被读屏聚焦)

## [0.11.13] - 2026-10-14

### 优化

- 管理页「家庭同步」的连接灯**紧跟在标题后面、与文字同一行**(0.11.12 放在了行尾箭头旁,离字太远,看着像另一列的东西)

## [0.11.12] - 2026-10-14

起因:报销管理页「看不到具体备注信息,不好区分」。查下来不是备注没存,而是**行宽被挤没了**:那一行的右侧要放「核销」按钮和金额,左侧信息列被压到约 60dp,而日期胶囊自己就占掉约 56dp —— 备注和它排在同一行、允许收缩,于是被压到零宽,屏幕上看着像"这条记录没有备注"。明细页没有核销按钮,所以同样的代码在那边只是截断、在这边是彻底消失。

### 修复

- **记录行的日期与备注改为整行宽度单独占一行**(左边缘与分类文字对齐):不再和「核销 + 金额」抢宽度,报销页的备注、记账人、日期都能看全;备注由 1 行放宽到 2 行,长备注不再只露两三个字
- 顺带修好同一页缺的两处明细:**记账人标识**(此前该页没传成员缓存,全家一起报账时分不清是谁垫的)和**点击条目进入编辑**(此前该页条目点不开,长备注没有任何地方能看全)
- 影响面:明细页、统计页分类明细用同一个行组件,备注同样变成整行宽、最多两行(只是能看更多字,位置不变)

### 优化(触感反馈调柔)

用户反馈「振动太大了,点击反馈不舒服」。查 `expo-haptics` 的 Android 实现后确认这不是错觉:它在 Android 上是**直接驱动马达的固定波形**(不走系统触感通道),`notificationAsync` 因此天生偏重——成功是「40ms+停顿+40ms」双段共 180ms,失败是三段共 330ms,而这两档此前被用在记一笔成功、金额校验失败这类日常高频动作上。

- **日常确认改为单段轻击**:`hapticSuccess` 由 `notificationAsync(Success)`(双段 180ms)改为 `impactAsync(Soft)`(单段 50ms,振幅 30)
- **错误提示保留但减重**:`hapticError` 由三段 330ms 改为 `impactAsync(Heavy)`(单段 60ms,振幅 70)——错误仍要能被察觉,但不再是一串长震
- `hapticLight`(数字键盘按键等,已是单段 50ms 最轻档)不变;三个入口仍集中在 `src/utils/haptics.ts`,以后调强度只改这一处

### 新增(管理页状态灯)

- **「家庭同步」入口行右侧加连接状态灯**:绿=已登录且 `/api/health` 探测通过,灰=未配置 / 未登录 / 服务器不可达 / 正在检测。此前管理页只有一行文字摘要,看不出"同步到底还活着没",得点进二级页才知道
- 探测随管理页摘要一起刷新(进入该 Tab、同步完成、登录态变化),并沿用同步页的 `mounted` + 序号防护,切走页面后旧响应不会回来盖掉新结果
- 读屏会念出「服务器已连接 / 未连接」,颜色不是唯一线索

## [0.5.9] - 2026-10-08(服务端,镜像已发布 GHCR:`0.5.9` + `latest`)

### 修复(可诊断性,接 0.11.11)

- **四种 401 现在各有各的文案,不再共用「登录已过期,请重新登录」**(实测逐一验证):
  - 过期 → `登录已过期(超过 365d 未使用),请重新登录`
  - 验签失败 → `登录凭证校验失败:服务端签名密钥可能已变更…若全家同时出现请检查 .env 的 JWT_SECRET` ← 与"自然过期"以前完全同句,恰好是本次「两台手机同一天被退出」最需要区分的两种
  - 签名有效但账号查不到 → `用户不存在:…请先确认数据卷与数据库是否还是原来那份`(这类被当成"重登就好"会掩盖一次真实数据丢失)
  - `token_version` 不匹配 → 改密导致的预期撤销,说明不变
- **401 开始写入容器日志**:`[auth] 401 原因=… uid=… path=… ip=…`。此前日志里只有启动行和每日备份,掉线事故在服务端侧完全无痕;同一「原因+账号」每分钟最多记 5 条,避免公网扫描刷爆日志
- 客户端无需改动:0.11.11 起同步页/提示直接用服务端原文,升服务端后这些区分立刻生效

## [0.11.11] - 2026-10-08(含服务端 [0.5.9])

起因:两台手机的账号同一天都被退出了。查下来 APP 里只有两处会清登录态——手动退出、以及同步收到 401 时的自动清理;所以两台同时掉线必然是服务端回了 401。但**代码里有个缺陷让这件事当场无法判断是哪一种**:401 有语义完全不同的三种原因,却被统一压成一句「登录已过期,请到同步页重新登录」,而且后台自动同步这条路径没有任何 UI 出口,用户只会发现"不知道什么时候被退出了"。本版把原因透出来,并补上"NAS 上到底在跑哪一版"这个此前只能登容器才能确认的信息。

### 修复(APP)

- **401 不再抹掉服务端给的具体原因**:提示直接用服务端原文,并区分三类处置
  - 「登录已过期,请重新登录」=token 到期,或服务端换过 `JWT_SECRET`(**全员同时掉线最常见就是这个**)→ 重新登录即可
  - 「登录状态已在其他设备变更」=有人在别处改了密码(`token_version` 递增,属预期行为)→ 用新密码登录
  - 「用户不存在」=服务端账号/数据库不见了(换 `DATA_DIR`、数据卷没挂上、从旧备份回滚)→ 提示升级为 ⚠️,让用户先确认服务端数据再决定要不要重新注册,别急着记新账
- **自动同步遇到 401 现在会主动弹提示**(6 秒,不阻塞操作):此前只有手动点「立即同步」才看得到错误,后台路径静默清完就走
- 本地账本不受影响:掉线只是停止同步,记账照常,重新登录后未推送的变更按水位继续上传

### 新增(便于自查在跑哪一版)

- `GET /api/health` 返回 `version`(服务端 0.5.8 起):`{"ok":true,"version":"0.5.8","time":…}`。此前 tag 会被 `latest` 覆盖、`docker images` 看不出内容,版本只在容器启动日志里
- APP 同步页的连接状态直接显示它:「已连接服务器 · 服务端 v0.5.8」;对 0.5.7 及更早的服务端不显示版本(留空而不是写「未知」误导人)
- 服务端内部把版本号读取收敛到 `server/src/version.ts` 一处(启动日志与健康接口共用)

### 已排除的一项

不是新加的 `token_version` 校验误伤:老库由 `ALTER TABLE` 幂等补列默认 0,旧 token 无 `tv` 载荷时按 0 处理,实测升级不会把已登录设备踢下线(该项在 v0.5.6 发布前已验证并写进部署文档)。

## [0.11.10] - 2026-10-07

### Bug 修复

- **从分类明细返回后,统计页不该滚回顶部**:统计主页此前只渲染当前那一支(`page === 'main' ? 主页 : 子页`),进入分类明细/报销页时主页被**卸载**,返回时重新挂载,ScrollView 自然回到 y=0,刚看到的排行位置丢了。改为三个页面常驻、用 `display` 互斥切换(与 App 切 Tab、明细页编辑态同一做法),主页滚动位置与已挂载状态都保住
  - 语义按「同一页内的上下钻不重置滚动,只有切 Tab 才回顶」:切 Tab 回顶的行为保留,但因为主页不再是重新挂载,同帧 `scrollTo` 可能作用在刚由 `display:none` 切回可见的视图上,故放到下一帧(`requestAnimationFrame`)执行
  - 顺带收益:报销管理页返回统计主页同样不再丢位置;统计主页的图表也免去每次往返重新布局

## [0.11.9] - 2026-10-07

### 新增

- **分类明细里可以直接改某一条账**:统计页点「支出分类排行」进入某分类的明细后,点任意一条(图标+名称/备注那块热区,与明细页一致)即进入编辑页,改金额/收支类型/分类/备注/待报销后保存。编辑完仍停在这个分类的明细列表上——保存会广播 `RECORDED`,本页 `tick` 递增触发下钻查询重跑,所以列表就地更新;把某条改成别的分类时它会从本列表移出(合计与笔数随之变小)
  - 做法上是**复用明细页那同一个 `EditRecordModal` 组件**,不是再写一个编辑器,也不是跳回明细 tab:0.11.4/0.11.5 两轮修复(RNModal 独立 Dialog 窗口导致卡底数字键盘点不动 → 改并列页)才把触摸与键盘避让弄对,重复实现等于再赌一次。该组件本身是 `flex:1` 的普通页、对宿主无耦合,只要求宿主用「并列页 + display 互斥」承载它,统计页的分类明细分支照此接上
  - 顺带把组件注释里"由 LedgerScreen 负责互斥"的措辞改成"由宿主负责(明细页、统计页分类明细)",避免下一个接宿主的人以为只能挂在明细页里
  - 边界:从统计 tab 切走、返回统计主页、重新点另一个分类,都会收起半开的编辑页;Android 返回键优先关编辑页(它后注册,故先消费),再按才退出分类明细

## [0.5.7] - 2026-10-05(服务端,镜像已发布 GHCR:`0.5.7` + `latest`)

### 新增(服务端)

- **启动日志打印版本号**:容器启动即输出 `[boot] TapLedger server v0.5.7 监听 :8420` 与 `[boot] NODE_ENV=…|自动备份 开启/已关闭`。动机是实际运维里识别不了在跑哪一版——compose 里写 `latest` 时 tag 会被覆盖,`docker images` 也看不出内容,而 NAS 上用 `docker compose logs --tail 20` 一眼就能看到版本。版本号取自镜像内的 `/app/package.json`(`dist/index.js` 的上一级),读不到时降级为「未知」而不是拖停服务
- 部署文档同步跟上:`DEPLOY_DOCKER.md` 的「升级 / 回退」加了用日志确认版本的命令,并补一句「升到 v0.5.6+ 不会把已登录设备踢下线」(`token_version` 由老库自动补列,旧 token 无 `tv` 载荷按 0 处理)

## [0.11.8] - 2026-10-05(含服务端 [0.5.6],随后服务端另有 [0.5.7] 见上)

本版是一次**全仓库审查后的集中修复**:同步的安全与正确性、数据层语义、两处交互反馈(记一笔后点不动 / 分类图标排布)、构建与发布链、以及一直过期却没人发现的文档。

### 新增

- **统计页分类下钻**:点「支出分类排行」任意一行 → 进入该分类在当前时间区间(及当前成员筛选)下的具体明细,顶部给「合计 ¥X · N 笔」,行内含日期/备注/记账人标识,返回键与顶部「‹ 返回」都能回统计页。合计与排行条上的数字**必然相等**:新查询 `getRecordsByCategory` 的 WHERE 与 `getCategorySummary` 逐字对齐(同样 `type='expense'`、同样排除待报销、同样按 `user_id` 过滤),所以列表里看不到那笔走报销的支出,副标题也写明「不含待报销」;已用同构 SQL 断言两侧合计一致(含跨账本、区间外、墓碑、成员筛选四类干扰行)

### 安全(服务端 0.5.6,实测复现后修复)

- **跨账本写入(严重)**:`sync/push` 的 upsert 以 uuid 为冲突键,但 `WHERE` 不校验该行原本属于哪本账本。实测:另一个家庭的用户把已知 uuid 推进**自己的**账本,即可改写别人家庭账本里那条记录的金额/备注(数据仍留在原账本)。被移除的前成员也照样能做——他的本机副本保留着全家 uuid,直接 push 家庭账本会 403,但绕道个人账本就能改。修复:三张表的 `DO UPDATE` 都加 `AND <表>.family_id = excluded.family_id`
- **记账人错位(严重)**:push 时服务端无条件把 `user_id` 注入为「推送者」,且更新分支也覆盖它 → 家人改一笔金额,这笔的记账人就变成他,成员支出排行与报销归属一起失真。修复:`user_id` 只在插入时落定,编辑不改作者
- **未来时间戳永久压制**:客户端时钟超前(改错时区/手动调时间)时,那条记录的 `updated_at` 落在未来,之后所有正常编辑都被 LWW 判为「更旧」而静默丢弃。修复:服务端把 `updatedAt` 钳制到服务端时钟,只压版本号、不丢数据
- **无改密通道**:365 天长效 JWT + 无状态,密码泄露只能销号。新增 `POST /api/auth/password`(需当前密码复核、5 次/分/IP),改密时 `users.token_version +1`:本端换发新 token 继续用,其它设备旧 token 立即 401。老库靠幂等 `ALTER TABLE` 补列,旧 token 无 `tv` 载荷按 0 处理,升级不会把全家踢下线
- 其余:`/admin` 页面在未配置 `ADMIN_TOKEN` 时也返回 503(此前恒定 200,等于公开后台入口);面板口令改为**勾选才**写 localStorage;限流表与邀请码锁定表加过期清理,不再只增不删

### 数据正确性

- **被拒变更不再静默丢失**:服务端把「未生效」拆成 `skipped`(版本更旧,幂等丢弃)与 `invalid`(字段超限,本地已存但传不上去)并回 `invalidIds`;客户端同步页如实提示「N 条超出字段限制未上传」。此前客户端完全不看 push 响应、照推水位,那条数据就永远停在我有家人没有
- **导入按服务端上限归一**:`SERVER_LIMITS` 统一 note/category/name/label/emoji/uuid 长度与数值范围,超限在导入入口截断或丢弃并计入 skipped,而不是到服务端才被拒
- **「替换导入」不再硬删本地**:改为按 uuid 身份 upsert + 对备份里没有的行打墓碑。硬删不产生墓碑 → 服务器与家人设备留着旧记录,换机/重装后被 pull 回来,表现为「替换没生效、账目翻倍」(与 `resetPersonalLedger` 早已采用的墓碑语义对齐)
- **分类主键改为 (key, ledger_id)**:单列 `key` 主键让个人账本与家庭账本的同名分类互相顶掉——同 key 插不进去(合并导入静默计 failed),而按 key 改名/标删又会影响另一本账本。老库走幂等重建迁移(`PRAGMA table_info` 判主键列,重复执行安全)
- **pull 一律限定账本**:本地按 uuid 查找原先不带 `ledger_id`,家庭账本的增量能改写个人账本里的同 uuid 行;分类改名场景也改成「先删同 uuid 旧 key 行,再按复合主键 LWW upsert」,不再 `ON CONFLICT DO NOTHING` 永久吞掉一条
- **周期记账多设备不重复入账**:生成记录的 uuid 由「规则 + 到期日」决定,两台设备各自到期生成时服务端按同 uuid 收敛为一条(此前房租这类订阅可能被记两次)
- 报销三条 SQL 补 `type = 'expense'`:待报销只对支出成立,配合下面那条 UI 修复两头都堵
- `claimLocalRecordsAsUser` 只认领「尚未归属任何账本」的本地存量,不再把另一本账本里的无主记录标成自己

### 交互反馈(本次直接收到的两条)

- **记一笔后那 2 秒点不动别的图标**:透明 `RNModal` 在 Android 上是全屏 Dialog 窗口(原生只加 `FLAG_NOT_FOCUSABLE`,从不加 `FLAG_NOT_TOUCHABLE`),窗口内 `pointerEvents="none"` 只影响 RN 自己的手势派发,系统仍把这层之下的所有触摸交给它。Toast 改为**非模态浮层**:全局单例状态 + App 根一层渲染 + 每个表单弹窗内一层(弹窗是独立窗口,提示必须挂在同窗口才盖得住)。顺带修掉「4 个 tab 各挂一份 Toast,隐藏页的报错弹到当前页」「分类表单里 toast 不可见」。现在提示显示期间分类网格、数字键盘照常可点
- **添加分类时图标靠左**:宫格只有 `flexWrap` 没有居中,29 个固定 46dp 单元格左堆。改为**单元格按可用宽度等宽铺满**(列数自适应、最小 56dp 保触摸面积)+ `justifyContent: center`,最后一行也居中

### 其它修复

- 首页:「记一笔」加防重入(连记模式手快双击就是两条真实支出);切到收入时清掉待报销标记(此前存出收入+待报销的脏数据且界面上清不掉);`SETTINGS_CHANGED` 只在收支类型真的变化时才重置分类(改预算不再把已选分类顶回第一个);上次金额缓存解析只接受正数条目
- 明细页:加载补竞态守卫(四条并发加载路径此前慢的那轮回来会盖掉新数据);月度合计改走 `filteredRecords`,与列表同口径;切月不再在 `setState` updater 里调另一个 setter
- 同步页:`setSyncBusy` 进 `finally`(此前 `runSync` 一旦抛错,立即同步与账本选择永久置灰);网络回调加卸载守卫;退出登录同时复位本地账本作用域
- 报销页:一键核销加二次确认;成功后只广播一次事件(此前每个核销并发跑两轮查询)
- 周期规则表单:切收支类型按当前可见分类列表校正选中项(此前硬编码只认 food/housing,自定义分类切类型后「选中但看不见」仍被保存)
- 统计页:合并重复的事件监听(RECORDED/导入/同步 此前每个事件跑两遍);跨零点后切回本页重算时间窗口;趋势图空数据改为显示占位而不是空白图
- 无障碍:给缺角色的按钮补齐 `accessibilityRole`(现在全仓库 88 个 `Pressable` 都有角色,此前子页大量按钮只有点击没有角色);`RecordList` 拆掉「整行编辑 Pressable 包住核销/删除」的嵌套(读屏会把它们合并成一个节点,两个动作完全不可达),热力图补选中态与今天的单次日期计算,图标按钮字号 8.5sp→10sp
- App 根:自动同步的 debounce 路径接住 reject(此前是一次未处理拒绝、这轮同步静默消失),去掉重复广播的 `SYNC_DONE`;NetInfo 只在可达性真的变化时补同步
- `Modal` 删掉自 v0.7.1 起无人使用的底部 sheet 分支(连带去掉「轻扫即关」——表单填一半会被误滑丢弃且无二次确认)

### 构建与发布链

- `withReleaseSigning`:三处 build.gradle 文本注入全部加命中断言——此前模板一挪缩进就静默不注入,release 包带着 debug 签名照常构建成功(同包名可被覆盖安装),只有跑发布脚本时才被 apksigner 拦下;缺 `keystore.properties` 时开发回落并大声告警,设 `TAPLEDGER_REQUIRE_RELEASE_SIGNING=1`(发布脚本会设)则直接终止。`withVersionSync` 缺 version 时改为报错
- `release-app.ps1`:加 eslint + prettier 门禁、构建前先自检签名注入片段与 keystore 存在(省掉一次几分钟的白跑);`git add -A` 改为只暂存发布链路涉及的文件
- `docker-push.ps1`:本机 `docker build` 只产宿主架构,直接推会覆盖 CI 的 amd64+arm64 清单、arm64 NAS 拉不到镜像 → 优先走 `docker buildx` 多架构,无 buildx 时需显式 `-AllowSingleArch`;GHCR 凭据支持只含 `write:packages` 的环境变量 PAT,临时文件名随机化,结束时 `docker logout`
- `server/Dockerfile`:`npm install` → `npm ci`(此前镜像内容取决于构建日期)、运行层不再重装编译链、加 `HEALTHCHECK`(此前只有 compose 里配了,`docker run` 或群晖界面跑就没有存活探测)。**保留以 root 运行**:改 `USER node` 会让已部署实例写不进原 root 属主的 data/ 卷,要收紧请连部署文档的 chown 迁移步骤一起改
- 门禁全部本地化,并**删除仓库里两个 GitHub Actions workflow**(`ci.yml`、`build-and-push-server-image.yml`):APK 与 Docker 镜像一律本地构建,不再经 Actions。原计划放在 CI 里的检查改由本地脚本承担——`scripts/release-app.ps1` 补 `eslint` + `prettier --check` 与签名注入自检(构建前先跑,省掉几分钟白跑),`server/scripts/docker-push.ps1` 推送前先 typecheck + build + 断言「生产模式缺 JWT_SECRET 必须拒绝启动」。此前仓库没有任何自动门禁,`format:check` 已烂了 42 个文件没人发现
- 这次误加 CI 又踩出一个只在「非本地环境」才暴露的问题,值得记一笔:根 eslint 配置会连 `server/src` 一起 lint,服务端依赖没装时报 11 个 `import/no-unresolved`(express/bcryptjs)——本地两个 `node_modules` 都在,所以本地永远跑不出这个错
- `.gitattributes`:显式声明行尾与二进制(`docker-push.ps1` 出现过 8 个 UTF-8 BOM 叠在文件头的编码事故,根因就是没人声明)
- `tsconfig.json` 排除 `server`(根 typecheck 此前把服务端源码和它的 node_modules 一起拖进来);`sync-version.js` 顺带同步 `server/package-lock.json` 的 version(此前停在 0.4.0)
- 修正 `server/scripts/selftest.ps1` 与 README curl 示例漏掉 `ledgerId`(照抄必 400),README 示例里的 `accountUuid` 一并清掉

### 文档纠偏

- README 的「全部数据保存在本地,**无账号、无网络请求**」与同页的可选家庭同步自相矛盾,改为「本地优先,同步是可关闭的功能」
- README 项目结构树过期约四成:删掉不存在的 `AccountPicker.tsx` 与「账户/转账管理」,补上 `EditRecordModal`、`screens/manage/*`、`memberUtils`、服务端 `routes/{ledgers,admin}.ts`、`backup.ts`
- 「全部分类可**拖拽**排序」→ 实际是 ↑/↓ 按钮;设计令牌表里的「转账」改为辅助青
- `docs/COMMERCIAL_EVALUATION.md` 中被实测推翻的两条 ✅(访问控制「基本完备」、push 注入 userId 是优点)改写为修复记录;JWT 7 天等过期表述更新;`DEPLOY_DOCKER.md` 版本戳 0.5.2 → 0.5.6
- **已知未做**:安卓端「注销账号」入口本版已补,但 `DELETE /api/me` 的合规说明口径(他人账本内本人记录只归零不删除)仍需在用户协议里写清楚;服务端墓碑清理任务、pull 分页、字段上限的跨端单一来源仍待做

## [0.11.7] - 2026-10-01

### Bug 修复

- **编辑页备注仍被输入法遮挡(0.11.6 未根治)**:真机为 Android 15+(edge-to-edge 强制),系统对 `adjustResize` 已不再压缩窗口——视口高度不变,`scrollToEnd` 无从可滚,仅重滚一次无效。现改为**手动键盘避让**:监听 `keyboardDidShow` 捕获 IME 高度,动态给滚动内容补 `paddingBottom = 键盘高度` 后再滚到底,把备注行精确顶到键盘上沿;系统键盘弹起期间收起自定义数字键盘(其区域正被 IME 占据),收起键盘后自动恢复。iOS / Android 均按同一机制生效,不再依赖系统的 resize 行为。(首页不受影响:其备注行随内容自然排布在较高位置)

## [0.11.6] - 2026-10-01

### Bug 修复

- **编辑记录页备注被输入法遮挡**:备注框的 `onFocus` 滚动发生在系统键盘弹起、视口压缩**之前**,滚完又被键盘盖住,看不到正在输入的文字。现监听 `keyboardDidShow`,待键盘布局稳定后再滚一次,把备注行顶到固定数字键盘上方;数字键盘停靠区同时对齐首页改为 `KeyboardAvoidingView`(iOS 上键盘将整块停靠区顶起)

## [0.11.5] - 2026-10-01

### Bug 修复

- **编辑记录页彻底修复(0.11.4 遗留)**:0.11.4 的绝对定位覆盖层仍有三宗罪——absolute 子级无视父级 `paddingTop`,顶部栏顶进状态栏;页内再算一次底部安全区,键盘上方顶出幽灵空隙、整页被挤压;叠层触摸在部分机型仍不稳。现改为与 App 切 Tab 完全同款的**并列页 + display 互斥切换**:编辑页成为明细页根内的普通页面,触摸与安全区行为等同首页记账卡(首页键盘一直被证实可用);数字键盘重新稳定可点
- 切换到其他 Tab 时自动收起编辑页,避免回到明细页时停留在半开编辑态

## [0.11.4] - 2026-10-01

### Bug 修复

- **编辑记录页无法修改金额(0.11.3 遗留)**:根因不是键盘遮挡——编辑页此前用 RNModal 承载,Android 上它是独立 Dialog 窗口(RN 内部对 Dialog 强制 `disableEdgeToEdge` + 使用已废弃的 `ADJUST_RESIZE`),与 App 的 edge-to-edge Activity 坐标系不一致,卡底数字键盘的 Pressable 触摸不生效;备注之所以能改,是因为它走系统 IME 输入、不依赖窗内触摸。现重构为渲染在明细页 Activity 视图层级内的**绝对定位全屏页**:触摸与键盘避让(adjustResize)行为与首页记账卡完全一致;同时移除 0.11.3 的键盘高度补偿(会覆盖底部安全区,反致键盘行落入导航条触摸区)与 Android 上空操作的 KeyboardAvoidingView;系统返回键改为关闭编辑页。

## [0.11.3] - 2026-10-01

### Bug 修复

- **登录态 7 天失效导致同步静默失败**:服务端 JWT 有效期原为 7 天,家庭自用场景每周被迫重新登录,且过期后同步只报错不清登录态,用户以为仍正常。现服务端默认改为 **365 天长效**(可用 `.env` 的 `JWT_EXPIRES` 覆盖,需部署 server 0.5.5);客户端遇到 401 自动清理登录态并明确提示「登录已过期,请到同步页重新登录」,不再静默失败
- **编辑记录弹窗键盘遮挡**:RNModal 是独立 Dialog 窗口、不响应 Activity 的 `adjustResize`——编辑带备注的记录时键盘随 `autoFocus` 自动弹起,直接盖住卡底数字键盘(表现为「金额无法编辑」)和备注输入框。现监听键盘高度给弹窗容器加 `paddingBottom` 强制整卡顶到键盘上方;且仅用户主动点「+ 添加备注」才聚焦弹键盘,打开弹窗不再自动弹

## [0.5.5] - 2026-10-01(服务端,镜像已发布 GHCR:`0.5.5` + `latest`)

### 新增(服务端)

- **账号注销接口 `DELETE /api/me`**:满足应用市场对"涉及账号体系须提供便捷、真实有效的注销服务"的硬性要求。需携带当前密码复核(防误操作,不设其他障碍),并加 5 次/分/IP 限流。事务内级联:
  - 家庭创建者注销 = 解散家庭账本(删除该账本的记录/周期规则/自定义分类,其余成员自动解绑,其本机副本保留但不再同步)
  - 普通成员注销 = 仅解绑,家庭账本与历史记录归创建者
  - 删除个人账本及其全部数据;残留在他人账本中的记录 `user_id` 归零(避免成员列表出现幽灵)
  - 删除 users 行;JWT 无状态,旧 token 随即 401 失效
- 鸿蒙版已接入(`ApiClient.apiDeleteAccount` + 「管理 → 家庭同步 → 注销账号」)并端到端实测;**RN 客户端尚未接入**,安卓端补入口即可复用本接口
- **登录态长效(0.5.5)**:`JWT_EXPIRES` 由硬编码 `7d` 改为默认 `365d`,并支持 `.env` 覆盖(`JWT_EXPIRES=30d` 等)。家庭自托管属可信设备,无需定期重登;密钥(JWT_SECRET)不变则老用户 token 不受影响,新登录起生效

## [0.11.2] - 2026-09-26

### Bug 修复

- **同步页断网仍显示「已连接服务器」**:绿点状态此前仅由「已配置服务器地址」这一本地设置驱动,手机断网或服务器宕机时依旧常亮,误导用户以为同步正常。现每次进入同步页、每轮同步完成、登录态变化都会主动探测服务器健康接口(5s 超时),三态如实显示:「正在检测服务器…」(灰)/「已连接服务器」(绿)/「服务器不可达」(红 + 重试按钮)

## [0.11.1] - 2026-09-26

- **开源许可**:全仓库采用 **Apache License 2.0**(`Copyright 2026 sctale`)
  - 新增根目录 `LICENSE`(Apache-2.0 标准全文)与 `NOTICE`(归属声明)
  - README 顶部新增 License 徽章,文末新增「开源许可」章节(含义务简述:保留许可证与版权声明、修改需注明)
  - `package.json`(app 0.11.1)与 `server/package.json`(0.5.3)补充 `license: "Apache-2.0"` 字段
  - 纯许可/元数据变更,无功能代码改动

## [0.11.0] - 2026-09-12

全面代码审查修复版(安全 + 正确性 + 体验)。

### 安全

- **release APK 改用私有 keystore 签名**:此前 release 包沿用 debug keystore(公开已知),存在同包名恶意 APK 覆盖安装的升级劫持风险。新增 `plugins/withReleaseSigning.js` 在 prebuild 时注入私有签名配置(读取根目录 `keystore.properties`,不入库),发布脚本增加 `apksigner verify` 校验并拦截 debug 签名包。**⚠️ 升级提示:签名证书变更,老用户需卸载旧版后重装本版(记账数据请先在「数据管理」导出备份,或已登录家庭同步的账号重新登录拉回)**
- **备份文件脱敏**:JSON 导出不再携带登录凭证(`sync.token` 等 `sync.*` 私有键)——此前把备份分享给他人等于泄露账号,导入他人旧备份会覆盖/劫持当前登录态;导入侧同样过滤这些键

### Bug 修复

- **登录后记账人归属**:在首页未重启 App 的情况下完成登录/切换账号后,新记录仍以「未登录」归属(userId=0),家庭成员排行与「谁记的账」显示错误;现已随登录态事件即时刷新
- **统计「近 12 个月」跨年数据**:趋势图窗口跨年时,去年的月份查询范围被截断、柱子恒为空;现查询起点与回溯窗口对齐
- **replace 导入完整性**:「替换」策略此前只替换记录,周期规则与自定义分类被重复追加;现同事务清空后重建
- **周期记账新建当天补记**:新建规则当天会把创建日之前的到期日「补记」一条;现生成范围不早于规则创建日
- **双页返回键**:统计页与管理页同时打开子页时,按一次返回两页同时弹回主页;现仅激活页响应
- **Toast 层级**:同步/数据管理/报销页的提示此前画在滚动容器内会跟手滚走,且被全屏弹窗完全遮挡(弹窗报错不可见);现以透明原生层渲染,浮于一切内容之上
- **编辑记录保存失败**:失败时仅有震动反馈,用户误以为已保存;现弹窗内显示错误文案
- **默认收支类型即时生效**:偏好页改「默认记收入」后首页需重启 App 才生效;现即时应用
- **跨零点连记日期**:凌晨继续记账仍记在「昨天」;现保存时实时取当天日期
- **CSV 金额可求和**:导出 CSV 金额带千分位逗号导致 Excel 按文本处理无法求和;现输出纯数字
- **同步分类冲突兜底**:拉取自定义分类与本地 key 冲突时整轮同步回滚卡死;现跳过该条并继续
- **连续同文案 Toast**:连点两次同一提示,第二条会提前消失;现每次重置定时器
- **周期规则金额输入**:`1.2.3` 被静默截为 `1.2`;现归一为 `1.23`

### 体验

- 触摸热区:连记/待报销胶囊、分类排序按钮、删除按钮的可点击区域扩展至 ≥44dp 标准

### 工程

- 发布脚本:`gh release create` 改用 `--notes-file` 传递说明(规避 PowerShell 通配符干扰);新增 apksigner 签名校验步骤

## [server 0.5.2] - 2026-09-12

- **反向代理限流修复**:新增 `TRUST_PROXY=1` 开关(`.env` 配置)。经 Nginx/群晖反代部署时,登录/注册限流此前按代理 IP 聚合——一人触发全员 429;开启后按真实客户端 IP 计数。局域网直连无需配置

## [server 0.5.1] - 2026-09-09

- 文档:升级注意事项固化进部署文档——`server/README.md` 新增「升级到 v0.4.2+ 注意」(JWT_SECRET fail-fast 先查 .env、可选 ADMIN_TOKEN 面板、自动备份确认、回退方法);群晖教程第 7 节补升级自查清单,FAQ 新增「fatal 拒绝启动 / 注册 403 / 加入 429」三条排障(纯文档,无代码变更)

## [server 0.5.0] - 2026-09-09

管理面板(公网应急熔断):

- **运行时开关**:新增 `app_flags` 表 + `getFlag/setFlag`;`/register`、`/family/join` 分别受 `allow_register`、`allow_join` 拦截(关闭即 403,家人老账号登录不受影响)
- **极简面板**:`GET /admin` 单文件页(内联,随 tsc 构建产出)+ `/api/admin/{state,flags}`;即时切换开关、显示用户/家庭/记录数与最近同步
- **鉴权**:`.env` 配 `ADMIN_TOKEN` 才启用;未配一律 503、口令错 401,另有 10 次/分/IP 限流,`timingSafeEqual` 定值比较
- **文档**:`.env.example`、`server/README.md`、`DEPLOY_SYNOLOGY.md` 补充面板用法与安全说明
- 用途:服务器已通过反代暴露公网时,随时在网页端关闭「允许新用户注册」,阻断攻击链「注册 → 爆破邀请码 → 进入他人家庭账本」,无需重启容器

## [server 0.4.3] - 2026-09-09

数据安全(自动备份):

- **每日自动热备份**:用 SQLite 在线备份 API(无需停服)把数据库备份到 `data/backups/tapledger-YYYY-MM-DD.db`,默认保留最近 7 份(`BACKUP_KEEP` 可调、`BACKUP_DISABLED=1` 关闭);当天缺备则启动即补一份,之后每 24h 一份
- **文档**:`server/README.md` 与 `DEPLOY_SYNOLOGY.md` 更新备份/回滚步骤(停容器→删 `tapledger.db-wal/-shm`→用目标备份替换 `tapledger.db`→起容器),`.env.example` 补 `BACKUP_*` 说明

## [server 0.4.2] - 2026-09-09

服务端(公网可达场景)安全硬化:

- **JWT_SECRET fail-fast**:生产环境(`NODE_ENV=production`)未配置/沿用内置默认/短于 16 位时直接拒绝启动(此前仅 warn——默认密钥下任意用户 token 可被伪造,等于全家账本可被读取);本地开发仍仅 warn
- **注册限流**:`/api/auth/register` 3 次/分/IP(原无限流,公网可批量建号)
- **加入家庭限流 + 邀请码锁定**:`/api/family/join` 5 次/分/IP;同一邀请码连续错误 10 次临时锁定 10 分钟——封堵公网「注册→爆破 6 位邀请码进别人家庭账本」的路径
- **邀请码改用 CSPRNG**:`Math.random` → `crypto.randomInt`

## [0.10.3] - 2026-09-09

### 体验

- **「待报销」选中态去掉对钩前缀**:记账页与编辑页的「待报销」按钮选中时不再显示「✓」符号,文字恒为「待报销」,选中状态仅由胶囊底色 + 描边表达(与「连记」完全一致)

## [0.10.2] - 2026-09-09

### Bug 修复

- **成员筛选胶囊长昵称折行**:明细页/统计页顶部的「全部 / 成员」筛选切换胶囊,长昵称在部分设备会折成两行,胶囊被撑变形、名称显示不完整;现固定单行展示,名称过长时胶囊按内容变宽、整行横向滚动查看

### 体验

- **「待报销」与「连记」选中效果统一**:记账页与编辑页的「待报销」选中态由实色浅底(warningBg)改为与「连记」一致的样式——主色 9% 透明底 + 主色描边 + 深色文字(琥珀色系)

## [0.10.1] - 2026-09-08

### Bug 修复

- **单人家庭不显示记账人标识**:明细页记账人标识此前要求家庭成员 ≥ 2 人才显示,家庭只有 1 人时被拦截。现改为已加入家庭即显示(即使只有自己,家人后续加入时历史记录已有归属);未加入家庭的纯个人使用仍不显示(个人账本全是自己记的)。成员缓存未拉到时按本地登录态兜底显示「自己」

## [0.10.0] - 2026-09-08

围绕「启动速度 + 家庭账本体验」的 7 项优化(交互参考随手记):

### 新功能

- **明细点击编辑**:明细页(日历/流水两种模式)点击任意记录进入全屏编辑页,可修改金额(支持四则运算)、类型、分类、备注、待报销;保存后列表即时刷新并自动同步全家(LWW)。左滑删除保留不变
- **网络恢复自动同步**:新增网络状态监听(`@react-native-community/netinfo`),断网记账后联网自动补传;App 切后台再回前台也自动拉一轮同步

### 体验

- **冷启动提速**:启动不再等待服务器连接,先进 App 本地数据秒开,同步转后台静默执行(断网/服务器下线不再卡在启动页)
- **服务器地址脱敏**:同步页已连接后仅显示「已连接服务器」状态行,地址不再明文展示(截图/演示不泄露内网地址),点「修改」才进入编辑态
- **统计页时间显示统一**:支出趋势与支出占比标题右侧统一为简化标签(近 7 天 / 本月 / 近 12 个月);月度趋势数据同步从「近 30 天滚动窗口」改为「本月 1 日至今」,标题与数据一致
- **记账人标识兜底**:家庭成员列表尚未拉到时,按本地登录态显示「自己」的记账人标识,家庭账本明细不再缺标签

### 家庭统计

- **成员排行增加笔数**:成员支出排行显示「金额 · 占比 · 笔数」,配合顶部成员筛选(家庭/各成员切换,总览/占比/分类/趋势全维度过滤)完整还原随手记式按人统计

## [0.9.4] - 2026-09-06

### Bug 修复

- **「上次 ¥x」胶囊显示不全**:共用槽位高度 22 → 34,胶囊内边距与字号同步放大(xs → sm),文字不再被裁切;补 hitSlop 热区,点按更容易

## [0.9.3] - 2026-09-06

### 体验

- **记账页整卡固定、上下连为一张卡**:上半内容与数字键盘收进同一张圆角卡片,键盘经细分割线固定在卡底,金额区弹性居中吃掉剩余空间——备注拉起系统键盘时整卡稳定不重排(Android `adjustResize` 下键盘贴在系统键盘上方);上半区仅在极小屏时兜底滚动
- **「上次填入」与「= 结果」共用槽位**:两者状态互斥(金额为空 vs 含运算符),合并为金额与分类行之间的同一个固定高度槽位,出现/消失不再引起布局跳动

## [0.9.2] - 2026-09-06

### 体验

- **数字键盘固定停靠**:数字键盘与「记一笔」移出滚动区、固定停靠在页面底部,位置恒定;备注拉起系统键盘时键盘区不再跳动重排(Android `adjustResize` 下稳定贴在键盘上方),备注聚焦自动滚到输入框可见位置

## [0.9.1] - 2026-09-06

参考随手记的快捷记账体验,围绕「家庭共同记账」做了 5 项增强(保持轻、无新增原生依赖):

### 新功能

- **数字键盘四则运算**:键盘新增 `× ÷ − +` 右列,支持 `18+22.5` 这类算式;金额位显示友好表达式并实时给出「= ¥结果」,记一笔即按结果入账。求值自研(不用 `eval`),`* /` 优先于 `+ -`,尾部多按的运算符与除零自动容错
- **首页顶部标注当前账本 + 记账人**:卡片顶部显示「📒 账本名」与「头像 昵称」(未登录显示"未登录 · 本地"),多人连同一服务端共同记账时避免记错账本
- **连记模式**:记一笔卡片新增「🔁 连记」开关(持久化);开启后保存不弹提示、保留类型/分类/待报销标记,便于连续补记同类
- **记住各分类上次金额**:金额未输入时在分类下方出现「上次 ¥x · 点击填入」,一键复用最近一次该分类金额
- **明细页成员筛选**:日历与流水模式顶部新增「全部 / 各成员」筛选(仅家庭多成员账本显示),当日小计与本月合计随之过滤

### 体验

- **明细行左滑删除**:记录行支持向左滑动触发删除确认,纯 `PanResponder` 实现(不引入手势库),并保留行末 ✕ 作为兜底

### 工程

- moneyUtils 单测扩充运算符输入与表达式求值用例(含优先级、除零、四舍五入、尾部运算符容错)

## [0.9.0] - 2026-09-02

### Bug 修复

- **数据导入彻底修复**:JSON「合并 / 替换」导入此前因 `INSERT` 列与参数错位、且 `deleted` 被硬编码为 1,导致导入的记录全部变成隐形墓碑、字段串档(提示"已导入 N 条"却一条都不显示)。已修正列/占位符/参数对齐;「替换」也改为只替换当前账本,不再误清其它账本
- **新建周期记账修复**:`addRecurringRule` 的 `INSERT` 同样存在列错位,导致**新建规则被存成 `frequency=1`、永不自动生成**。重写后按正确列绑定,周期记账恢复正常

### 重构

- **账本本地隔离(ledger 作用域)**:本地「记录 / 周期规则 / 自定义分类」三表新增 `ledger_id`,DB 层维护活动账本 `activeLedgerId`,所有读写 / 汇总 / 统计 / 报销查询按当前账本过滤;`push` 只推本账本、`pull` 落库打标本账本,**同一台设备在个人与家庭账本间切换不再互相串数据**。`ledger_id=0` 表示本地未归属;登录后首轮同步用 `adoptUnassignedRowsIntoLedger` 把本地存量一次性认领进个人账本
- **重置当前账本改为墓碑**:由"跨表硬删"改为对当前账本写 `deleted=1` + 新 `updated_at`,下一轮同步把删除推到服务端且 LWW 不会复活,修复已登录用户"重置后数据被重新拉回"的问题;且只作用于当前账本
- **冷启动同步**:不再只认 `family_name`,只要配好服务器即拉一轮,个人账本用户冷启动也能同步
- **切换账本即时生效**:切换后立即切换本地读写作用域,并无条件通知各页刷新

### 体验

- **报销页单条核销 / 撤销**:报销明细每条可单独核销 / 撤销(此前仅有"一键全部核销")

### 清理

- **移除账户 / 转账遗留**:客户端不再创建 / 读写 `accounts`、`transfers` 表及 `account_*` 列;服务端 `pull` 不再返回 `accounts`/`transfers`,删除对应 UPSERT / schema / 类型(老部署物理表未 DROP,无害)
- 移除死代码:`clearAllRecords`、`constants.getCustomCategories`、`DEFAULT_ACCOUNT`、`SUB_PAGE_TOP_INSET`、服务端 `requireFamily`、HomeScreen 未用的成员缓存、SyncScreen 未用的 `activeLedgerName`、若干未用 import / 样式 / 变量
- `sync.family_name` 魔法串收进 `SETTING_KEYS.SYNC_FAMILY_NAME`

### 工程

- 修正管理页"数据完全保存在本地,不上传任何服务器"的误导性文案(改为"默认本地、家庭同步为可选自托管")
- 服务端未设置 `JWT_SECRET` 时启动告警
- 新增 ESLint + Prettier 配置与 `lint` / `format` 脚本、Vitest 纯函数单测(金额键盘、日期工具、周期规则)

## [0.8.2] - 2026-08-20

### 优化

- **分类预设图标精简**:预设图标从 74 个精简为 30 个精选常用图标,且不再按「支出/收入/通用」分栏,改为单组平铺宫格点选(仍保留自定义图标输入),选择界面更简洁

## [0.8.1] - 2026-08-20

### Bug 修复

- **周期记账记录归属记账人**:周期记账自动生成的记录现在会带上当前登录用户 id,家庭账本明细中能正确显示记账人的头像/昵称(此前 `user_id=0`,不显示记账人标识)
- **同步拉取事务化**:`sync/pull` 的本地合并改在单个事务内整体提交,任一条失败即整体回滚,避免半程写入导致水位不一致而永久漏拉

### 清理

- 移除已无监听者的 `ACCOUNTS_CHANGED` 事件及两处冗余 emit(v0.8.0 移除账户体系后的死代码)

## [0.8.0] - 2026-08-19

### 简化使用(移除账户体系)

- **彻底移除账户概念**:为降低使用门槛,砍掉账户管理(账户列表/余额/增删改/转账)。记账不再需要先选账户,直接记一笔收支即可——「3 秒记一笔」更纯粹
- **管理页精简**:移除「账户管理」板块与转账入口,管理页只保留 自定义分类/周期记账/家庭同步/偏好设置/数据管理
- **记账页精简**:移除账户选择器,保存记录自动归入默认账户
- **流水明细精简**:不再显示账户名,移除转账记录
- **统计页精简**:移除「总资产」卡,只保留支出/收入等收支统计
- **数据兼容**:历史账户数据不删除、原记录不受影响,只是不再展示/管理;家庭同步协议与服务器字段保持不变,不影响已接入的家庭成员
- **添加分类图标改进**:预设图标从"宫格常显"改为"点击一行预览框全屏弹出选择"(支出/通用/收入分组 + 自定义输入),不再占用表单空间(v0.7.4 引入,本次微调交互)

## [0.7.4] - 2026-08-19

### 优化

- **添加分类支持预设图标**:参考「随手记」,添加/编辑分类时表情图标从手动输入升级为预设图标网格点选(按支出/收入归属分组 + 通用图标,约 70 个),选中即时高亮并预览;仍保留输入框支持自定义图标

## [0.7.3] - 2026-08-19

### 新增

- **家庭账本切换(个人/家庭双账本)**:连接服务器注册后自动创建「个人账本」;家庭同步页新增账本选择器,可切换个人/家庭账本,各账本独立水位、数据彼此隔离互不污染;切换后自动拉取该账本数据
- **服务端账号体系升级**:注册自动创建个人账本(老用户惰性补建),新增 `GET /api/ledgers` 列出用户可访问的全部账本;`sync/pull`、`sync/push` 按 `ledgerId` 分区并校验访问权限
- **待报销明细显示日期**:报销管理进入后明细条目增加日期标签

### 优化

- **共同记账角色与名称**:参考「随手记」成熟方案,家庭账本成员显示创建者/成员角色徽标,可改昵称与头像,家庭共用;账本明细中每条记录的记账人(头像+昵称+专属配色)稳定显示

## [0.7.2] - 2026-08-19

### 新增

- **账户可编辑**:账户管理每个资金账户新增「编辑」入口(✎),可修改名称、类型、图标、颜色、初始余额;复用原全屏弹窗,默认账户同样可编辑。编辑会刷新账户 `updated_at` 以支持多端同步

### 优化

- **移除管理页重复的报销入口**:管理页功能列表中删除「报销管理」,与统计页入口重复;报销功能仅保留在统计页

### 修复

- **统计页结余显示负数**:移除总览卡中的「结余」项。原「结余」为期间收入-支出差值,花费大于收入时必然为负,容易与「总资产」概念混淆造成误解;总资产(各账户余额合计)已能准确表达真实余额,不再出现负值

## [0.7.1] - 2026-08-19

### 修复

- **隐藏分类仍显示在首页**:分类管理中隐藏的分类此前会因兜底逻辑被重新加入首页支出/收入分类列表,现已修复——`buildVisibleCategoryList` 在按配置顺序遍历时对所有分类(含隐藏项)都标记为已处理,隐藏项不再被末尾兜底循环重新添加

### 优化

- **所有输入型弹窗统一改为全屏**:重置个人账本(Android 二次确认)、添加账户、转账、周期记账、登录/注册、家庭管理等含输入框的弹窗,从底部小弹窗升级为全屏表单页——顶部「取消 / 标题 / 保存」导航栏 + ScrollView 滚动 + 键盘避让,与 iOS/Android 主流「新建页」交互一致,彻底解决键盘遮挡、输入框太矮的问题
- **重置个人账本输入框加大**:字号与纵向内边距提升,便于确认输入

## [0.7.0] - 2026-08-19

### 新增

- **数据管理**:原「数据备份」升级为「数据管理」,保留 JSON 完整备份、Excel(CSV)导出、合并/替换导入
- **重置个人账本**:数据管理页新增危险操作区,二次确认(iOS 输入「重置」/Android 底部弹窗输入「重置」)后清空所有记账记录、账户、周期规则、自定义分类;保留 app_settings(服务器地址、登录态、预算等);重置后广播 RECORDED / ACCOUNTS_CHANGED / CATEGORIES_CHANGED / SETTINGS_CHANGED 事件即时刷新各页

## [0.6.0] - 2026-08-18

### 新增与优化

- **报销不计入支出统计**:所有支出口径查询(首页今日支出、统计页总览/预算/饼图/排行/趋势、数据库汇总接口)统一排除 `reimbursable = 1`,待报销记录不再扭曲真实支出;明细页仍保留待报销记录显示与核销
- **统计页新增报销入口卡片**:「支出占比」上方新增报销摘要卡片,有待报销时显示金额与笔数,无待报销时显示提示文案;点击可进入报销管理页
- **管理页入口重构**:入口顺序调整为 周期记账 → 报销管理 → 分类管理 → 家庭同步 → 偏好设置 → 数据备份;偏好设置上移;「自定义分类」文案统一改为「分类管理」
- **分类管理增强**:支持内置分类显隐、全部分类拖拽排序、自定义分类编辑与删除;显隐与排序状态持久化到 settings 表,首页分类选择器自动按配置过滤与排序
- **添加分类改为全屏页**:新增/编辑分类使用全屏页面,顶部取消/保存导航栏 + ScrollView + 键盘避让,解决底部弹窗输入框遮挡问题
- **输入框键盘优化**:预算、账户、转账、周期规则等页面输入框统一优化键盘类型与 returnKeyType,按行业标准启用数字键盘/下一项/完成等适配

## [0.5.9] - 2026-08-18

### 优化

- **周期记账支持编辑**:点击规则行即可打开编辑弹窗,全部字段(名称/金额/类型/分类/账户/频率/备注)预填当前值,保存后原规则 id 与上次生成时间保留,不会重复生成记录;此前只能删除重建
- **管理页重构为「入口列表 + 二级页面」**(iOS 设置页标准结构):主页仅保留账户管理卡片与 6 个功能入口行(图标 + 标题 + 状态摘要 + 箭头),周期记账/报销管理/自定义分类/家庭同步/数据备份/偏好设置分别移入独立二级页,各自加载数据与管理弹窗
- 二级页顶部「‹ 返回」导航栏,Android 系统返回键同样回到管理主页;切走 Tab 再切回自动回到主页顶部并刷新摘要(规则数/待核销数/分类数/记录数/同步状态/预算)
- 偏好设置保存预算后广播 SETTINGS_CHANGED,首页预算进度条与统计页预算卡即时刷新(与 0.5.5 行为一致不回退)

## [0.5.8] - 2026-08-18

### 修复

- **底部弹窗无法升起(添加分类等)**:Modal 组件彻底重构为绝对定位底部弹窗(`position:absolute; bottom:0`),不再依赖 Android Dialog 中不可靠的 `justifyContent:'flex-end'`;使用 RNModal 原生 `slide` 底部滑入动画;移除内嵌 `KeyboardAvoidingView`,避免 Dialog 布局测量冲突
- **弹窗输入框被遮挡**:为无 ScrollView 的 `CategoryModal` 增加 `ScrollView` + `keyboardShouldPersistTaps="handled"`,键盘弹出时仍可滚动并点击保存按钮

## [0.5.7] - 2026-08-18

### 修复

- **底部弹窗卡在屏幕外(添加分类等)**:Modal 的 KeyboardAvoidingView 缺少 `flex: 1` 导致高度塌陷;`statusBarTranslucent`/`navigationBarTranslucent` 在 edge-to-edge 下导致 Dialog 窗口坐标系异常。去掉 translucent 属性、KAV 加 `flex: 1`、Android 不指定 behavior(让 Dialog 自带 adjustResize 处理),弹窗正确显示在底部
- **键盘遮挡输入框**:弹窗内 KAV 配置修正后键盘弹出时 sheet 自动上移;管理页主 ScrollView 增加 `keyboardShouldPersistTaps="handled"` 并用 KeyboardAvoidingView 包裹,预算保存按钮不再被键盘遮挡
- **预算保存不生效**:根因是键盘弹出后遮挡保存按钮导致用户无法点击;修复键盘避让后保存按钮始终可见可点

## [0.5.6] - 2026-08-17

### 修复

- **弹窗偶发卡在屏幕外(添加分类等)**:彻底移除底部弹窗的自定义开关动画(native driver 动画在 Android Dialog 未完成首次布局时会静默丢失,且关闭后偏移量残留,再次打开即卡屏);改用系统原生淡入淡出,显示不再依赖任何自定义动画;下拉关闭手势保留
- **键盘遮挡弹窗输入框**:弹窗新增键盘避让(KeyboardAvoidingView),键盘弹起时整个面板自动上移,输入内容始终可见;同时对齐 edge-to-edge 布局(statusBar/navigationBarTranslucent),修复 Android 全面屏下弹窗错位
- **预算等设置跨页不即时生效**:切换页面时自动重新加载该页数据(主流 App 标准做法),保存预算后切到首页/统计页立即看到最新值,不再依赖事件广播

## [0.5.5] - 2026-08-17

### 优化

- **预算即时生效**:管理页保存/取消月度预算后,首页预算进度条与统计页预算卡立即刷新,无需重启应用

### 修复

- **添加分类弹窗卡在屏幕外**:底部弹窗打开动画改为在 Android Dialog 就绪(onShow)后启动,避免动画丢失导致弹窗停在屏幕下方出不来;同时动画初始位置归零,极端情况下弹窗仍可直接显示

## [0.5.4] - 2026-08-16

### 优化

- **热力图配色**:记账日期色块由靛蓝色系改为暖橙色系(消费语义),与选中日期的深蓝实底明显区分,不再混淆
- **切 Tab 回顶**:四个页面改为常驻挂载后,切换 Tab 时自动滚动回顶部(此前会保留上次浏览位置)

### 修复

- **新增自定义分类不生效**:首页分类选择器常驻挂载不重渲染,添加/删除分类后新分类不可见;现监听分类变更事件自动刷新

## [0.5.3] - 2026-08-16

### 修复(明细页)

- **选中日期数字不可见**:选中格背景仍是浅热力色,但文字变白导致日期数字与金额不可见;现选中格填充深蓝实底(白字对比度约 5:1),白字清晰可见(系统日历"选中日=实底+白字"标准样式)

## [0.5.2] - 2026-08-16

### 修复(明细页)

- **月历热力图末行格子超宽**:月末最后一行不满 7 格时,剩余日期格平分整行宽度导致比正常格宽数倍(如 2026-08 末行仅 30/31 两格,各为正常格 3.5 倍宽);现末行补空白占位格,所有日期格与上行等宽

## [0.5.1] - 2026-08-16

明细页 / 统计页 UI 修复(11 项)🔧

### 修复(明细页)

- **切月后选中日不跟随**:点 ‹/› 切月后热力图显示新月份,但下方明细仍停留在旧月份选中日;现选中日自动同步到目标月同日(月末自动 clamp,与系统日历一致)
- **流水模式无法切月**:流水模式新增月份切换条,与日历模式共享月份状态,无需切回日历模式换月
- **流水模式缺失转账**:转账现与收支按时间倒序混排进流水(筛选"支出/收入"时不显示转账;搜索匹配转账备注),日历/流水两模式数据一致
- **记账人标识缺失**:多成员账本下明细页两种模式的记录行均显示记账人标签(与首页一致)
- 搜索框有内容时显示 ✕ 一键清除

### 修复(统计页)

- **预算对比数据错乱**:切到「近7天/年度」时预算卡用 7 天/全年支出与**月预算**对比,百分比与超支判断全错;现预算卡仅在「本月」视图显示
- **总览金额溢出**:总资产/支出/收入/结余大金额自适应字号,不再换行截断
- **饼图中心金额格式**:裸整数(12346)改为千分位格式,与全 App 一致
- **饼图图例增强**:每行显示金额与占比(原来只有占比);分类超过 8 项自动聚合为「其他」,卡片不再无限拉长
- **趋势图标签截断**:近30天模式 30 个标签挤在一行被截断;标签改在 SVG 内按柱心对齐绘制(抽样显示),任意数量不再截断
- **趋势图宽屏拉伸**:viewBox 固定 320 宽导致宽屏柱子变形;改为 onLayout 实测宽度自适应

## [0.5.0] - 2026-08-16

多成员账本体验 👨‍👩‍👧(记账人标识 / 成员筛选 / 成员统计 / 成员管理)

### 新增:记账人标识

- 记录行显示记账人标签(头像 emoji + 昵称,成员专属配色),一眼分清谁记的账
- 多成员账本自动显示;单成员/未登录不显示,界面零打扰
- 成员配色按 userId 全家一致(同一人在所有设备同色)

### 新增:统计页成员维度

- **成员筛选**:统计页顶部横向筛选 chips(全部 / 各成员),总览/饼图/排行/趋势全部按成员过滤
- **成员支出排行**:各成员支出金额、占比、对比条,点击任一行直接切换为该成员视图
- 数据库查询层支持 userId 过滤(区间汇总/分类汇总/按天汇总)

### 新增:成员管理

- **owner 可移除成员**(`DELETE /api/family/members/:userId`),二次确认,被移除者历史记录保留在账本
- **修改自己的昵称/头像**(`PUT /api/me`):12 个可选头像,保存后全家设备同步更新
- 家庭管理弹窗:成员行显示创建者/成员徽标,owner 行内「移除」按钮

### 修复(服务端)

- **`/api/me` 路由 404**:原 `/me` 误挂在 `/api/auth` 下(实际路径 `/api/auth/me`),客户端调 `/api/me` 返回「接口不存在」。已拆分为独立 `meRouter` 正确挂载到 `/api`
- 同步完成后主动刷新成员缓存并广播 `SYNC_DONE` 事件(原事件从未发出,管理页同步状态不刷新)

### 变更

- 登录/加入家庭/同步后自动拉取成员列表缓存(`sync.members_json`);退出登录清空
- 家庭资料修改后回读服务端,本地昵称/头像即时更新

## [0.4.0] - 2026-08-16

家庭共享账本(NAS Docker 自托管后端)🏠

### 新增:后端(server/)

- **Node.js + Express + better-sqlite3 + JWT**,零外部依赖,单文件数据库,Docker 一键部署到 NAS
- **多用户**:用户名密码注册/登录(bcrypt 哈希 + JWT 7 天),登录限流(同 IP 5 次/分钟)
- **家庭公共账本**:创建家庭生成 6 位邀请码,家人凭码加入;owner 可重置邀请码;成员列表展示
- **增量同步 API**:`/api/sync/push`(逐条 LWW upsert)+ `/api/sync/pull`(since 光标增量拉取),zod 入参校验,家庭数据强制隔离
- **Docker**:多阶段 alpine 构建 + tini 信号处理 + healthcheck + volume 持久化(`server/data/`);NAS 部署指南见 `server/README.md`
- 全链路自测脚本 `server/scripts/selftest.ps1`(注册/建家/加入/push/pull/LWW 冲突/401 全部通过)

### 新增:APP 家庭同步(本地优先)

- **本地 schema v0.3 迁移**:全部实体增加 uuid / user_id / updated_at / deleted(墓碑),旧数据自动回填 uuid
- **删除改软删除**(墓碑):删除记录/账户/转账等在全家设备上同步生效;90 天前墓碑自动清理
- **同步引擎**(`src/sync/`):push 本地水位后变更 → pull 服务端增量 → 本地 LWW upsert;uuid 对齐,账户 uuid ↔ 本地 id 自动映射
- **自动同步时机**:启动、记账/导入后 5 秒 debounce、手动「立即同步」;断网静默失败下次自动重试
- **管理页「家庭同步」区块**:服务器地址(连接探活)、登录/注册弹窗、家庭管理(创建/邀请码加入/成员列表/重置邀请码/退出)、上次同步时间
- **可选功能**:不配置服务器则完全本地使用,与旧版无差异;登录后本地历史记录自动归属当前用户
- 冲突策略:整条 last-write-wins(updatedAt 新者胜),家庭场景简单可靠

### 变更

- 备份格式升级 v3(实体含 uuid/updatedAt),**导入兼容 v2** 旧备份
- 管理页新增「家庭同步」区块(数据管理之前)

## [0.3.0] - 2026-08-16

UI 缺陷修复 + 代码质量清理 🔧

### 修复(UI)

- **全面屏适配**:底部导航适配 home indicator 安全区(动态 inset);底部弹窗改用 SafeAreaView;Toast 位置随状态栏高度自适应
- **键盘遮挡**:首页备注输入聚焦时自动滚回顶部;管理页各弹窗「保存」按钮移入滚动区,键盘弹出不再遮挡
- **热力图布局**:月历格子改按周分行 flex 平分(替代 12.2% 经验值),任意屏宽不再溢出
- **排行条误导**:统计页分类排行改相对最大值归一化(原来占比 >33% 一律顶满)
- **转账行溢出**:长账户名自动省略号截断;流水月汇总卡窄屏收缩
- **趋势图空状态**:无数据时显示提示而非空柱
- **弹窗动画魔数**:起始偏移改用屏幕高度计算,小屏不露馅

### 优化(可访问性)

- 底部导航 / 收支切换 / 数字键盘 / 分类与账户选择 / 删除按钮 / 日历格子等补齐 `accessibilityRole/Label/State`
- 次级说明文字对比度提升至 WCAG AA(#9E9E9E → #857F78)

### 优化(性能)

- **流水明细虚拟化**:流水模式改 FlatList(日期头 + 记录行拍平),整月大数据不再卡顿
- 核心组件 React.memo(数字键盘 / 分类选择 / 账户选择 / 记录行)
- **修复双重刷新**:首页记账后统一走事件通道刷新,删除手动重复查询
- 今日明细合并为单个列表组件(原来每条记录单独包一层)

### 优化(代码质量)

- 设计令牌补齐:语义色(热力 4 档 / 待报销 / 收入徽标 / 遮罩)+ 字号档位,全量替换硬编码颜色与字号
- 版本号单一来源:关于页改读 `expo-constants`(不再硬编码字符串)
- 新增公共 hook:`useToast`(三处重复 Toast 状态收编,ref 模式防定时器泄漏)、`useDeleteRecord`(删除确认收编)
- 导入失败可见:附属数据(账户/转账/周期/分类)写入失败计数,导入完成提示汇总
- 数据加载失败提示:统计页 / 管理页 / 明细页查询失败不再静默空白
- 清理死代码:删除未使用的 `useLedger.ts`、`void xxx;` 占位、`showReimburse` 无效 prop
- 新增依赖:expo-constants(读版本号)

## [0.2.3] - 2026-08-15

交互手感打磨 ✨

### 优化

- 底部弹窗支持**下滑关闭** + 淡入淡出动画(背景渐隐 + 弹窗滑出)
- 弹窗把手扩大触摸热区,下滑更易触发,且不与内部表单滚动冲突
- 空状态美化:列表空数据时显示 📭 表情 + 引导文案
- 月历热力图「今天」增加圆点标记 + 强调边框,一眼定位今日
- 统计饼图图例改为**双列布局**,分类多时不再拉长页面

## [0.2.2] - 2026-08-15

体验与健壮性修复 🔧

### 修复

- 趋势柱状图自适应:30 天数据时柱子不再重叠(动态柱宽 + 标签自动抽样)
- 金额格式化改用纯手写千分位实现,移除 `toLocaleString` 依赖(Hermes 一致性)
- 切换底部标签**不再重挂载页面**,保留滚动位置、避免重复查库闪烁
- 周期记账/账户/转账弹窗支持内部滚动,长表单不再溢出
- 数字键盘按键对齐改为 `space-between`,间距更均匀
- 删除记录/转账增加二次确认,避免误删
- 清理 `getRangeSummary` 的脆弱写法

## [0.2.1] - 2026-08-15

首页交互优化 🎨

### 优化

- 分类宫格改为**横向滑动一行**(圆形色块 + 表情 + 文字,选中填充分类色)
- 记账卡片**置顶**,进入即记,无需滚动
- 数字键盘与「记一笔」按钮收进同一组合,按钮紧贴键盘(间距 8px)
- 金额字号自适应(`adjustsFontSizeToFit`),长金额不溢出
- 今日总览精简为一行 4 项紧凑卡片(今日支出/收入/本月支出/总资产)

## [0.2.0] - 2026-08-15

功能大升级(对齐随手记核心能力)🚀

### 新增

- **账户管理**:现金/银行卡/信用卡/支付宝/微信等账户,每笔记录关联账户,总资产与账户余额自动计算,支持初始余额
- **账户转账**:账户间转账不计收支,明细页可查看/删除转账记录
- **周期记账**:每天/每周/每月/每年自动生成记录(工资、房租、订阅等),可启停、删除
- **报销管理**:记账时标记"待报销",管理页汇总待报销金额,支持一键全部核销
- **流水明细页**:月历热力图 + 流水列表双模式,按收支类型筛选、备注/分类搜索,按天分组展示,显示账户与报销状态
- **统计增强**:总资产/结余总览、预算 vs 实际对比、支出分类排行 Top5、近 7 天/30 天/12 个月趋势
- **自定义分类**:可新增/删除自定义分类(表情 + 颜色 + 归属支出/收入)
- **Excel 导出**:CSV 格式(Excel 可直接打开,带 BOM),与 JSON 完整备份并存
- **数据迁移**:v0.1 旧数据自动迁移(记录归入默认"现金"账户)

### 变更

- 底部导航重构为 4 个标签:记账 / 明细 / 统计 / 管理
- 预算进度条增强:显示已用/剩余金额与百分比,超支标红
- 内置支出分类扩展至 9 个(新增居住/医疗/教育)

### 技术

- 数据库 schema 升级:accounts / transfers / recurring_rules / custom_categories 四张新表,支持自动迁移
- 备份格式升级至 v2(含账户/转账/周期规则/自定义分类)