Commit Graph
70 Commits
Author SHA1 Message Date
laoyang 79bc593d43 鸿蒙端:分类、循环改成可编辑 (#83)
用户 2026-11-05:「分类和重复,也就是循环,都需要加上,鸿蒙端也得加上。」

原来这两个只在**有值**时才画一行只读文字
(`if (this.categoryOf() !== )` / `if (this.repeatOf() !== )`)——
用户不仅改不了,连"这里本来有这一项"都看不出来。
(这与 #76 里网页端"负责人为空就整行消失"是同一个毛病。)

改法:与截止/负责人同形态(标签一行 + 控件一行),**始终显示**。

① FbTaskPanel 新增 `@Param cats: FbCatOpt[]`
   ⚠️ 与 boards 同一理由:**不能从任务里反推** ——
      反推只能看见"已经被用过的"分类,新建的分类在端上永远选不到。
② 下拉用 ArkUI 原生 `Select`,第 0 项固定「未分类」/「不循环」
   - 分类:index 0 → category_id = 0;index N → cats[N-1].id
   - 循环:index 0 → (不循环);index N → REPEAT_KEYS[N-1]
   ⚠️ REPEAT_KEYS 与 REPEAT_WORDS **必须严格对齐**,
      错一位就是"选每天存成每周"这种静默错数据。
③ `inlineBusy` 时退回只读文字:请求在飞的时候不给选,
   否则用户连点两下会发两次相反的值。
④ 首页 / 任务页两处宿主传 `cats`(tskCats / metaCats)。
⑤ 服务端 API 早就支持这两个字段(`Api::str(repeat_rule, ...)`),
   本轮只补了 `Task::setAttrs` 的 repeat_rule 分支(见服务端那次提交)。

编译通过,已装真机。
2026-11-05 20:15:25 +08:00
laoyang 8fe1131334 去掉截止、负责人右侧的小铅笔图标 (#77)
用户 2026-11-05:「截止日期和负责人右侧都有一个编辑用的、像小铅笔似的图标。
我感觉这个东西不需要了。如果要加的话,全部都得加上,台式机采购计划、测试
之类的都要加上。但现在我觉得不加也可以,就把所有小图标全部去掉,
也就是右侧那个小铅笔图标全去掉。」

删掉两处 `✎`:
  · 截止那行(rows: 到期时间 + 已逾期徽标 + ✎)
  · 负责人那行(头像 + 名字 + ✎)

上一版加它是想提示"这里能点"(本项目踩过"纯文字看着不可点"的坑),
但代价是:
  ① **只有这两行有、别的行没有** ⇒ 视觉上不齐,看着像漏做;
  ② 输入框本身(浅底圆角 40 高)已经足够表达"这是个可操作的控件",
     铅笔是重复提示。

⚠️ 全项目扫过:`✎` 只在 FbTaskPanel 这两处,已全部清零。
⚠️ 注释里记下取舍:以后若要恢复"可点"提示,必须**所有行一起加**
   (任务标题/截止/负责人/分类/循环/备注),不能只给其中两行 ——
   那才是用户不接受的地方,不是"有提示"本身。

编译通过,已装真机。
2026-11-05 20:04:59 +08:00
laoyang 619f800ae7 删除按钮从底栏移到内容最底部(危险操作区)(#77)
用户 2026-11-05:「删除按钮不能出现在那里,因为这样太容易误触了。
删除按钮应该放在哪里?你考虑考虑。」

放在**滚动内容的最末尾**。

为什么不在底栏(原来和「保存」并排):
  ① **误触**:底栏是"保存"这种高频操作的位置,手一滑就点到删除,
     而删除**不可恢复**,一次误触 = 数据没了。
  ② **语义错位**:底栏是"我改完了、要留下这个结果"的确认区,
     不该混进"我要毁掉它"。

为什么不放顶栏:顶栏是"知道我在哪 / 怎么退出"的位置,
  在那里放删除,误触代价同样不可恢复(还紧挨着关闭按钮)。

放最底部的好处:
  · 用户得主动滚到最底才看得见 —— 低频操作不必首屏可见;
  · 纯文字(fontTiny)、无填充底色,**不像按钮**,不招手指;
  · 与正文隔一条分割线,视觉上属于"其它",不属于"操作";
  · 这是通行做法(Notion / 备忘录 / 各类设置页的删除都在最底部)。

二次确认也重做了:
  第一下「删除这个任务」→
  变成「删了就找不回来了,确定吗?」+ [确定删掉] [取消]
  原来只有"再点一次"、没有取消出口,误点后只能点别处退出
  (不明显)。现在有明确的「取消」可退。

底栏现在只剩**全宽的「保存」**,视觉上更明确:这一条就是"确认改动"。

编译通过,已装真机。
2026-11-05 20:03:05 +08:00
laoyang ad63b4c89d 修:面板底部「保存/删除」按钮被裁掉看不见 (#77)
用户 2026-11-05:「你改的是什么?是鸿蒙版吗?怎么没有看到任何按钮啊?」

代码确实在(我解包 sy-77.hap 在 modules.abc 里搜到了"保存中"字符串),
是**布局把它裁掉了**。

真根因:中间那个 Scroll 用的是 `flexShrink(1)` ——
`flexShrink` 只表示"空间不够时允许收缩",但 Scroll 的**内容高度**
仍会把自身撑开(ArkUI 竖向 Scroll 默认按内容高参与布局)。于是:
  头部 + Scroll(按内容撑满) 吃光 `maxHeight: 86%`
  → 下面的 footBar 被推到 86% 之外
  → `FbSheetRing.clip(true)` 把它整条裁掉
  ⇒ 一颗按钮都看不见(而且连痕迹都没有,看起来就像"没做")。

改法:
① Scroll → `layoutWeight(1)`
   "占据剩余空间"而不是"按内容撑开"。高度被限定成
   "86% − 头部 − 分割线 − footBar",footBar 因此必然留在可视区内,
   内容再长也只在自己内部滚动。
② footBar 补 `flexShrink(0)`
   显式禁止压缩,防止某些机型把 42 高的按钮压扁。

⚠️ 旧注释(v60b)说 `layoutWeight(1)` 会让"短内容也把卡片顶满、底下留一片空白"。
   那个副作用是真的,但属于观感问题;按钮看不见是功能问题。
   两害相权先保证按钮可用。要消空白的话正确做法是外层配 minHeight:0,
   而不是回到 flexShrink(那条路会让底栏被挤出去)。

编译通过,已装真机。
2026-11-05 19:58:53 +08:00
laoyang 980630644b 底部「编辑」换成「保存」(#77)
用户 2026-11-05:「和网页版一样,没有保存按钮啊,这应该怎么保存呢?
在哪个地方点保存呢?」

之前是"失焦自动保存"——逻辑上能存,但**用户看不见这件事**,
没有按钮就没底,不知道到底存没存。补一颗显式按钮。

① footBar 的「编辑」→「保存」
   原来那颗「编辑」本身就是矛盾的:面板已经能改所有字段
   (标题/备注/截止/负责人/分类/协作人/子任务/附件/留言),
   再给一颗"编辑"反而跳到另一张表单。
   ⇒ 换成「保存」,保存后关窗(与网页端保存后关浮层一致)。

② saveAll():串行存标题 + 备注,然后 onChanged() + onClose()
   ⚠️ 必须**串行**不能并发:`saveField()` 内部会 `reload()`,
      两次并发会让后一次的 reload 覆盖前一次的结果,
      表现为"改完标题,备注却回到旧值"。
   ⚠️ 有 `saving` 忙碌态:避免连点两次发四组请求。

③ 其余字段保持"点了就生效",不按保存
   状态/看板/截止/负责人/分类/协作人/子任务/附件/留言 都是**动作**
   (选中即生效),多一步确认反而烦;只有"打字"类(标题、备注)
   才需要一次确认 —— 这也是网页端的口径。

编译通过,已装真机。
2026-11-05 19:54:30 +08:00
laoyang cbeeb55a82 任务标题改用单行输入框 (#77)
用户 2026-11-05:「现在这个任务标题是好几行的那种输入框,不要用这种输入框,
用一行的输入框就行了。标题没有那么长,现在太高了。」

TextArea(多行,height 84)→ TextInput(单行,height 40)。

① 高度 84 → 40,与「截止」「负责人」那些控件行同高
   标题是一句话,本来不需要多行;84 的高度等于多占两行位置,
   把下面所有内容(状态/看板/截止…)整体往下推。

② 顺带补 onSubmit:单行框里回车是"提交"而不是换行,
   用户打完直接按回车就保存,不用先点别处。
   多行框里回车是换行,所以以前不能加这一条。

⚠️ TextInput 只有一行,高度超了也只是上下留白 ⇒ 直接给 40,
   不是像 TextArea 那样"给多少显示多少"。
⚠️ 单行框超长时不换行、横向滚动 —— 标题本来就没那么长,可接受。

编译通过,已装真机。
2026-11-05 19:52:23 +08:00
laoyang 10cd10e9ff 顶栏去掉重复状态徽标 + 标题输入框与其它统一 (#77)
用户 2026-11-05 两点:
  1.「编辑任务那一行,右上角还有个"进行中",那个也去掉吧,
     因为在状态那里已经看到"进行中"了。」
  2.「台式机采购计划这个任务标题的输入框和其他的都不一样。我感觉没必要,
     字体什么的大小,和下面的备注还有什么的保持一致就行了,没必要特别特殊。」

① 删掉顶栏的状态徽标
   下面「状态」区三个 chip 已经把当前态高亮出来了,顶栏再挂一个只读徽标
   是同一信息画两遍。顶栏现在只剩「编辑任务/属于父任务 + 关闭」。

   ⚠️ `statusWord() / statusTint() / statusWash()` 三个方法**保留** ——
      状态区的 statusChip 还在用。
   ⚠️ 上一版注释写"只留状态徽标 + 关闭",与现实不符了,已同步改掉。

② 标题输入框与备注完全统一
   改前:fontSize(fontTitle=17) + Bold + height(64)
   改后:fontSize(fontSmall=13) + 不加粗 + height(84)
   三项参数与备注(noteDraft 那个 TextArea)**逐项一致**。
   标题的"重要性"由它排在最前 + 标签主题色表达,输入区不必再靠字号放大。

编译通过,已装真机。
2026-11-05 19:50:45 +08:00
laoyang d4b9d08b7e 标题/备注改成常驻输入框 + 内容用墨黑 (#77)
用户 2026-11-05 两点:
  1.「任务标题里面,"台式机采购计划"这样的字样还是改成黑色吧。
     任务标题这四个字用主题色,但是输入框里面的文字用黑色。」
  2.「这个输入框本来就应该是输入框,怎么现在点了一下才变成输入框呢?
     它应该直接就是输入框,只是把文字放到输入框里面就行了。
     这样的话,点一下键盘就弹出来了,包括下面的备注也是一样。」

① 颜色:标签主题色、内容墨黑
   上一版我把 TextArea 的 fontColor 也设成 accent,整段标题变成红的,
   像报错。现在:
     · 「任务标题」四个字 → fieldLb(主题色 + Bold)
     · 框里的文字 → FbColors.ink
   备注同理。

② 常驻输入框(这是关键的结构修正)
   上一版是"只读文字 + 点一下切成 TextArea"的双态(titleEdit / noteEdit),
   所以用户看到的是"点一下才变成输入框"。
   现在**直接就是 TextArea**,值从 titleDraft / noteDraft 来。
   点输入框由系统自己聚焦 + 弹键盘,不需要 requestFocus。

   ⚠️ 必须是**受控**的:`TextArea({ text: this.titleOf() })` 是非受控,
      每次 reload() 重建组件会丢掉用户正在打的字。
   ⚠️ 删掉 titleEdit / noteEdit 两个状态(双态不存在了),
      保存判据改成"值变了没有"。
   ⚠️ 标题**空值回填、不提交**:服务端会把空标题清洗成空串 = 把标题删了。
      用户只是清空重打,不该丢标题。
   ⚠️ 备注**允许清空**:删掉备注是正常操作,所以 note 不像 title 那样回填。

③ 焦点保护(新增 titleFocus / noteFocus)
   `@Monitor(refreshTick)` 会在外部刷新时调 reload(),而 reload 会用
   服务端的值覆盖 titleDraft —— 用户正打字时被覆盖就是"打着打着字没了"。
   ⇒ 有焦点时不覆盖,失焦后再同步(onBlur → save → reload)。

编译通过,已装真机。
2026-11-05 19:48:49 +08:00
laoyang 4cca4c51be 任务面板:左上「编辑任务」+ 点一下即编辑 + 标题最前 + 字号统一 (#77)
用户 2026-11-05 五点:
  「这个框的标题在左上角,是"编辑任务"几个字,颜色用主题颜色。
    你参考一下"编辑行程"的做法。」
  「任务和备注是点进去之后,只要点一下,马上就要变成编辑状态,
    然后弹出输入法,开始输入。不需要点一下之后先变成一个输入框,
    再点一下弹出输入法。直接点进去之后就是输入状态。」
  「任务标题应该放在最前面,放在所有内容之前,也就是状态之前。」
  「状态、分组、看板、任务标题、截止、负责人、备注、协作人、
    子任务、附件、留言这些字的大小要保持统一。」

① 左上角「编辑任务」(主题色)
   与 FbTripNodeSheet 的「编辑行程」完全同一套:
   fontSize(fontBody=15) + Bold + accent。
   只写固定的「编辑任务」,不写任务标题 —— 标题是内容(可改),
   已经下移到 titleRow()。
   「属于「父任务」」跟着放到标题下面第二行(原来是同一行的大字位置)。

② 点一下直接进编辑态并拉起输入法
   原来点一下只是 `xxxEdit = true`,把 TextArea 画出来 ——
   但**画出输入框不会自动获得焦点**,用户还得再点一下才弹输入法,
   正是他说的"变成输入框,再点一下弹出输入法"。
   ⇒ 点击时:
        this.titleEdit = true;
        focusControl.requestFocus(tkpTitleInput);
     并给控件加 `.id(...)` + `.defaultFocus(true)`。
   ⚠️ `focusControl` 是**全局声明的命名空间**(ets/component/common.d.ts),
      **不能 import** —— 写成 `import { focusControl } from @kit.ArkUI`
      会报 "has no exported member"。这个坑踩过一次。
   ⚠️ `defaultFocus(true)` 是兜底:个别机型上 requestFocus 会因
      "节点刚创建"而落空,defaultFocus 能让它自己拿到。
   备注同改(id = tkpNoteInput)。

③ 任务标题提到最前
   build() 的 Scroll 里顺序改为:
      titleRow() → statusSeg() → boardChips() → infoRows() → …
   原来它在 infoRows 内部第一个(等于排在状态/看板之后)。

④ 字号统一
   `fieldLb` 从 fontTiny(11) 改成 **fontSmall(13)**,
   与 `FbSectionTitle` 内部字号完全一致(那个组件是 fontSmall)。
   上一版标签比区块标题小一号,看着不成套。
   现在统计:状态/分组(看板)/协作人/子任务/附件/留言 六个 FbSectionTitle
   全部 fontSmall,任务标题/截止/负责人/备注/分类/循环 六个 fieldLb
   也全部 fontSmall ⇒ 11 处标题字号统一。

编译通过,已装真机。
2026-11-05 19:45:17 +08:00
laoyang 28c6e674ba 任务面板:去掉左上圆圈、标题下移成可编辑的「任务标题」(#77)
用户 2026-11-05:「还是不对,这个弹出框的标题是编辑任务,台式机采购这样的
东西,它是一个标题,也应该可以被修改才对。所以它应该往下放,就是任务标题;
然后台式机采购计划也往下放,也是可以修改的。左上角那个标志,好像一个圆圈
什么的,那个也不要了,去掉。」

① 删掉左上角的 FbCheckCircle("圆圈")
   它是"点一下勾完成"的快捷入口,但下面「状态」区已经有三个明确的 chip,
   入口重复。而且它紧贴标题左侧,视觉上像"标题前面的项目符号",
   反而让人以为标题前面该有个图标。

② 大标题从顶栏下移到「更多信息」区,成为第一项「任务标题」
   原来标题画在 headBar 里,所以它是**只读**的 —— 而标题本来就该能改。
   现在与截止/负责人/备注同形态:标题行 + 控件行,
   点一下变成 TextArea(height 64、字号 fontTitle 17、主题色),
   失焦自动保存(与备注同一套交互,不设保存按钮)。
   ⚠️ 空标题不发请求:服务端会清洗成空串,等于把标题删了。
   ⚠️ `saveField(title, ...)` 走的是既有 PATCH —— 服务端 ApiHandler
      对未提交字段回退原值,所以只传 title 不会把 note/due_at 抹掉。

③ headBar 只剩「状态徽标 + 关闭」,左端放「属于「父任务」」
   父任务那行保留在顶栏不跟着下移:它是上下文不是内容,
   跟标题走反而离"父任务"这个概念更远。
   没有父任务时用 Blank() 占位,保证徽标和关闭仍在右边。

④ 完成态标题仍是 faint + 删除线(不上主题色)
   删除线是"这件事已经过去了"的信号,再上主题色会和待办标题一样抢眼。

编译通过,已装真机。
2026-11-05 19:41:14 +08:00
laoyang 2fefacccd3 任务面板字段改上下两行 + 标题主题色 (#77)
用户 2026-11-05:「还是不对,你还是没有看清楚那个样式的设置:上面的标题和
下面的输入框,其实要分成两行。比如说,"截止"不是直接后面排一个日期,
而是把日期输入框放在"截止"下面;"负责人"也是,"备注"也是,全部都是这样的。
上面"台式机采购计划"这样的标题,应该是主题的颜色。」

前两轮(#77i / #77j)我都在改**颜色和字重**,方向偏了 ——
用户要的是**布局**:标签一行、控件另起一行。

① fieldLb 去掉 width(48)
   上一版把它定成 48vp 固定宽的行内标签,形态是「[截止] 2026-11-07 18:30」,
   与要求的上下两行正好相反。
   现在 width(100%) + textAlign Start,独占一行;
   字号从 fontSmall(13) 降到 fontTiny(11):上下两行之后标签再和控件同号
   会抢视觉重心。

② infoRows 重写为 Column({space:14}),每项 = Column({space:6}){
     标题行 fieldLb(...)
     控件行(height 40 / inputBg / radiusS / 左右 padding 12)
   }
   控件行统一给 height(40):不给的话「设置…」「未指定」这类短文字
   会把框压扁,几行之间的间距就参差不齐。
   负责人那行的头像从 22 调到 24(框内 40 高,22 略嫌小)。
   备注:只读态改 constraintSize({minHeight:40}) + maxLines(3),
   避免长备注把框撑成一大片;编辑态从单行 TextInput 换成
   TextArea(height 76)—— 备注本来就是多行的,单行输入框里
   看不见换行,用户会以为只能写一句。

③ kvRow 同步改成上下两行
   「分类」「循环」若还左右排,夹在截止/负责人/备注中间会显得错位。

④ 面板标题改主题色
   Text(this.titleOf()) 从 FbColors.ink + Medium 改成 this.accent + Bold。
   ⚠️ 完成态仍保留 faint + 删除线:那是"这件事已经过去了"的信号,
      再上主题色会和待办标题一样抢眼,反而分不出轻重。

编译通过,已装真机。
2026-11-05 19:37:08 +08:00
laoyang b42e657bb2 标题颜色/看板/截止/负责人/备注/协作人/留言头像 全部修正 (#77)
用户 2026-11-05:「还是不对,标题的颜色不对。分组看板那个地方没改,截止没改,
负责人没改,备注没改,协作人没改。评论那个地方的头像怎么又没了?」

逐条查下来,四个真问题:

① 上一轮只改了 3 个标题,漏了一大片
   只换了「子任务/附件/留言」,而 **看板、状态、协作人**、
   以及「更多信息」里的**字段标签**全是手写的 fontTiny + faint。
   ⇒ 看板/状态/协作人 补 FbSectionTitle(Bold + accent + 字距 0.6)。
   ⇒ 截止/负责人/备注/分类/循环 这些**行内字段标签**新增 fieldLb() Builder:
     同样取主题色 + Bold + letterSpacing,但宽度 48、无内边距
     (不能直接用 FbSectionTitle:它 width 100% + padding,会把右侧控件顶下去)。
   ⚠️ 这与「编辑表单」的口径不同:那边 `期限` 仍是浅灰小字。
      用户明确要求这个面板里字段标签也用主题色粗体 —— 按用户要求走,
      已在 fieldLb 的注释里写明这个差异,避免以后有人"统一"回去。

② 留言头像丢失的真因
   不是数据问题,是 `FbPersonChip` **不透传 imageUrl**。
   它内部的 `FbAvatar({ name, color, dia, fontSizeVp })` 漏了这个参数,
   而 `FbAvatar` 自己是支持的(注释里还写着"800 人里只有 19 人有头像,
   有图显示图、没图回落色块字")。
   ⇒ 上一轮我换成 FbPersonChip 后,原来手写 FbAvatar 时**能显示的头像反而没了**。
      给 FbPersonChip 补 `@Param imageUrl` 并传给内部 FbAvatar,
      调用点把 `fbImageSrc(c.avatar, token)` 传进去。

③ 状态区原来没有标题
   三个 chip 直接飘在那(上下每个区段都有标题)。补 FbSectionTitle(状态)。

④ 协作人标题改组件
   顺手把「+ 共享」按钮挪到标题下面单独一行(FbSectionTitle 是 100% 宽,
   放同一个 Row 里会把它压成 0 宽)。

编译通过,已装真机。
2026-11-05 19:34:12 +08:00
laoyang 09f7878994 样式改为走规范组件 FbSectionTitle / FbPersonChip (#77)
用户 2026-11-05:「样式还没变,还记得咱们之前规定的样式是怎么做的吗?」

找到了。规定就在 entry/src/main/ets/components/Widgets.ets,
而且 FbSectionTitle 的注释里**留着用户原话**:

  「"今天的日程",既然是日程,标题颜色就应该用日程的主题色。
    …下面的"近期任务"改法跟上面一样,还有"最近纪念日"也不说了,全都是一样的。」

上一轮我做错的地方:**手写 Text 改 fontColor**,没走组件。
而 FbSectionTitle 给的是三样东西:
  · fontWeight: accent 存在时 Bold(不是 Medium)
  · fontColor: accent
  · letterSpacing: 0.6
我只改了颜色 ⇒ 字重和字距还是老样子 ⇒ 用户看着"样式没变"。

改动:
① 子任务 / 附件 / 留言 三个区块标题 → 全部换成 FbSectionTitle
   传 accent: this.accent(模块主题色)+ extra(数量/进度)
② 留言人 → 换成 FbPersonChip
   网页端留言人用的就是 person_chip()(头像 + 名字的胶囊),
   鸿蒙端对应 FbPersonChip。上一轮我手写的"头像 + 纯文字名字"只是长得像,
   不是同一套。换掉后同时删掉外置的那个 FbAvatar(组件内置了,会画两遍)。

🔴 踩到一个组件约束:FbSectionTitle 是 `width(100%)` 的独立组件,
   塞进 `Row` 里会和后面的计数/按钮抢宽度,被压成 0 宽。
   ⇒ 三处都改成**独占一行**;「已完成 2/5」这类附加信息走它自带的 extra;
      按钮(+ 添加附件)另起一行右对齐。

编译通过,已装真机。
2026-11-05 19:29:39 +08:00
laoyang 32a35ca222 留言底部被裁 + 留言支持附件 (#77)
用户 2026-11-05:
  「留言输入框下面还有东西,现在被挡住了,根本看不见,也滚不动了。
   但下面应该还有字什么的,看看是什么。」
  「留言是可以需要添加附件的。」

① 被挡住的是什么
   是 #77 刚加的「回车发送」那行小字。

   根因:`commentSection()` 是 `Scroll` 里**最后一个**子元素,
   而 Scroll 内容末尾只留了 `height(6)`。
   Scroll 本身是 `flexShrink(1)`(内容不够高时按内容收缩),
   最后一行下边距一旦小于"卡片 padding + footBar 高度",
   就会被判定为"已到底" —— 滚不过去、也看不见。
   ⇒ 底部留白从 6 改成 28。

② 留言带附件
   服务端网页端早就有(tasks/comment_add 里的 attach_save_files),
   但 **API 的 comments 端点只收 content**,且空文字直接 422
   ⇒ 端上根本没有传附件的入口。三处一起改:

   · app/ApiHandler.php:comments POST 支持 multipart
       - 有 file 就走附件流程:先 addComment 拿 cid,再 Attachment::save
         (顺序不能反:save 要 owner_id,cid 是插入后才有的)
       - kind 用 task_comment,与网页端 attach_save_files 同一归属类型
         (不同的话同一条留言的附件在两端互相看不见)
       - **允许只发附件不写字**:自动补占位文案「(附件)」,与网页端同口径
   · FbTaskRepo.addTaskCommentWithFile():手拼 multipart
       - 不能复用 fbUploadOne(那个是"一个文件 + 一个字段",
         评论要同时传 content + file)
       - 文件段必须带 Content-Type:服务端按它判"是不是图片"
       - boundary 之间用 \r\n(用 \n 有些解析器会把整段当一个字段,
         表现为"文字上去了、附件不见了")
   · FbTaskPanel:
       - 「+ 添加附件」按钮(DocumentViewPicker,任意文件)
       - 选中的文件名显示成 chip,可点 ✕ 去掉
       - 附件**不立刻上传**,随留言一起发(与「任务附件」那块语义不同:
         那个传完就生效)
       - 发成功后清空待发附件,否则下一条会重复带上

   实测(模拟上传链路):
     只有文字          => cid=18 ✅
     文字 + 附件        => cid=19,附件数 1 ✅(key=files/6b4e...txt)
     只发附件(占位)   => cid=20 ✅
   测试数据已清理。
2026-11-05 19:25:32 +08:00
laoyang ad25792732 选人改「只留搜索框」+ 附件用系统打开 + 留言头像与主题色 (#77)
用户 2026-11-05 五项。

① 选人慢 → 未输入关键词时不渲染任何人
   用户:「实在不行,我们就不把人列出来了,只留搜索框…输入拼音首字母就可以找到这个人」

   慢的根因(不只是节点多):
     · 名册 **800 人**(不是 8 个);
     · 每人一个 FbAvatar,而它内部调 fbImageSrc(avatar, token) ——
       那是**同步**查本地缓存 + 拼 URL,800 次全在打开弹窗那一帧跑完;
     · 再加 800 个 Column/Text 节点参与布局。
   ⇒ 未输入时一个候选都不渲染,只显示"输入名字或拼音首字母找人"。
     只有真打了字才过滤、才渲染(结果通常 1~20 个,瞬间出来)。

   ⚠️ 没改成"只显示前 N 个":800 人里翻前 20 个没用,用户还是得搜。
   ⚠️ 暂存区(已选那几个)不受影响:数量极少且必须一直可见
      (否则用户忘记选了谁)。多选 + 多次搜索的流程因此保持完整:
      搜一个 → 点选进暂存 → 改关键词再搜 → 再点选 → 确定。

② 附件:Word 等非图片文件交给系统应用打开
   用户:「应该打开本机上默认能够打开这个文件的应用,让它把文件打开才对。」

   项目里**已经有** common/FbAttachOpen.ets(下载明文到 cacheDir +
   隐式 Want + action.viewData 交给系统「打开方式」),只是任务面板没调用。
   ⇒ 附件缩略图挂上 onClick:
       · 图片 → 就地全屏预览(新加浮层,ImageFit.Contain 看全图不裁边)
       · 其它 → fbAttachOpen(),系统自己找能打开 .docx 的应用
   ⚠️ 不能用「显式 Want + 包名」:不知道用户装的是 WPS 还是 Office。
   ⚠️ 预览用 Contain 而非 Cover —— 缩略图用 Cover 对,预览裁边就错了。

③ 留言人头像
   服务端 Task::comments() 的 SQL 里**一直有** `p.avatar`,
   端上 parseTaskComment 只取了 name/color ⇒ 头像从没显示过。
   补 RawTaskComment.avatar + FbTaskComment.avatar + 解析,
   UI 换成公共 FbAvatar(有图用图、没图退回首字色块)。

④ 留言标题/发言人用模块主题色加粗
   原来发言人是 muted 灰、常规字重 —— 漏了全站「以模块色标识」的规矩
   (观影紫 / 旅程绿 / 任务赭红)。改成 this.accent + FontWeight.Medium。
   同时把「附件」「留言」「子任务」三个小节标题也从 ink 改成主题色。

   ⚠️ 正文**没有**跟着改主题色:整段上色会掉可读性(色彩需要留白)。
      如果用户要求正文也变色,改一行即可(已在代码注释里标了位置)。

⑤ 编译通过,已装真机。
2026-11-05 19:19:57 +08:00
laoyang 4e2fbb04b7 鸿蒙任务面板:看板平铺、截止负责人备注可改、文案与图标 (#77)
用户 2026-11-05 对鸿蒙端首页任务弹窗提了 12 项,本批完成 9 项
(附件预览/下载单独做)。

① 看板平铺,不再左右滑
   参考 FbTripNodeSheet 的做法:≤6 项 Flex(wrap) 换行平铺,>6 项才横滑。
   当前家庭 4 个看板(默认 + 正在进列/等待反馈/等待结果)⇒ 走平铺。
   ⚠️ 平铺必须用 Flex(wrap),ArkUI 的 Row 不换行,塞不下会横向溢出
      父容器(v76 真机踩过:最后一个 chip 被裁掉且点不到)。
   ⚠️ Row 的 space 在 Flex 里不生效 ⇒ chip 补了 margin 做间距。

② 删掉截止下面重复的「看板」行
   上面 boardChips() 已经把当前看板高亮出来了,再列一行纯属重复,
   而且两处都要同步才不出错。

③ 截止日期可改
   点整行 → FbDateTimePicker(与编辑表单同一个组件,交互一致)。
   改完立刻保存,不设保存按钮 —— 与状态/看板/子任务同一套交互。

④ 负责人可改 + 头像
   点头像/名字 → FbPersonPickSheet 单选。
   头像来源:服务端 Task::find 的 SQL 里**一直有** a.avatar AS assignee_avatar
   (实测 #49 返回 avatars/2026xxx.jpg),只是端上 parseTaskItem 从没解析
   ⇒ 补 RawTask 字段 + FbTaskItem.assigneeAvatar/assigneeColor + 解析。
   没头像时用首字色块兜底(与留言区同一手法)。

⑤ 备注可改
   点一下变输入框,回车或失焦自动保存(等用户去找"保存"按钮
   就会以为没存上)。

⑥ 「共享给谁」→「协作人」(标题与弹层标题都改)

⑦ 「+ 加图片」→「+ 添加附件」,**行为也跟着改**:
   原来 pickAttach 用 PhotoViewPicker + IMAGE_TYPE 只能选相册图片,
   改叫"添加附件"却选不了 PDF/Word,用户会以为功能坏了
   ⇒ 换成 DocumentViewPicker(系统文件选择器),能选任意文件。

⑧ 留言区标题加 💬 图标

⑨ 「回车发送」从 placeholder 挪到下方独立小字
   (塞 placeholder 里一打字就看不见,而且 placeholder 该放"填什么"不是"怎么操作")

⑩ 发送按钮显示不全
   根因:写死 width(56) + textAlign(Center),Text 默认内边距吃掉两侧空间,
   56 减去内边距 + 两个汉字正好压线,字体缩放或不同机型就截掉半个字。
   ⇒ 改 constraintSize({minWidth:60}) + 水平 padding,按文字实际宽度自适应。

⚠️ 另一个必须同步的地方:FbTasksPage 里改状态时会**整条替换** FbTaskItem,
   新加的两个字段漏了就会被抹掉(改一次状态头像就没了)——已补。

编译通过。
2026-11-05 19:12:24 +08:00
laoyang 5fba8c4eda 修导航目的地前面多出问号 (#69)
用户 2026-11-05:「导航的,点进去导航之后,为什么目的地地址前面有个问号」

根因:我上一轮拼的是 `lat,lng?地名` —— 那个 `?` 是照抄 `geo:` URI 的写法,
但这个字符串**不是 URI**,是交给 fbOpenNavigation() 的普通文本。

FbNav.parseGeoOf() 的动作:
  ① 正则抓 `数字,数字` 当坐标
  ② clean = 把坐标从原文剪掉后剩下的文字,用作地名

传 `36.168977,120.375693?青岛北站` 剪掉坐标后剩 `?青岛北站`
⇒ 问号跟着进了高德 dname,目的地标题显示成「?青岛北站」。

改成「地名 + 空格 + lat,lng」:
  `青岛北站 36.168977,120.375693`
剪掉坐标后剩「青岛北站」,正是要显示的名字。

实测(复刻 parseGeoOf 跑):
  旧 lat,lng?地名  → clean=[?青岛北站]   ❌
  新 地名 lat,lng  → clean=[青岛北站]    ✅
  只有地名         → ok=false 走地理编码  ✅

两处都改了:
- FbEventSheet.navTargetOf()(#69 新增的)
- FbDayEventPanel 行程卡片的 onClick(#67 里同一个错误)
2026-11-05 17:22:36 +08:00
laoyang ad9b934760 日程详情卡:补齐行程的出发地/目的地 (#69)
用户 2026-11-05:「点击行程里旅行日历本里的信息时,弹出来的那个框是在首页上
弹出的,还是没有出发地和目的地。」

根因链(三段,缺一段都不显示):

1. FbCalEventFull 没有那四个字段
   #67 只给了 FbCalEvent(列表用),详情卡消费的是 FbCalEventFull。

2. shellOf() 只塞了 location: b.place
   行程节点的 place 是空串 —— 它的位置概念是"从哪到哪",没有单一地点。

3. 最关键:行程条目**永远走 shell,压根不请求详情接口**
   openEvent() 里 `if (!isEditableEvent(e)) { shellOf(e); return; }`,
   行程的 kind 是 trip 不是 custom ⇒ 直接短路。
   而且即使去请求也没意义:Calendar::event($id) 查的是 calendar_events,
   行程节点在 trip_nodes 表,那个 id 查不到东西。
   ⇒ 位置信息**只能**靠 shell 带过去。

改动:

- FbCalEventFull 加 fromPlace/toPlace/fromLng/fromLat/toLng/toLat
- FbCalRepo.toFull() 从接口读(接口给就填,普通日程取到空串)
- 首页 FbHomePage.shellOf()、日程页 FbCalendarPage.shellOf() 两处传递
  (两处是独立实现,只改一处会漏)
- FbEventSheet:
  · navRow 改 navRowRaw(label, place, navTarget) —— 标签不再硬编码「地点」
  · onNavigate 从无参改成 onNavigate(navTarget)
    原来宿主只能读 event.location 猜导航目标,无法区分出发地/目的地
  · 渲染三分支:有 fromPlace 画「出发地」、有 toPlace 画「目的地」、
    有 location 画「地点」(三者互斥:行程条目只有前两个,普通日程只有第三个)
  · 新增 navTargetOf(place, lng, lat):有坐标返回 `lat,lng?地名`
    (同名地点不会搜错城市),没有就只给地名让 FbNav 地理编码
- 首页 navigateEvent / 日程页 navigateDetail 改成接受 navTarget,
  非空直接用,为空退回原来那套推导(底部那颗「导航」按钮传空串)
2026-11-05 16:21:16 +08:00
laoyang f30ea5b1e4 日程行程条目端上同步:字段、列表卡「出发地→目的地」可导航 (#67)
同步服务端 #67 的新字段与三处改动:

数据层
- RawCalEvent / FbCalEvent 加 fromPlace/toPlace/fromLng/fromLat/toLng/toLat
- FbParse 解析赋值;新增 numStr() 把 DECIMAL 可能给的 number 转成字符串
  (`s()` 签名只收 string|null|undefined,坐标字段会报类型错)
- FbCalStore.toModels、DemoData 的 8 条演示数据同步补字段

渲染
- FbDayEventPanel 列表卡片:在时间行**上面**加一行「出发地 → 目的地」,
  只对两端都有值的条目画(普通日程 fromPlace/toPlace 都是空串,不画)。
  目的地用日历本色 + 下划线,点击 fbOpenNavigation:
  有坐标走 `lat,lng?地名`(同名地点不会搜错城市),没有就让 FbNav 地理编码。
  不塞进时间行是因为那里已有「时刻 + 重复徽标 + 地点」,窄屏会挤爆。
- FbDayTimeline 全天块:一行文字「出发地 → 目的地」,替代原来的 place。
  不给点击 —— 26vp 高的块切两个热区点不准,导航入口在详情卡。

图标与 ✓ 的改动在服务端(Trip::leadIcon / 删  ✓ ),端上只消费 title,
所以不需要额外处理。
2026-11-05 16:12:14 +08:00
laoyang c498037ec8 首页标题主题色补完 + 新建去加号 + 全天块上间距 (#60 #61)
用户二次反馈四项,全部是上一轮没做到位的:

1. 标题颜色没变
   上一轮给 FbSectionTitle 加了 @Param accent(默认空串),
   但首页 6 处调用点**一处都没传**。grep 到的 7 个 accent:
   全在 FbEventSheet/FbTaskPanel/FbDayEditSheet/FbMovieDetailSheet/
   FbPersonSheet 上 —— 那些组件本来就自带这个参数,与标题无关。
   空串 ⇒ titleColor() 退回 #8A7A6D 灰 ⇒ 组件改得再对也不变。
   补 5 处:日程/任务/纪念日/观影/联系人各自 fbModuleOf().brand。
   往年的今天不传(混合来源,没有单一模块色)。

2. 条数与"新建"贴死
   extra 后面直接接胶囊,Row 无 space、胶囊无 margin。
   加 margin({left:12})。不用 8:胶囊有实体底色,视觉比纯文字更紧。

3. 去掉「+」前缀
   5 处 addText 去加号;连带 3 处空态提示的「点右上角「+ 新建」」
   同步改掉,否则提示里带着按钮上已不存在的符号。

4. 全天日程问题在上面不在下面
   上一轮改 bottom 6->12,方向反了。
   FbDayEventPanel 给 FbDayTimeline 的 padding 没有 top
   ⇒ 第一个全天色块上边直接压在白卡片顶边上。
   改成 {top:10, bottom:12}。只加在有全天的分支里。
2026-11-05 15:52:40 +08:00
laoyang c11548cb51 首页改造:概述跳锚点、主题色、详情卡、点击响应 (#59 #60 #61 #62 #63 #64 #65)
#59 概述四格数字可点击跳锚点
- 加 Scroller + yEvents/yTasks 坐标字段
- 两个区块挂 onAreaChange 记录 y 偏移
- FbStatCell 加 onTap(整格热区,不是只有数字两个小字)
- 今天日程 -> 今天日程区块;在做/任务/逾期 -> 近期任务区块
  (三者都是任务状态计数,点哪个都是想去看任务,让"逾期"单独跳
    一个不存在的"逾期列表"是骗人的)
- 概述数字本来就来自 d.stat.*(接口真实数据),无需改

#60 各区块标题用模块主题色 + 新建改胶囊按钮
- FbSectionTitle 加 accent 参数(默认空 = 保持原灰,老调用点不动)
- 标题有主题色时加粗一档(光换色辨识度不够)
- 新建从文字链接改成胶囊按钮:底色主题色、白字、全圆角
- 6 处调用点分别传 calAccent/taskAccent/daysAccent/moviesAccent/peopleAccent

#61 全天日程与下方白块无间距
- FbDayTimeline 全天块 bottom padding 6 -> 12
- 6vp 在全天色块实心背景下几乎看不出缝;12 才有明显呼吸
- 与 Column(space:4) 的行间距形成「行内紧、块间松」层次

#62/#63/#64/#65 四个区块加"先看后改"详情卡
根因:5 个 @Builder 全都没有 onClick(memoryRow/movieRow/recentDayRow/
personTile 各自 0 个),所以用户"点了没反应"。

- 新增状态:mvDetail/mvDetailLoading/mvDetailError/mvSeed/pfDetail/
  dayDetail/memDetail,全部计入 sheetCount(否则按返回键直接退出页面)
- openMovieDetail 照 FbMoviesPage.openDetail 的成熟写法用
  FbMovieRepo.inst().detail(),不自己拼接口(parseMovieDetail 不导出)
- 复用三张现成卡:FbMovieDetailSheet / FbPersonSheet / FbDayEditSheet
- 往年今天按 kind 分发:movie 走电影卡;trip_node 拿不到 tripId
  ⇒ 退回通用摘要卡(弹错类型的卡比不弹更糟)
- memorySheet 用 FbSheetRing 就地画,内容可滚动(用户"滑下去看信息")

联系人头像本来就有(personTile 里 if (p.avatar !== "") 分支),无需改。
2026-11-05 13:43:03 +08:00
laoyang aafd83b130 悬浮栏选中底圆角:999 -> 12,不再那么圆鼓 (#58)
用户 2026-11-05:「颜色我感觉透明度什么的还是可以的,但是这个圆有点太圆了,
应该弄成更椭圆一点,效果可能是不是更好啊?」

borderRadius(999) 会把 40 高的块钳成两端完整半圆,看着很圆鼓。
改成 12:圆角半径只占高度的 30%,是"柔和的圆角矩形",
视觉更平更舒展,也与 dock 外框的大圆角(28)形成内外层次。

ArkUI 的 borderRadius 是等比圆角(四角半径相同、正圆圆弧),
没有"椭圆角"(x/y 半径不同)的写法 —— 所以"更椭圆"只能靠减小半径近似,
半径越小圆弧越平。

实机偏圆就降到 10,偏方就加到 14,改动只有这一个数字。
2026-11-05 13:25:06 +08:00
laoyang 5d9902bb82 底部悬浮栏:选中项加灰色椭圆底 (#58)
用户 2026-11-05:「当选中其中一个按钮之后,它除了图标和文字颜色发生变化,
还给它加了一个那种椭圆的那种框那种背景,灰色的。这样更能清晰表示
现在正在被选中是哪一个功能。」

原来选中态只有变色 + 加粗,没有背景,观感偏弱。

结构上关键:不能直接给 Column 加 backgroundColor + borderRadius ——
那个 Column 是 layoutWeight(1),椭圆会撑满整格,看起来像"选中了一整块"。
套两层:外层等分格(透明,负责布局与热区)+ 内层椭圆(自适应内容宽高)。

底色用半透明黑 #14000000 而不是实色灰:
dock 底色是 EBFFFFFF(92% 白叠模糊层),实色灰在深色系统主题下会突兀;
半透明黑与背景混合,浅色下浅灰、深色下深灰,两种主题都协调。

borderRadius 用 999 而非固定值:固定值在标签变长时圆角比例会失调。
2026-11-05 13:21:34 +08:00
laoyang 0c2cf9b5e7 表单规范全量铺开 + 编辑时视频附件不显示 (#56)
① 编辑行程的视频附件不显示

根因:照片墙对所有附件都用 Image 渲染,视频是 .mp4,
Image 加载不出来 ⇒ 空白块 ⇒ 用户以为"视频没带进来"。
按 mime 分流:video/ → 深色底 + ▶(不取首帧,端上解视频首帧很卡)。

② 新增共享组件 FbFormField.ets(GLOBAL-FORM-SPEC)

5 个表单原来各写一份 field()/labelRow(),改一处要改五处,
而且外层结构不一致(有的 Row 有的 Column),批量脚本改坏过两次。
收敛到一个组件,表单只调用不自己实现。

③ 全量铺开

- FbPersonEditSheet  姓名标必填(红色无星号)
- FbEventEditSheet   日程 field 两排
- FbMovieNewSheet    片名/影院/同看的人/海报/观影照片/简介/备注 全部两排
- FbDateTimeField    FbDateTimeRow 改两排(电影+日程同时生效)

规范:标签主题色 + Bold + fontSmall,必填红 #C0533F 无星号,
标签独占第一排、控件第二排整宽。
2026-11-05 12:52:51 +08:00
laoyang 86de59616c 端上:#47~#55 打包 —— 行程双轨、动态类型、表单全局规范 (#53)
#48 计划/实际双轨:nodeAction(start/done/cancel),完成即转实际行程,
    实际行程不再显示状态;新增 actualStartedAt/actualEndedAt
#49 类型/出行方式/车辆 chip 改内部读状态(@Builder 值传参不刷新)
#50 类型点击立即变色、出行方式平铺、参与人显示已选头像
#51 节点类型动态加载(原硬编码5种)、地址 onPick 拿详细地址与坐标
#52 标签改主题色 Bold、必填红无星号、编辑行程标题同步
#53 表单两排版式(标签独占一行解决左右对不齐);电影弹层 layoutWeight 修滚动
#54 地址联想框加时间窗抑制(600ms 内不搜)、候选限高 180

含附件回填诊断日志(DIAG-ATTACH/FILL),待真机确认后移除
2026-11-05 12:41:04 +08:00
laoyang 825008657b 品牌改名:家书 → 岁月家 (laoyang/FamilyBook#38)
## 起因

用户 2026-11-01:「家书已经有人做了,八月份刚上线的」。

核实:「家书 HomeBook」(广州家书科技有限责任公司,粤ICP备2026096420号,
2026-06 成立、08 上线)。功能**部分重合**(家庭记录 + 相册 + 家谱)。
华为审核指南 1.3「应用名称不得和其他应用名称相同,避免混淆」,
虽然个人与企业主体查重不互通、技术上能过,但被人工驳回概率高。

## 定名过程(用户逐个否掉)

| 候选 | 否决原因 |
|---|---|
| 寸草 | — |
| 家卷 | ❌ 与「家眷」同音,口头传播会打错字 |
| 家时光 | ❌ App Store 已有「家·时光」定位几乎一致;且「家+时光」是常见组合,辨识度低 |
| 家纪 | ❌ 已有重名 |
| **岁月家** | ✅ 搜不到同名应用;是口号的直接展开(岁月 + 家) |

## 最终定稿(用户明确指定)

```
应用名      岁月家
主标语      一家人用岁月写成的一本书     ← 不变
一句话简介  一家人用岁月写成的一本书     ← 改为与标语相同
官网标题    岁月家 · 一家人用岁月写成的一本书
英文名      FamilyBook                ← 不变
包名        familybook.yangyq.net     ← 不变
```

## 改动范围(17 + 18 处,12 个文件)

### 应用名(17 处)
`AppScope/resources/base/element/string.json` 的 app_name,
`entry/.../string.json` 的 16 条(应用名、卡片名、无障碍描述)。

### 用户可见文案(18 处)
`FbBackgroundSync` / `EntryAbility`「正在同步家书数据」、
`FbAttachOpen`「家书附件」相册名、`FbHttp` 地址校验失败提示、
`FbCardData` / `FbCardMediaData` 卡片空态「先在「家书」里填写服务器地址」、
`TopBar` / `Drawer` / `FbLoginPage` 的应用名文本、`FbSettingsPage` 的说明。

## 刻意不改

- **包名 / bundleName**(用户明确要求不变,且改了等于换一个应用)
- **英文名 FamilyBook**、目录名、仓库名(改了会让 git 历史、部署脚本、证书路径全跟改)
- **代码注释**里 200+ 处「家书 FamilyBook · 某模块」—— 不影响运行时,
  改了只会制造噪音 diff,让将来 blame 变难
- **主标语**里一个字都没动

## 验证

### 编译
`BUILD SUCCESSFUL`,33 tasks。

### 产物实测
解包 `.hap` 的 `resources.index`:
- 「岁月家」出现 **17** 次
- 「家书」出现 **0** 次

### 全局一致性校验(31 项全过)
`_tooling/check_rename.js`:
- 客户端 app_name / EntryAbility_label / 产物内容
- .ets 用户可见文案无残留(排除注释)
- 服务端非注释代码无残留
- `Auth::homeTitle()` 用「某某某的家庭岁月」
- `DEFAULT_SLOGAN` 未被改动
- 官网线上 5 个页面无「家书」、标题含岁月家、渲染后 h1 是品牌名
- 材料 app_name / 一句话简介 / 包名 / versionCode / 域名

⚠️ 校验过程中我自己的判据出了 2 次假红:
① 注释识别不全(漏了 `/**` 单行块注释和 `<!--` HTML 注释)→ 补齐 isCommentLine
② 「为什么叫岁月家」整节重写后,旧断言还在查「书信」「家与书」
   (那是**旧名字**的逻辑)→ 改成查「岁月」+「家」的拆解

## 一处语义决策(问过用户)

网页端 `Auth::homeTitle()` 生成「某某某的家书」—— 这是**产品核心语义**
(每个家有自己的那一本),直接换成品牌名会读成「某某某的岁月家」,不通。

用户定:改成 **「某某某的家庭岁月」**。
兜底(无姓名时)仍用品牌名「岁月家」。

## 连带影响(不阻塞)

- 上架材料 7 个文件已同步(含软著全称改为「岁月家家庭生活档案管理软件」)
- 官网已部署,线上标题已是新名字
- 软著申请尚未提交 —— 正好一次写对新名字,不需要变更
2026-11-01 19:55:36 +08:00
laoyang 5661312679 日程编辑:确认「开始」后结束时间自动 = 开始 + 1 小时 (laoyang/FamilyBook#34)
用户 2026-11-01:
  「当我选择了开始时间并确定之后,结束时间要跟着开始时间变成开始时间加1个小时。
    也就是说,所有时间我们都默认它是一个小时。如果不是一个小时,再让他自己去改。」

## 改前

`applyPicker()` 只在**会变成负时长**时才动结束时间:

    if (this.fEndDate < iso || (this.fEndDate === iso && this.fEndTime < hm)) { ... }

⇒ 用户改了开始、结束还挂在老位置,实际时长变成随机的。

## 改后

点「开始」确定 ⇒ 结束**无条件**重置为「开始 + 1 小时」。
改「结束」那条路一个字不动(改结束不影响开始)—— 这是「默认一小时、
不像一小时再自己改」的另一半。

全天日程跳过这段:时刻部分本来就不生效,改了不会落到服务端,
但会让"切回非全天"时看到一个莫名的时间。

## 新增 fbPlusHours()

`FbDateTimeField.ets` 里加 `fbPlusHours(date, time, hours)`,返回 `{date, time}`。
**不复用现成的 `fbAddHour()`**,因为那个有两个问题:

    return fbPad2(Math.min(hh + 1, 23)) + ':' + mm;

① `Math.min(hh+1, 23)` **封顶 23 点** ⇒ 23:30 加一小时还是 23:30;
② 只返回时间、不带日期 ⇒ 表达不了「次日 00:30」。

跨天/跨月/跨年全部交给 `Date` 运算,不自己算天数
(自己 `if (hh>=24) dd+=1` 在"11 月 30 日 + 1 天"上就会错成 11 月 31 日)。

## 验证

**纯函数真值表 13 项**(从真实 `.ets` 抽函数、只擦类型标注后 node 直接跑,
不手抄替身;擦除后断言行数不变、无类型残留、没引入新字符):

普通 / 跨天整点 / 跨天半点 / 跨月 / 跨年 / 非闰年 2 月末 / **闰年 2 月末 ⇒ 2/29** / 午夜 / 带分钟
—— 全过。其中 23:30+1h ⇒ **次日 00:30**、12/31 ⇒ 次年 01/01、2028-02-28 ⇒ 2028-02-29。

**反向验证 3 项**(证明判据不是恒真):
坏A(封顶 23 点 / 不带日期)被抓到、坏B(丢分钟)被抓到、坏C(固定 28 天)被抓到。

**接线断言 12 项 + 反向验证 5 项**(静态,因为"函数对 ≠ 接进去了",
上一轮 #34 主体 bug 就是参数名对不上、编译运行都不报错):
- 开始分支调用 `fbPlusHours(iso, hm, 1)`、结果**同时**写 `fEndDate` 与 `fEndTime`
- 结束分支**没有任何** `fStart*` 赋值
- 全天日程有 `!this.fAllDay` 判断
- `pickIsEnd = !isStart`(#34 主体防回归)
- 不再残留 `this.fEndDate < iso` 那种条件顺延
- 反向:改回老写法 / 只写日期不写时间 / 改结束顺手改开始 / `pickIsEnd` 再反过来
  → 逐条转红;**对照组**(只改一句注释)仍绿 —— 证明断言不会"见到改动就红"

> ⚠️ 接线判据自己第一版有 bug,是反向验证抓出来的:坏1 用正则
> `if \(!this\.fAllDay\) \{[\s\S]*?\n      \}` 找替换目标,但渲染层还有一个
> `if (!this.fAllDay)`(控制「结束」那一行显示),正则先吃到那个 ⇒ 注入落在无关位置
> ⇒ 断言仍绿(假阴性)。改成"先把 applyPicker 的函数体切出来再替换"。

**编译**:BUILD SUCCESSFUL,33 tasks(17 executed)。验签通过。

**⚠️ 已知存量失败(与本次无关)**:`_fb_arkts_guard.js` 跑出 1321 通过 / 3 失败,
三条都在工作区里**别的会话未提交的改动**上(`AGENDA_BACK_DAYS` 被换成 `CAL_FULL_FROM/TO`
导致"列表视图显式传日期范围"判据失配、`canEdit` 相关两条属 #31)。
本次只改 `FbDateTimeField.ets` 与 `FbEventEditSheet.ets`,不碰那些文件;
用 `git show HEAD:` 核实过 HEAD 版本里 `AGENDA_BACK_DAYS` 还在(判据本来通过)。
2026-11-01 09:38:14 +08:00
laoyang d2d0a435a3 日程编辑:点「开始」却改了结束时间 laoyang/FamilyBook#34
用户 2026-10-30:「鸿蒙端,日程添加时,选择时间,点上面,
修改的反而是结束时间,这个是 bug。」

根因:openPicker 的参数语义与调用方相反。
  · 调用方 dateTimeRow(label, isStart) 传的是 **isStart**
  · 被调用方签名却是 openPicker(isEnd),内部直接 pickIsEnd = isEnd
  ⇒ isStart=true(点开始)→ isEnd=true → 整条链路按"结束"处理。

错得很"自洽"所以一直没被发现:
  · 弹层初值读的是结束时间
  · 确认后写回的也是结束时间
  · 连标题都显示「选择结束时间」
三处一起错,"弹出来的值"和"改完的值"是一致的,看着不像坏。

修法:参数统一成 isStart(与调用方同名同义),内部自己取反。
  没在调用处写 openPicker(!isStart) —— 双重否定更容易再错一次。

对照:观影表单 FbMovieNewSheet 用同一个 FbDateTimePicker 组件,
但 pickIsEnd 是显式赋 false/true,没有正反颠倒的空间,不受影响。

验证:
  · 编译 BUILD SUCCESSFUL(33 tasks / 17 executed)
  · 从真实源码提取三条映射做断言:pickIsEnd=!isStart、
    读日期/读时间均在 isStart 时取 fStart* —— 全部通过
  · 真值表:点开始→读fStart*→写fStart*→标题「选择开始时间」,
    点结束→读fEnd*→写fEnd*→标题「选择结束时间」
  · 反向验证:拿修复前的代码跑同一判据,正确转红
    (旧版点开始时读写都是 fEnd*),证明判据不是恒真
  · 全仓库排查 isStart/isEnd 用法,无同类问题
2026-10-30 15:41:38 +08:00
laoyang 7bc26dd7e1 地点联想:改用鸿蒙自带 Map Kit,不再走高德 laoyang/FamilyBook#33
用户指示「端侧不用高德,用鸿蒙自带的接口」。

改用 @kit.MapKit 的 site.queryAutoComplete(),顺带解决原方案两个问题:
  ① 高德 Web 服务 key 是服务端密钥,本不该出现在客户端;
  ② 线上那个 key 实际是 JS API 类型、调 Web 服务接口一直失败(见 #33 排查记录)。
现在端上不需要任何外部 key,也不需要我们自己的服务端接口与配额。

改动:
- FbGeoRepo:site.queryAutoComplete({query}) -> sites[]
  取 name + formatAddress + location。
  formatAddress 的 SDK 注释原文是 "Formatted detailed address of a place",
  正是用户要的「作为参考的详细地址」。
  formatAddress 缺失时用 locality + streetName + streetNumber 兜底。
- FbAddrInput:去掉 token 参数(MapKit 鉴权走 AGC 应用认证,与 API 令牌无关)。
- FbEventEditSheet / FbTripNodeSheet:两处调用同步去掉 token。

错误码全部降级为空结果、不打扰用户(声明文件列明的):
  - 1002600003 App authentication failed
  - 1002600004 The Map permission is not enabled
  - 1002603001 Zero result(查不到,属正常业务结果,不刷错误日志)
  - 1002600002 Failed to connect to the Map Kit server
联想挂掉 => 退化成普通文本框,手敲照常保存。

字段名一律以声明文件 @hms.core.map.site.d.ts 为准:
StreetNumber 的字段是 streetName / streetNumber,
第一版凭印象写成 street / number,编译直接报
"Property 'street' does not exist on type 'StreetNumber'",已修正。

编译通过(33 tasks,17 executed)。
2026-10-30 08:12:15 +08:00
laoyang f4f27cea55 地点输入:补地址联想,下拉带详细地址好区分同名地点 laoyang/FamilyBook#36
用户 2026-10-30:「在所有调用高德显示详细地址的地方…不能只把地点告诉我,
一定要把详细地址也放在后面。因为同一个地址可能有好几个,
我根本不知道应该选哪一个。」

网页端(addr_suggest.js)已经是这个行为,端上原本**连联想都没有**(纯文本框)。
本次把能力补到端上:

- 新增 FbGeoRepo.ets:调服务端 /api/v1/geo/tips,最少 2 字才查
- 新增 FbAddrInput.ets:候选每行两行字(地名 14px 深色 + 详细地址 12px 灰),
  同名地点靠第二行区分;防抖 260ms(与网页端同节奏);用 seq 丢弃过期响应
- 日程编辑、行程节点两处地点接入

🔴 走服务端代理而不是端上直连高德:高德 Web 服务 key 是服务端密钥,
打进安装包等于公开(反编译/抓包即得,配额会被跑光、key 可能被封)。
网页端用的 JS API key + 安全密钥是域名白名单那套,原生端没有等价机制。

⚠️ 必须是 @ComponentV2 组件而不是 @Builder:@Builder 的值类型参数
不参与状态观察(SDK r/quick-rules/05-state.md),候选列表/展开态写在
@Builder 里会数据变了界面不重建。本项目在 FbEventChip/FbTagChip/
FbCalOptionRow 上各踩过一次。

⚠️ 联想失败一律返回空候选 ⇒ 退化成普通文本框,手敲照常保存
(接口挂/没网/没配 key 都不能拖垮主流程)。

编译通过(33 tasks,17 executed)。
2026-10-30 08:03:13 +08:00
laoyang fd3ea53086 资源:点列表进只读详情,附件可预览/下载,编辑走底部按钮分离 laoyang/FamilyBook#29
用户原话(2026-10-28):「资源模块,点击资源后是编辑,那怎么下载资源
里面的文件呢?应该和名册一样,点击后是详情,属于谁、共享给谁、附件,
可以打开预览然后下载。下面有按钮可以编辑,点编辑之后才是编辑功能。」

原来 FbResourceSheet 是「查看=编辑」合体:点列表直接进可编辑框,附件区
点不动、没有预览/下载。这次拆成两级:

1) 新增 FbResourceDetailSheet(只读详情):标题/正文纯文本、归属人、
   共享列表、附件列表(图片点开全屏预览、非图片点开交给系统「打开方式」、
   每条附件可「下载」)。底部「编辑」按钮(仅 owner/write)→ 切编辑浮层。
2) FbResourcesPage 编排:点列表 → 详情;详情「编辑」→ 原 FbResourceSheet;
   编辑保存/删除回列表。 + 新建仍直接进编辑浮层。
3) 新增 common/FbAttachOpen.ets:附件「下载明文 / 打开发 / 存相册 / 另存为」
   的端上工具。走服务端 media.php 的移动端出口(能力早已就绪,纯端上改动)。
   · 存相册:photoAccessHelper.showAssetsCreationDialog(系统弹窗,无需 WRITE 权限)
   · 另存为:picker.DocumentViewPicker.save
   · 系统打开:隐式 Want viewData + 读授权 flag

编译 BUILD SUCCESSFUL;守卫 _fb_arkts_guard.js 1324/0(浮层计数清单已同步
resources 1→3 模板、books 3→4 补 #28 存量、cardN 42→44)。
2026-10-28 23:53:53 +08:00
laoyang f5a979f5c8 读书上传+在线阅读+笔记导入:端上补齐三个入口 laoyang/FamilyBook#28
用户原话(2026-10-28):「鸿蒙端对齐网页端」。读书端上原来只有书架。

1) FbBookUploadSheet:上传电子书(选文件→服务端解析建书)+ 粘贴导入读书笔记
2) FbBookReadSheet:在线阅读(章节列表 + 单章正文,一次一章)
3) FbBookSheet 详情态加「在线阅读」入口(有章节时才显示);
   FbBooksPage 加「+上传/导入」入口 + 设置菜单「导入笔记」接上

编译 BUILD SUCCESSFUL;服务端冒烟 _i28_book_api.py 11/0 + 反证命中。
2026-10-28 22:47:38 +08:00
laoyang f1f279f973 电影影评+评论:详情浮层接「评价与评论」真功能 laoyang/FamilyBook#27
用户原话(2026-10-28):「鸿蒙端对齐网页端」。观影详情原来只能看平均分。
现在:

1) FbMovieRepo 加影评/评论类型 + reviews/saveReview/deleteReview/
   addReviewComment/deleteReviewComment 五个 API + 解析(含感受目录、评论树、mine 判断)
2) 新组件 FbMovieReviewSheet:我的影评(打分 1-10 星 + 观后感受多选 + 正文 +
   改/删)、全部影评列表(含评论树、回复、逐条删除)
3) FbMovieDetailSheet 加「评价与评论」入口 + 内嵌影评浮层

⚠️ 影评数/均分仍以列表项为准(详情接口不返回,与既有注释一致)。

编译 BUILD SUCCESSFUL;服务端冒烟 _i27_movie_review_api.py 22/0 + 反证命中。
2026-10-28 22:30:09 +08:00
laoyang 59a9d3df39 任务共享:面板补「共享给谁」区块 laoyang/FamilyBook#26
用户原话(2026-10-28):「鸿蒙端对齐网页端」。任务面板原来没有共享入口,现在:

1) FbTaskShare 类型 + parseTaskDetail 解析 Task::find 已带的 shares
2) FbTaskRepo 补 fetchTaskShares/shareTask/unshareTask(删除走 query)
3) FbTaskPanel 加「共享给谁」区块:列表(可改/只看)+ 逐条取消 +
   「+ 共享」复用选人组件(与资源/财务共享同一套)

编译 BUILD SUCCESSFUL;服务端冒烟 _i26_task_share_api.py 13/0 + 反证命中。
2026-10-28 22:10:15 +08:00
laoyang f93dd92cdb 资源附件+共享:浮层补「附件」「共享」两个真功能 laoyang/FamilyBook#25
用户原话(2026-10-28):「鸿蒙端对齐网页端」。资源浮层原来「附件与共享
还在网页端,App 暂未开放」,现在服务端补齐 API 后:

1) FbResourceRepo 补 attach/share 写操作(上传走 fbUploadOne 字段 file;
   删除走 query 传 attachment_id / person_id —— DELETE 带 JSON body 服务端读不到)
2) FbResourceSheet:附件区块(列表 + 缩略图/图标 + 「+传附件」+ 逐条删)+
   共享区块(列表 + 「共享给…」复用选人组件 + 逐条取消)
   · 只读共享来的不摆入口(与 canWrite 对齐)

编译 BUILD SUCCESSFUL;服务端冒烟 _i25_resource_api.py 21/0 + 反证命中。
2026-10-28 22:00:15 +08:00
laoyang 97846029c7 笔记图片块:展示真图 + 插图 + blocks 保存(图文混排后半程)laoyang/FamilyBook#23
用户原话(2026-10-28):「鸿蒙端对齐网页端」,笔记端上只支持纯文本。
这半程改鸿蒙端,配合服务端 cd56a9d 补的 /notes/upload:

1) 展示:FbNoteItem 新增 blocks(FbNoteBlock[]),parseNotes 把原始块
   带进来;FbNoteSheet 里 image 块真画图(fbImageSrc),不再显示「[图片]」。
2) 插图:正文区下加「+ 插图」,选图 → fbUploadKey 上传拿 key → 加入
   待保存列表;每张图带「×」可删。
3) 保存(P0 修复):带图时改走 saveNoteWithBlocks —— 正文 text 块 + 图片
   image 块整组提交。原来只传 body,服务端折成单个 text 块整组替换,
   **编辑带图笔记会把图片块静默删光**。
4) fbUploadKey 是 fbUpload 抽出的第二出口:复用同一 multipart 核心拿 key,
   不复制第二份实现(本项目"两份实现漂移"栽过多次)。

编译 BUILD SUCCESSFUL;守卫 _fb_arkts_guard.js 1324/0;服务端冒烟
_i23_note_image_api.py 11/0(含 PATCH 后图片块不被删)+ 反证命中 2/2。
2026-10-28 21:35:11 +08:00
laoyang 051d3974ba v86 首页补齐五项入口(就地编辑 + 4 张卡新建 + 新增观影/联系人卡)
用户 2026-10-24「端上也全部都实现」,即 v84 验收时留下的三条:
名册新增成员 / 首页日程弹框后的编辑 / 首页其他卡片的新增。

实现:
- 日程详情卡「编辑」改成就地叠编辑表单,不再 closeEvent()+switchModule
- 近期任务 / 最近纪念日 / 最近观影 / 联系人 四张卡加「+ 新建」
- 观影卡与联系人卡端上原来没有,本次新建(数据走本地仓储,/home 不返回)
- backOnce 按层加 6 个分支;@Monitor 覆盖 8 个标志位
- FbSectionTitle 扩 addText/onAdd(带默认值,老调用点不改)

真机验收抓出三个「断言全绿但用不了」的 bug,一并修掉:
1. 编辑表单所有按钮点不动 —— Stack 层叠顺序写反,详情卡的整屏遮罩
   盖在表单之上。dumpLayout 坐标全对、节点全在,只有真点才会发现。
2. 「+ 新建」点了没反应 —— syncNote 在本页只有赋值没有渲染点,
   失败分支静默 return。统一改成 note() = 赋值 + 当场 Toast。
3. 改选「不重复」存完还是「每天」—— recognize 的保护逻辑(防清掉自定义
   重复规则)误伤了「用户主动清空」:原规则非空 + 选 none ⇒ rrule 整项
   不发。加 fRepeatTouched 区分「归入 none」与「主动选 none」。

验证:守卫 1167/0 · 反向验证全量 242/0 · 装机成功
端到端落库 建→改→删 全走通,测试数据已清理
2026-10-24 20:22:35 +08:00
laoyang cdbf0a9593 v84: 首页「+ 新建」日程 + 红线去端点 + 新建表单两处可见性修复
用户 2026-10-23 原话:「日程,没有新建日程按钮,名册,也没有新增成员这个按钮。」
「首页上的日程,红色线不对,变短了左侧缺了一块。在日程模块中,那个红线左侧
是不是还有一个红点?如果有,去掉。」

① 首页日程区块加「+ 新建」(FbHomePage / Widgets.FbSectionTitle)
   - FbSectionTitle 扩两个可选参数 addText / onAdd,带默认值 ⇒ 老调用点一行不改
   - 按需懒拉 /calendar/books,缓存进 calBooks,同一会话只拉一次
   - todayIso() 用本地年月日拼,**不用 toISOString**(UTC 会让早 8 点前算成昨天)

② 时间红线两端统一成「一根满宽线、无端点装饰、无行首文字」(FbDayTimeline)
   - 老写法 `Row { Text(时间) width(34); 线; 圆点 }` ⇒ 行首 34vp 把线顶掉一块
     (与网页端 .ag-now「双重缩进」同一类病),行尾 6x6 圆点就是用户问的红点
   - 改成满高 Column + Blank 撑高 + 满宽 2px 线,与卡片端同一写法

③ 🔴 真机实测发现的两处可见性问题(FbEventEditSheet)
   - 「加入哪本日历」原来排在「提醒」之后 ⇒ 表单高于卡片(maxHeight 86%),
     它被推到滚动区下方,首屏只看到被底边切掉半行的标题(实测 bounds 高 7px)。
     它是必选项(没选就「请选一本日历」存不上)⇒ 挪到「全天」之后、日期时间之前。
   - 错误提示原来在滚动区**内部最底部** ⇒ 用户在顶部点「创建」时提示在几百 px
     之下看不见;就算滚到底也被 Scroll 底边切半行。现象是「点了创建什么都没发生」。
     ⇒ 挪到滚动区外面、按钮区上方(实测:Scroll 底 1953,提示 1984~2024,按钮 2074)。

真机验收(设备 5NC0225B27004718,线上 family.yangyq.net):
  - 静态守卫 1112/0;BUILD SUCCESSFUL;install bundle successfully
  - 「+ 新建」bounds [868,931][1018,1045],点击真的弹出「新建日程」
  - 诊断确认 books=15 canWrite=6(数据层完全正常,之前的「选不了日历本」是可见性问题)
  - 日历本挪位后首屏可见:标题高 40px(原 7px),10 本 chip 从 838 排到 1358
  - 错误提示挪位后不滚动即可见:命中「日程得有个标题」
  - **端到端建卡成功**:线上库确认 id=2411 cal=5 20:00~21:00,首页 1 条→2 条;
    测试数据已按 id 精确删除(remaining_test_rows=0),重启后恢复 1 条
2026-10-24 19:14:18 +08:00
laoyang 1c6af8ac56 v79b 任务面板:补「看板」一行 + boardOf 反查兜底
真机验收时发现 boardOf() 定义了却从没被渲染,面板上看不出这条任务
属于哪个看板 —— 而「挪看板」正是 v79 新做的动作,做完看不见结果等于没做。

- infoRows() 增加 kvRow('看板', boardOf())
- boardOf() 在 base.board 为空时按 boardId 反查 boards,最后回落到「默认看板」

验证:静态守卫 1068/0(新增 ⑰ 三条)· 反向验证 222/0(新增 2 条注入)
     真机探针 _fb_v79_verify.py 57/0
2026-10-22 18:46:04 +08:00
laoyang 559d79b2e2 v79b 端上:修 ForEach 键——改完状态卡片还显示旧状态
自查发现的隐患,**编译通过、静态断言全绿、节点也都在**,只是内容陈旧:

任务行是 `@Builder taskRow(t: FbTaskItem, …)` / `taskCard(t)` —— 入参按值传递、
不参与状态观察(本仓库文件头第 A 条反模式)。而 `ForEach` 的生成键只有
`'htk_' + t.id` ⇒ 改了状态 → 重拉列表 → `this.tasks` 换成新对象,但**键没变**
⇒ ArkUI 按 key 复用旧节点、不重跑生成器 ⇒ 卡片仍显示旧状态。

用户会看到:「面板里改成已完成了,关掉面板那张卡还是待办。」

修法:新增 `model/Types.taskKey(t)`,键里带**所有会被画到行上的字段**
(status / boardId / dueAt / overdue / childDone+childTotal / title),
首页与任务页两处 ForEach 都改用它;面板里子任务行的键也补上 `k.status`。

⚠️ 代价是状态一变这一行会重建(丢的是这一行的局部动画,不是滚动位置)。
   这是刻意的取舍:**宁可重画,也不要不刷新**。

新增守卫 ⑯ 组 10 条(计数/关系型:
「键生成器必须调 taskKey」「taskKey 必须含这 6 个字段」「子任务键要带 status」),
并补 5 条反向验证注入(退回只拼 id / 漏 status / 漏 boardId / 子任务键缺 status)。

守卫 1064/0 · 反向 220/0 · 编译 BUILD SUCCESSFUL。
2026-10-22 17:33:32 +08:00
laoyang 25a0797a74 v79 端上:任务面板(首页/任务页共用)+ 任务模块功能补齐
用户原话:「鸿蒙端的功能,也需要同样适配,在首页上可以直接修改编辑任务,
另外任务模块的功能需要完善。」

端上原来只有"看列表 / 勾完成 / 新建改标题",网页 v78 那套「就地处理」
(改状态、挪分组、子任务、附件、留言、删除)一件都没有;首页点任务
弹的还是 v73 的只读卡。

一、新增 FbTaskPanel(首页与任务页共用同一组件)
  · 改状态三段(待办/进行中/已完成)+ 标题左侧状态圈
  · 挪看板 chips、勾/加子任务、加图片附件并删、发留言
  · 编辑 → 交给宿主开 FbTaskEditSheet;删除要**点两次**(端上没有 undo)
  · 每个动作成功后再 getTask() 重拉一次:一次动作会牵动子任务计数、
    附件张数、看板色,逐处同步 DOM 必然漏一处

二、退役 FbTaskDetailSheet(v73 只读卡,387 行)
  ⚠️ 它已无人挂载,而守卫还在逐条"通过" —— 等于给死代码盖章。

三、任务模块补齐
  · 卡片点击 → 面板(不再直接进编辑表单:误触一下就开始改标题,
    且子任务/附件/留言没地方放)
  · 「只看已完成」改真打 ?view=done:看板视图服务端就写死
    status <> 'done',在客户端筛永远是空
  · 看板列表改走 ?view=boards:原来从任务反推 ⇒ 空看板永远选不到
  · 编辑表单补分类/循环两排

四、顺带修掉一个自查发现的真 bug
  任务页里 sheetCount 有**两个** @Monitor 都监听 boardSheetOpen、都写同一个值
  ⇒ 关掉设置浮层时谁最后执行不确定,会把还开着的面板"顺便让位结束",
  底部悬浮栏浮上来盖住面板按钮。已并成一个取并集的 monitor。

服务端(另一仓):新增 GET /tasks?view=boards、POST/DELETE /tasks/<id>/attach,
详情补 board_name/board_color_calc,越权删别条任务的附件 → 404。

验证:静态守卫 1055/0(新增 v79 一组,含"全页只许一个 sheetCount 赋值点")、
反向验证 215/0(新增 15 条注入,逐条证明新断言会红)、
服务端 e2e 1558/0、_fb_v79_api.py 26/0、编译通过并已装机。
2026-10-22 16:40:44 +08:00
laoyang c7bd26dd1b v77 旅程:列表只留整卡入口 + 动作区改成 2×2 + 地址不截断 + 题图
用户 2026-10-22 的五条:

① 列表卡去掉「看行程」「编辑」——「点进去之后会有编辑,直接点进去
   看行程就可以」。卡尾整行删除,整卡可点变唯一入口(标题行尾留一个 ›
   作提示);openEdit() 一并删掉,不留死代码。

② 详情页动作区重排——「排列得不好看,乱七八糟的」。旧版是四颗彩色实心
   胶囊分两行、右端对不齐。现在 ☰ 挪到标题行右侧,四颗动作做成 2×2
   等宽(layoutWeight(1));主操作保持实心,编辑/删除改浅底同色字。
   配色与顺序仍是 v41 硬要求,一个 hex 都没改。

③ 出行方式:十种方式**不能增删**(网页版也只是只读 + "常用信息",
   而端上加行程表单里根本没有车次/车牌,那条备忘在端上无从使用)
   ⇒ 方向反过来,给加行程表单补上「车次/航班号」(火车/飞机/轮船/
   公交/地铁)和「车牌」(自驾,从车辆库选一台,服务端按 vehicle_id
   join 出车牌)。没有这两项,"出行方式"在端上就只剩一个图标。

④ 行程详情里的地址显示不全——「从哪到哪,如果地址特别长就显示不全」。
   原来出发地与到达地挤在一条 Row 里各 maxLines(1) + 省略号;
   改成「从」「到」上下两行、整段铺开不截断。

⑤ 旅程题图:端上原来根本没有上传链路(服务端只有观影的 poster 槽位)。
   服务端已加 POST /trips/<id>/cover;端上新建/编辑浮层加 16:9 题图选区,
   流程「先建 → 拿 id → 再传图」,传图失败记 id 再点只补传(不会建出
   两趟一样的旅程)。详情页空题图那块也能直接点开去加。

顺带把 multipart 上传收敛成一份(data/FbUpload.ets):原来头像 / 海报 /
观影照片各抄了一遍,旅程题图会是第四份。
2026-10-22 10:15:03 +08:00
laoyang 3e2904cc3d v75 旅程重构:列表页 + 详情整页,卡片改「图在上、信息在下」
用户 2026-10-21:
「每一个旅程里面的行程才是重头戏……点击一个旅程,应该进入到一个新的界面,
  在里面列出所有的行程,就跟网页版的一样。
  这个旅程的图片是比较重要的……图片在正上方,就是充满整个的这个列表的宽度。
  然后呢把这个标题还有其他的一些信息放在下面。如果是图片放左边,信息放右边的话,
  它总是被挤占,显示不全。……想看其他的这个旅程的话,直接往下滑就可以了。」

- 新建 `pages/trips/FbTripDetailPage.ets`(照 `trips/show.php`):题图 16:9 占满整宽、
  五项统计、行程按 计划/实际/未成行 三组(空组不显示)、页头两行动作区
  (+计划=蓝 / +实际=绿 / 编辑=黄 / 删除=红 / 设置)
- `FbTripsPage` 846→573 行:删掉就地展开全套,`tripCard` 改「题图在上、信息全在下面」
  卡尾「看行程」自己带 onClick,整卡也可点
- `Types.ets` `FbTripNode` 8→18 字段 + 四个新类型;`FbTripRepo` 新增
  `fetchTripDetail` / `fetchTripSummary` 与四个解析辅助
- 修真 bug:`parseNodes` 读 `expense_cent`,服务端写的是 `spent_cents`
  ⇒ 每段「💰 这一段花了」从来没显示过
- `Store.ets` 新增 `@Trace tripDetailId` 与 `leaveTo(parentKey)`:
  删完整趟回列表要走它(内部 `applyModule` 会 `resetScroll`),
  直接赋 `activeModule` 会让回列表后轻轻一滑就收起悬浮栏
- 接线:`Index` 分发 / `TopBar` 标题「旅程」/ `Modules.fbModuleColor` 归到 trips 色
- `FbTripNodeSheet` 新增 `@Param startStage` 预选阶段(选择器保留,点错还能改)

守卫 931/0,反向验证 164/0,编译 BUILD SUCCESSFUL,真机待设备连上后跑 _fb_v75_verify.py
2026-10-22 00:07:16 +08:00
laoyang 6e7cdaf5fc v74 端上删掉家谱 + 名册页头改「醒目搜索 + 添加」
用户 2026-10-21:「算了,你画不好。这个功能直接从鸿蒙端去掉吧,不要了。
这样名册这个功能,进来以后,上面就是搜索和添加按钮,搜索,做的醒目一点,
现在看不清。家谱和名册的标签,都不要了。家谱的相关功能,都删除。」

家谱链路整条删除(不是隐藏):
- 删 common/KinLayout.ets、components/FbKinGraph.ets 两个源文件
- 删 Types 的 FbKinPerson / FbKinGraph / FbKinRelation、FbParse 的 parseKin
- 删 FbModuleLoad 路由、FbModuleRepo 模块名、FbSyncAll 预同步清单里的 kin
- 删 DemoData 的 FB_DEMO_KIN_RELATIONS、Theme/Settings 的家谱图标映射
- 删死资源 fb_ic_kin.png
- Index 的 kin 分支去掉(不再有「名册/家谱」两个模块共用一个页面)
- FbPeoplePage:删 subView 分段切换、kinHeader/kinView、loadKin、segBtn;
  openDetail 不再借家谱字段兜底,改成纯 id 占位 + 异步补全
  (保留"本地没缓存也要立刻开浮层",那是 v73 修过的"点了没反应")

名册页头:
- 第一行就是搜索 + 添加,上面不再有任何东西
- 搜索框从「看不清」改成醒目:白底 #FFFFFF(原来 inputBg 与页面米色几乎同色
  ⇒ 没有边界)、1.5 主题色描边、高度 36→44、图标 15→18 并染主题色、
  字号 fontSmall→fontBody、提示词收短成「搜索姓名 / 首字母 / 手机」

验证:
- ArkTS 静态守卫 917/0(新增 6 条:家谱五条链路全清 + 搜索框三个具体数值)
- 反向验证 154/0(新增 7 条注入全部有效;另修好两条因代码演进失效的旧锚点)
- 编译 BUILD SUCCESSFUL + 装机 install bundle successfully
- 真机 v74 探针 22/0(高度实测 44.2vp、白底、占屏 66%、同排对齐、
  家谱在页面与抽屉里都不存在、搜索输入与清空真的生效)

顺带修掉探针三处假红(都不是应用问题,是量错了节点):
- 搜索框在界面树里是两层,TextInput(75px) 只是内部输入区,
  白底容器 Row(138px) 才是盒子;只按"有文字/可点"收集会把它漏掉
- uitest uiInput inputText 是 (x,y,text) 三参数,只给文本会静默不输入
- 空态文案是「换个关键词试试…」,按"没有/找到"判永远为假
2026-10-21 23:28:19 +08:00
laoyang 8cabc35400 v73b:家谱连线改成「每对父母一条母线」,去掉重合的横段
旧画法每个子女各画一条独立折线,在真实数据上会画出两条横段完全
重合的线(岳父母→我户 216→72 与 我父母→姐户 72→216,拐点同为
132.9),视觉上是一个「口」字框,看着像父母家和岳父母家连在一起。

新画法:父母单元中点 ──竖──→ 母线高度 ──横(只覆盖自己子女)──→
各子女 ──竖──→ 子女卡片上沿。一条线只服务一对父母,不再交叉重叠。

关键修正:子女挂点必须是**子女本人那张卡**,不是「子女所在户的中点」。
我这一户同时是两对父母的子女户(我父母→我、岳父母→妻子),
用户中点时两条竖线都落在 x=72、完全重合;改用本人卡的 x 后
妻子那条落在 105,两条线才分开。

⚠️ 这类病端点断言完全抓不到(端点都好好贴在卡片上),所以测试侧
新增「没有任何两条线段完全重合」,并在视图侧说明 bus 段是同一折线
公式的退化情形、不必单独写分支。

验证:离线 30/0、反向 13/0、真实数据+屏幕对照 11/0、
像素级 9/0、真机几何 15/0、ArkTS 守卫 905/0。
2026-10-21 22:57:56 +08:00
laoyang 13f716c936 v73:家谱自绘重做(Canvas 画线 + 原生卡片),去掉列表只留图谱
用户原话:「手机端这个家谱实在是很难看,能有什么库之类的,让它好看点吗」

改法:不再全 Canvas 手绘(只有彩色圆点 + 首字,中文又平又硬、做不出圆角阴影),
改成「Canvas 只画线 + 原生 Column/Text/FbAvatar 绝对定位」。
不能改用 Path 组件:Path.commands 单位是 px,而 .position() 是 vp,混用会缩 1/3;
Canvas 绘制坐标系本身是 vp,与 .position() 自洽。

布局算法抽成纯函数 common/KinLayout.ets(可单独验证,不再和"画"揉在一起):
- 横向单位是「夫妻单元」而不是「人」,夫妻并排紧邻
- 子女挂在夫妻单元中点下方(family-chart 语义)
- 迭代松弛 24 轮容纳冲突约束(家谱不是树:姻亲的"挂父母下方"与"夫妻并排"互相打架)
- 全程确定性(排序带 minId 兜底),避免每次重画卡片抖位置

真机抓到并修掉两个真 bug:
1. 户号 0 被当成"没分户":服务端 unit_of 从 0 编号,端上判 > 0
   ⇒ 我与妻子被拆两户(真机 Δx=680px)。修:布局层 >= 0,解析层缺失给 -1,
   不把任何数值当哨兵。
2. 线没连对(用户原话「主要是线没连对」):buildBloodEdges 用 gen 数值当行号,
   minGen=-1 ⇒ 长辈线从 y=-148 起(屏幕外),整组线串到上一代卡片。
   修:新增 rowIndex(gens, gen),用走标换算而不是数值。

验证:
- 离线布局 29/0(含户号 0、脏数据一户三人、镜像常量与端上源码一致)
- 反向验证 11/0(9 处注入,含"户号 0 当没分户"与"用 gen 数值当行号"两条真 bug 复现)
- 真机几何 15/0、真实数据+屏幕对照 9/0(反解密度 3.125x,残差 0.23/0.32px;
  12 个血缘线端点全部贴在卡片上下沿)
- ArkTS 静态守卫 905/0
2026-10-21 22:18:43 +08:00
laoyang 48a0507455 v73:桌面卡片刷新修复 + 首页日程/任务就地弹详情卡 + 一键导航
用户 2026-10-21 三条:
  「卡片里面的数据并不刷新……应用里面的日程已经有了,但卡片上的日程却并不刷新」
  「首页上的那个日程和任务,点击之后都应该像日程和任务里面的卡片一样弹出来,
    不要进入到日程里面……有地址的还可以直接一键导航」
  「任务也是一样,点击任务之后弹出来详情,可以标记它是不是完成了」

① 卡片不刷新(根因)
   卡片进程(FormExtensionAbility)与应用进程靠 preferences 传 formIds / 翻页偏移。
   SDK 明文禁止跨进程用 preferences,且 getPreferences() 在**进程内**缓存实例、
   同名再取不读盘 ⇒ 应用进程可能永久持有空注册表 ⇒ pushAll() 首行
   `if (list.length === 0) return` 直接返回 ⇒ 卡片只靠系统 30 分钟自刷。
   修:store() 每次先 removePreferencesFromCacheSync 再取;并写死单写者纪律
   (register/unregister/setOffset/clearOffset 只由卡片进程调,应用进程只读)。

② 首页点日程 → 弹 FbEventSheet(canEdit: false,首页不给编辑入口)
   有地址的行整行可点 + 右侧「➤ 导航」胶囊;「编辑日程」才跳日历模块
   (编辑是日历页的职责,不在首页复制第二份表单)。

③ 首页点任务 → 新的 FbTaskDetailSheet
   状态圆可点(勾完成)、标题带删除线、底部「标记为已完成/未完成」+「改一改」。
   状态**必须走 POST /tasks/<id>/status**(PATCH 会忽略 status)。

④ 一键导航 FbNav.ets:startAbility 隐式 Want(不是 openLink,那是 App Linking);
   canOpenLink 需 module.json5 声明 querySchemes(未声明恒 false 且不抛错);
   高德/百度/花瓣/腾讯按序探测,一个地图都没装则复制地点到剪贴板 + Toast。

⑤ 首页登记 FB_BACK(与其余 10 个模块一致),根容器 Stack(浮层是兄弟节点,
   不能当 Column 子项排,否则卡片被挤出屏幕)。

静态守卫 905/0、v73 反向验证 17/0、assembleHap 通过、已装机启动。
2026-10-21 20:01:37 +08:00
laoyang 2ff90519fa v68:财务补齐(改一笔/删一笔/账本改名/共享给谁)
用户 2026-10-20:「还有这个财务的显示也有问题」——v65 修的是**显示口径**
(端上按正负号重加 vs 服务端负债取绝对值);这一批补的是**动作**:
设置里「共享给谁」「账本名字」一直是灰条,账目也改不了删不掉。

## 根因:不是端上没写,是服务端没有路由
`Ledger::rename / setShare / unshare / shares / updateEntry / removeEntry`
**一直都有**,`ApiHandler::ledger()` 却只开了「建账本 / 记一笔 / 删账本」
⇒ 端上只能把这两条画成"暂未开放"。

## 端上做了什么
- 点一行账目 → 同一个浮层进**编辑态**(类别 / 金额 / 名称 / 备注 +
  删除二次确认)。`aboutToAppear` 里把 @Param 拷进 @Local,
  点第二行才不会还是上一笔的内容。
- 设置「账本名字」→ 改名浮层;「共享给谁」→ 共享浮层(列表 + 选人 +
  逐个人取消)。列表解析走 `{items:[…]}`(`FbApiResult.data` 是原始 JSON)。
- 三个浮层共用 `recountSheets()` 让悬浮栏让位(原来只有"记一笔"有)。

## 顺手修的两处「对着服务端抄错」
1. `FbLedgerBook.myPerm` 的注释写 `owner / read / write`,
   而服务端**从来不给 `owner`**(主人也是 `write`)⇒ 端上"是不是我建的"永远 false。
   现在用服务端新补的 `isOwner` 布尔。
2. `FbLedgerEntry` 没有 `note` 字段 ⇒ 编辑时备注框永远是空的。补上类型与解析。

## 顺带
- 撤掉名册家谱那颗「补关系」按钮:从来没有 `onTap`,服务端 `relations`
  只有读接口、网页版也没这个功能 ⇒ 留个永远点不动的按钮只会被当成"坏了"。

## 验证
- `hvigorw assembleHap` → BUILD SUCCESSFUL
- `_fb_arkts_guard.js` → 827 / 0(新增 groupV68 29 条)
- `_fb_v59d_falsify.py` → 139 / 0(v68 新增 9 条注入全部变红)
- ⚠️ 设备仍断连(`hdc list targets` → `[Empty]`),真机未验收
2026-10-20 20:18:38 +08:00
laoyang ec2cb14c4c v67:读书模块补齐(书能点进去 / 加一本 / 读书笔记 / 统计)
用户 2026-10-20:「其他的功能也逐步完成……读书点不进去,也没法加书,
设置也没有任何的东西。」

## 先修三处「对着服务端抄错的口径」(都不报错,只是显示是错的)
1. 状态值:服务端 `Book::STATUS` 只有 wish/reading/done/paused,**没有 want**;
   `Book::status()` 遇到不认识的值会静默改成 `wish`,而端上分组键写的 `want`
   ⇒ 书架里「想读」那一组**整组不显示**(页头还数着它,看着像书丢了)。
   `paused`(搁置)同样没列 ⇒ 搁置的书也人间蒸发。
2. 评分:服务端 `rating` 是 0–10,端上当 0–5 用 ⇒ 8 分显示成 `8.0/5`。
   现在解析时折半、写回时 ×2。
3. 进度:服务端 `progress` 是「读到第几章」,端上当百分比 ⇒ 读到第 30 章 = 30%。
   现在按 `chapter_count` 换算,没解析过章节的书不画假进度。

## 补齐三个死掉的入口
- 书行整行可点 → `FbBookSheet`(状态 / 评分 / 章节进度 / 读书笔记 / 删除二次确认)
- 页头「+ 加一本」→ 同一浮层的新建态(书名 / 作者 / 状态)
- 设置「读书统计」→ 端上现算(藏书/在读/读完/想读/搁置/笔记条数);
  另两条(导入笔记 / 微信读书)要文件选择与授权,仍标「网页端」,不摆假入口

## 服务端能力边界(照实做)
- `PATCH /books/<id>` 只认 status / rating ⇒ 详情里不给改书名/作者的输入框
- 章节进度只读(`progress` 由阅读器翻页写,接口没有回写入口)
- 读书笔记走真实接口 `GET/POST /books/<id>/notes`

## 验证
- `hvigorw assembleHap` → BUILD SUCCESSFUL
- `_fb_arkts_guard.js` → 797 / 0(新增 groupV67 28 条,含「端上分组键 ==
  服务端枚举」的跨语言对照)
- `_fb_v59d_falsify.py` → 130 / 0(v67 新增 13 条注入全部变红)
- ⚠️ 设备仍断连(`hdc list targets` → `[Empty]`),真机未验收
2026-10-20 17:14:40 +08:00
laoyang 58f5509c1c v66 续:旅程三项设置(车辆管理 / 出行方式 / 节点类型)
用户 2026-10-20:「旅程的……车辆管理、出行方式、节点设置,这些东西都还没完成。」
这三项的 `action` 一直是**空串**(点了没反应),是本次批量需求里最后一块。

三项的真实能力并不一样,按真实情况做,不摆假入口:
· **车辆管理**:`vehicles` 表 + `Vehicle::save/destroy` 早就有,缺的只是 API
  ⇒ 新增 `GET/POST/PATCH/DELETE /api/v1/vehicles`;端上列表 + 内嵌表单
  (车型 / 车牌 / 自家·租的·借的 / 车主或租车公司 / 订单号 / 默认车 / 备注)
· **节点类型**:`node_types` 表 + `NodeType::save/destroy` 同理
  ⇒ 新增 `GET/POST/PATCH/DELETE /api/v1/nodetypes`;改名会**级联**到已有行程
  (`trip_nodes.node_type` 存的是名字,服务端 `NodeType::save` 里做)
  · 内置类型**不画删除按钮**(服务端会拒,画了就是"点了没反应")
· **出行方式**:**枚举常量**(`Trip::TRAVEL_MODES`),没有"管理"这回事
  ⇒ 只做一份只读对照表 + "加行程时选",**不做可编辑表单**
  (反向验证里有一条专门锁"这个浮层里不许出现保存/删除")

几个实现上的要点:
· 两条新路由是**平级资源**(`/vehicles`、`/nodetypes`),不是 `trips/xxx` 子路由 ——
  `Api::dispatch()` 只解析三段,写成四段会落到「trips 的第 0 条」= 列表
· 删不掉(还有 N 段行程在用 / 内置类型)回 **422 + 服务端原话**,不是 500:
  那是"现在不能删",不是"服务器出错"
· 表单输入框**不按值传 value**:`@Builder` 的值参数不参与状态观察,
  而这个浮层是"列表 + 内嵌表单"(点另一行的「编辑」时表单已经渲染着)
  ⇒ 会显示上一辆车的内容。改成传 key、builder 内部直接读 `this.fXxx`
· 三个浮层都遵守浮层铁律(根 `Stack` 叠放 + `hitTestBehavior(Transparent)`),
  各挂一个 `@Monitor` 让底部悬浮栏让位;`backOnce()` 按层级顺序关

服务端(`app/ApiHandler.php`)已随 `familybook_push.sh` 部署,线上核对:
`/api/v1/ping` 的 features 里 `trips_vehicles` / `trips_nodetypes` 均为 1,
两个端点无令牌时回 **401 unauthorized**(说明路由存在,而不是 404 not_found)。

验证:`hvigorw assembleHap` BUILD SUCCESSFUL;静态守卫 769 条全绿;
反向验证 116 条注入全部能让守卫变红(含"字段名拼错""按值传 value"等假绿陷阱)。
⚠️ 设备仍断连,真机未验收。
2026-10-20 16:36:30 +08:00