870a63df5a42962131be62b10b75e2d76c9ed831
106
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
df6fe894d0 |
v32 第 32 批(4 条):资源共享弹框真落库 / 消息通知覆盖全部共享入口 / 浏览器通知(站内消息+日程到期提醒) / 首页任务随看板色
用户原话: ①「资源共享功能有问题,弹框中,点击共享给某个人后,弹框消失,但是并没有共享成功。」 ②「消息通知,在所有引用自己的地方,都通知,包括别人分享了日历本给自己, 分享了笔记给自己,分享了资源给自己等等。」 ③「当有消息通知时,还有日历中日程到期提醒时,比如一个日程,设置了10点开始, 提前十分钟提醒,则需要在 9:50 时浏览器中发提醒通知。」 ④「首页,近期任务,任务的文字和前面的圆点,颜色并没有跟看板颜色一致,这个需要修正。」 四条真根因: ① v28 把资源编辑改成三页签后,每个页签各有自己的 form 与 <input name=id>, 而设 id 的代码是 panel.querySelector() → 只命中文档序第一个(info 的 update), ⇒ share/att 表单的 id 恒空 ⇒ 提交 id=0 ⇒ Resource::find(familyId,0,me) 返回 null ⇒ 静默不共享。修:表单打 data-res-idform 标记 + querySelectorAll 逐个填。 ② v30 只在 saveAttendees 后调 syncForTarget,6 个共享入口一个都没接。 新增 Notification::syncShares(家庭,类型,宿主id,动作人) —— 照共享表现状重建差额 (写表后调用;漏调只会没通知,不会状态不一致)。SHARE_KINDS 抽常量。 ③ 完全没有浏览器通知。新增 Calendar::remindersBetween()(窗口留 -2 分钟防后台节流) + ?r=notifications/poll JSON 路由 + assets/js/notify.js(轮询 + Notification API + id@at 去重)+ 顶栏铃铛(权限只能用户点一下才要,自动申请会被 Chrome 静默拒绝)。 ④ v30 把用户那句和主题颜色一样理解成了**任务模块主题色**,用户要的是**看板色**。 新增 Task::boardColorById(家庭,看板id)(没设色按看板名派生稳定色),home.php 两处改 $tkColor。 验证:诊断探针 18/0、真机 25/0(真点共享→库里真的多一行;三类共享真发通知; 9:50 窗口真算出 10:00 那条;首页每条任务色 == 其看板色)、反向 5/0、 e2e 1502/0、升级 153/0、几何 428+680+92、城市 32/0。 部署脚本静态断言段本地空跑通过;线上站点自检 19 条全过,notifications/poll 未登录 302。 |
||
|
|
be582569dd |
v31 第 31 批:DAV(CalDAV/CardDAV)同步修复 —— 发现链缺失 + ETag 潮汐 + 资源重复
用户原话:「carddav 和 caldav 好像都不好用,没有任何数据被同步出来。
同步的客户端,在 D:/Yongquan/code/SyncCalendar 中有,你可以用这个里面的代码试试同步一下。」
三处真根因**全部靠实测**拿到(不是"看代码猜"):写了 _fb_dav_diag.py,
逐字复刻 SyncCalendar 的 DavClient 请求序列(PROPFIND/REPORT/GET + 它那几个 XML 体),
打线上一次就复现出用户症状(15 条失败),修完线上 28/0。
1. 发现链(discovery)**整段缺失** ← 这就是"没有任何数据"的根因
标准客户端(DAVx⁵ / iOS / SyncCalendar)建账号按 RFC 6764 走:
GET /.well-known/caldav → 302 到 /dav/ ← 原来 404(落到 index.php 去登录页)
PROPFIND <上下文路径> → current-user-principal ← 原来回的是"日历本清单",没有 principal
PROPFIND <principal> → calendar-home-set ← 原来没这个属性
PROPFIND <home> Depth:1 / <book> / REPORT ← 这两步一直是通的
① 就断 ⇒ 客户端**在填完账号点保存那一刻**就失败,一条数据都拉不到。
为什么历次验收全绿:上批只验了 PROPFIND /dav/ 与 /carddav/ —— 恰好是**一直通**的那两步,
断点在其**前面**,从来没被测过。
改:两个入口补 PRINCIPAL_PATH/HOME_PATH + /.well-known/* 301(**在鉴权之前**)+
根/principal/列表三处都给 current-user-principal。.htaccess 用**独立标记块**追加
well-known 重写规则(本仓库第 4 次"独立入口必须独立块")。
2. DTSTAMP 取 time() → ETag 潮汐(增量同步**永远不收敛**)
App\Ics::stampOf() 原为"没给时间就 time()",而**派生条目**(观影/行程/生日/农历/
节假日/节气)没有 updated_at/created_at ⇒ 每生成一次 ICS 就写一个新"现在" ⇒ 正文变
⇒ ETag 变。线上实测:同一本连续两次 REPORT **每一条 ETag 都不同**,整本 GET 的 ETag
也在变 ⇒ 客户端把"增量同步"退化成"每轮全量重拉",且拉回来的对不上、永远不收敛。
改:缺时间写固定值 19700101T000000Z(确定优先于"诚实的当前时间")。
3. 派生本把"日历视图条目"当"CalDAV 资源"
Calendar::exportRows() 对派生本按**日历视图**取数(重复日程展开 N 次、跨天逐日、
生日/农历逐日派生)。线上 /dav/1.ics/ 实测 **2981 个 response / 只有 196 个不同 href**
(anniv-0_familybook.ics 自己重复 **2399** 次)。CalDAV 的资源是**一个 .ics 文件** ⇒
客户端要么把同一条反复入库,要么判定服务器异常而中止本轮同步。
改:CalDav::resources() 按 **UID 去重**(资源名本就是 UID)。
4. 连带两个真 bug(同一段代码顺出来的)
· exportRows() 派生分支**没按本过滤**:month() 里"任务""纪念日"两段不带 kind 判断
(它们 cal=0)⇒ 取"观影本"会把全家所有生日一起带出来(2981 条里绝大多数是 anniv)。
· CalDav::parsePath() 的 $event=(int)$evSeg:派生资源名(movie-100_familybook)
(int) 后全是 0 ⇒ 单条资源请求被当"整本",GET /dav/4.ics/lunar-...ics 实测回 **400 条**。
5. 顺带补全:系统日历本(农历/节假日/传统节日/节气)原来**导出是空的**
它们只走派生、没有 calendar_events 行,而 exportRows 只认 custom/movie/trip ⇒
手机上订阅这几个本一条都没有。改:按 [今年-1, 今年+5] 派生(**封顶**,农历每天一条,
130 年会有 4.7 万条)。实测耗时 lunarRange 31ms / traditional 51ms / Anniversary 30ms。
改:.htaccess、router.php、dav.php、carddav.php、app/CalDav.php、app/CardDav.php、
app/Calendar.php、app/Ics.php、templates/settings/me.php(补"或只填服务器地址"的提示)。
验证(全部 0 失败):DAV 探针 28/0(**线上修前 15 失败 → 修后 28/0**,同一套探针两处都跑)、
DAV 反向验证 8/0(7 处修复各自改回旧行为,断言全变红)、e2e 1478/0、升级 153/0、
城市 32/0、几何 428/0 + 移动 680/0 + 交互 92/0、b24 41/0、b27 34/0、b28 39/0、b29 38/0、
b30_import 26/0、b30_ui2 16/0、b30_notify 23/0;部署静态断言新增 12 条守卫并逐条证伪通过。
线上 7 个文件 SHA256 与本地一致;/.well-known/caldav→301 到 /dav/ 实测生效。
⚠️ 本轮在部署脚本上真踩到并修掉两个坑(已写进 pitfalls.md):
· router.php **不发到线上**,把它写进部署断言 → grep "No such file" → 部署中止在
"改了一半"的状态(迁到 push.sh 第 1 段)。
· `set -euo pipefail` 下 `X=$(... | grep -c ...)` 在 **0 匹配**时退出码 1 ⇒
**整个脚本静默退出**(不打 FAIL,日志停在上一行)。已给全部 7 处补 `|| true`。
|
||
|
|
2a10df2557 |
v30 后半(5 条):导入框选中反馈+单选 / 首页日程改色块 / 近期任务主题色 / 时间冲突错开 / 通知消息模块
用户 2026-10-08 第二批 5 条。 1. 导入日程选中文件后没有任何反应 + 不允许多选 `_stage_drop.php` 现在有两种模式:暂存型(附件,拖进来即刻上传)与导入型 (`$sdStage=false`,写回 input.files、随表单提交、PHP 读完即弃)。 暂存型框靠 queueDrop 画缩略图,而**导入型框选中文件后什么都不显示** —— 框里那句「点击或拖动进行上传」从头到尾不变,用户看不出"到底选上没有"。 改:app.js 新增 bindFileEcho(),只给**非暂存框**补 change 监听, 选中后框里的话换成文件名 + 图标变 ✓ + `.upbox.is-picked` 稳定态; 取消选择则还原原始文案(不是空字符串)。单选由 `$sdMultiple=false` 保证。 2. 首页「今天的日程」用色块表示,和日程保持一致 原来那套是"白底卡片 + 左边一条 3px 色线",色只占一条线,看不出是哪个日历本。 改:与日历页 `.tg-block` 同款实心色块(底色=日历本色,白字,圆角,无阴影), left/width/top/height 全部由内联给出(冲突分列 + 真实时长)。 ⚠️ `.ag-ev` 的 CSS 里去掉 `right` —— 绝对定位下 left+right+width 三者同时存在是 过约束,内联 width 会失效(冲突就不会错开了)。 3. 首页「近期任务」名称与圆点走任务模块主题色 原来用 `var(--brand)` —— 那是**当前页面**(首页)的主题色,不是任务模块的; 用户改过模块配色时两者不一致(首页砖红、任务别的色),正是他看到的"不一致"。 改:两处都走 `--tk-c`(= Module::color('tasks')),与右上角「全部任务」按钮同源。 逾期仍用红色(状态语义,不能被主题色盖掉)。 4. 日程时间冲突错开(算法借鉴 SyncCalendar) 用户指定去读 SyncCalendar 的算法。落地自 `entry/src/main/ets/common/TimelineUtil.ets` 的 build()/groupsOf(): ① 划分冲突组(与组内最晚结束仍重叠就并入);② 组内贪心分列(区间图着色), 列数 = **每组各自的最大同时重叠数**(sync 原注释点名的反例:传递性重叠但 B/C 不重叠 → 只该 2 列)。落成 `App\Calendar::conflictLanes()`(PHP,判据一字未改)。 ⚠️ 两处比原算法更细,都有单测钉住: · 列数按**组**算 —— 否则一个孤立日程会被别处的三方冲突连累成 1/3 宽; · "缺结束时间"不能一律按 30 分钟参与冲突(会与后面所有日程假冲突), 改为优先拿**同列下一条的起点**当它的视觉结束 → 因此必须**先分列再算高度**。 首页与日历页共用同一套(用户第 2 条要求"和日程保持一致")。 5. 通知消息模块(Schema v30 → v31) 不在导航显示,入口在头像下拉里、**排在个人设置之前**,名字「通知消息」; 有人在观影/旅程加我时发通知;通知出现在首页、**往年今天之前**。 新增:app/Notification.php + notifications 表(唯一键 (person_id,target_type,target_id) —— 参与人是整组替换,没有它改三次标题就收三条)+ ?r=notifications{,/del,/clear} + templates/notifications/index.php + 头像下拉未读角标 + app/helpers.php 的 fb_time_ago()。 ⚠️ 三条设计判断:不进 Module::LIST(进去就会被顶栏渲染、被"模块显示与排序"当开关); 只在 person_id !== actor_id 时发(不给自己发,"通知"≠流水账); 移出参与人**撤销未读**、**保留已读**(未读是没送达的提醒,已读是看过的历史)。 验证(全部 0 失败):e2e 1478/0(+14 条新断言)、升级 153/0、vCard 40 项、城市 32/0、 几何 428/0 + 移动 680/0 + 交互 92/0、b24/b27/b28/b29 真机全绿; 冲突分列算法单测 12/0(含 SyncCalendar 点名的反例)、 新探针 _b30_ui2 16/0 + _b30_notify 16/0 + _b30_import 26/0、 反向验证 18/0(五条各自改回旧行为 → 断言全红)、 部署脚本新增 9 条守卫 + 守卫证伪 11/0。线上 11 文件 SHA256 一致,Schema v31 已升级。 |
||
|
|
91c55038c9 |
v30 第 30 批(3 条):ICS 导入读完即弃 / 导航联想词根因修复 / 导航设置改纯图标
三条都是「静态检查与 e2e 恒绿、只有真机才现形」的,每条先写真机诊断探针 拿到真根因再改,并逐条反向验证(改回旧行为必须被抓住)。 1. 日程 ICS 导入:文件已上传仍提示「请选择一个文本」 真根因:_stage_drop.php 是"拖进来即刻上传到暂存区"的唯一形态—— 拖放时文件走 upload_stage.php 落到 uploads/_staging/,而 <input>.files 始终是空的;两个导入框(ics/通讯录)都带 required → 浏览器原生校验把提交 拦下,弹它自己的文案。就算绕过校验,路由只读 $_FILES['ics']、从不看 stage_token[],暂存文件永远没人认领。 改:片段新增 $sdStage(默认 true)。导入型传 false → 框上不带 data-drop-stage → app.js 走"写回 input.files、随表单提交"的老路,文件走 PHP 临时文件,路由读完即弃;不落存储、不建附件行、不进 OSS。 同时把 app.js 找框的选择器从 [data-drop-stage][data-sd-for=…] 放宽成 [data-sd-for=…]——否则这些框点不开也接不住拖动。 2. 导航联想词"还是不好用" 真根因:nav_show.php 给出的 suggest 地址已带 ?p=<短语>,而 nav-show.js 又 硬拼 '?slug=…' → 实际是 …?p=abc?slug=bing&q=…;PHP 认为第一个 ? 之后整串 都是查询值 → p 匹配不上短语 → 403 → 下拉永远空。服务端一直是对的(直连 200 + 10 条词),e2e 探针自己拼对了参数,所以 HTTP 层恒绿、只有真页面能抓。 改:nav-show.js 新增 withQuery(),按 base 里有没有 ? 决定分隔符; nav_suggest.php 加短语容错(切掉第一个 ?/& 之后的内容,救缓存里的旧 JS)。 3. 导航设置按钮改纯图标 ☰ v29 只改成了下拉菜单,summary 仍是 btn-ghost btn-sm + 文字「设置」+ 一个 向下箭头。改:换成与其他七个模块逐字节相同的 btn-icon + 三条横线。 验证:e2e 1464/0(新增 2 条)、升级 153/0、vCard 40 项、城市 32/0、 桌面几何全过、移动 680/0、交互 92/0、窄屏 30/0、b24_nav 41/0、b24_ui 26/0、 b28 39/0、b29 38/0、b27 34/0;新真机探针 _b30 22/0、反向验证 11/0; 部署静态断言新增 8 条守卫并逐条证伪通过(本轮抓到 2 条恒真守卫 + 1 处 _code_only 的 locale 差异)。线上 7 个文件 SHA256 与本地一致。 |
||
|
|
37ba86321b |
v29 第 29 批(9 条):日程定位/复选框提示/名册错乱三处真因修复 + 读书落盘 + 导航四设置 + 笔记阅读态与共享
前 4 条是"上批改了但用户仍报"的,这次先拿真机证据再动手,三处真因如下:
1. 首页没定位到红线 —— 真根因:定位脚本的 PHP 条件是
`if (!$todayEvents || empty($timed))`,**今天只有全天日程时整段脚本不渲染**
(实测 hasScript=False、scrollTop=0),而 .ag-grid(0–24h) 与红线都在。
有分时段日程时定位其实完全正确(偏差 0px)。已改成 `if (!$todayEvents)`。
2. 任务复选框提示还在 —— 真根因:`_task_check.php` 里那句无障碍文字
`<span class="visually-hidden">` 用的类名**在 app.css 里从来没有定义过**
(项目一直只用 .sr-only)→ 没有规则 = 普通 span = 直接可见。
已把两个类名合并到同一条规则(clip 隐藏,仍可读屏)。
3. 名册完全错乱 —— 真根因:v29 重写平铺列表时**漏了一个 `</div>`**(闭合 .person),
浏览器把下一张卡嵌进上一张里,实测 .person-main 只剩 24~151px(本该 ~800px)。
`php -l` 与全部静态断言都抓不到,只有真机量 DOM 才现形。已补上闭合。
另把 v28 引入的多列 grid 改回单列(它与 content-visibility 冲突)+窄屏动作区折行。
4. 读书慢 —— 按用户给的思路「做一个同步,然后落盘」:
新增 weread_snapshots 表 + App\WeReadCache(按人+数据集存 JSON);
渲染路径改走 notebooksCached()/readStatsCached()(永不联网);
同步从"渲染之前"挪到"**响应之后**"(queueBackgroundSync + fastcgi_finish_request)。
5. 导航设置改成右上角**下拉菜单 + 四视图**(addr/general/board/engine),
表单带 back 域 → 保存后回原来那一项。
🔴 顺带修掉一个真 bug:`if (!$inSettings && $canWrite)` 的 endif 漂到了文件末尾,
把 board/engine 两个视图也包进了"非设置态" → 打开时标题对、**内容是空的**。
6. 笔记列表缩略图:cover → contain + 固定高度(按比例完整显示,不裁切)。
7. 笔记段落编辑器:加 Markdown 工具条 + 斜线命令(行首打 /co → 回车补全代码块),
两者共用同一份命令表。
8. 笔记点进来默认**阅读态**(图文混排、附件在最下方、图片四档宽度、点「编辑」才可改);
清单块并入 Markdown —— 但**历史 list 块转写成 `- 项` 文本,老数据不丢**。
9. 笔记共享改成**逐人 + 权限**(note_shares,与 resource_shares 同构);
可见性收敛成全站唯一的 Note::VIS_COND;迁移用一次性哨兵。
|
||
|
|
6852b4ee2c |
v28 第 28 批(12 条):名册分组改标签 / 资源合并为两按钮 / 首页定位与主题色 / 笔记富文本双模式
其中 4 条是**上批遗漏的真 bug**(1/4/8/9),根因已逐条定位:
1. 首页日程没定位到红线 —— 真根因:刻度区间**不含当前时刻** → $showNow=false
→ 红线**整个不渲染**,而定位脚本用同一个越界比例算目标,被 clamp 到底部。
修:把当前小时并进区间端点(现在必然落在区间内)。
2. 「看整月」→「更多日程」+ 日程主题色;3. 「手头在做」→「近期任务」、
「任务看板」→「全部任务」+ 任务主题色(注入 --hd-c,不再写死白底)。
4. 任务复选框提示还在 —— 真根因:**JS 在点击后**给状态徽标挂 title
(`badge.setAttribute('title',…)`),上批只去了静态渲染,静态断言永远抓不到。
5. 名册「分组」→「标签」(用户:一个人可能既是小学同学又是初中同学,没法分组):
新增 person_tags(每人一份、一人多标签),名册改**平铺 + 顶部标签筛选**;
🔴 迁移用**一次性哨兵**(第一版用 NOT EXISTS,实测被"用户清空标签"绕过 → 旧分组名被灌回)。
6. 名册卡片按钮加语义底色。
7. 资源四按钮 → **编辑 / 删除**两颗:三块内容进一个带页签的弹框(信息/附件/共享)。
8. 财务多选数字不变 —— 真根因:前端发 `kinds=a,b`(逗号串),服务端 `(array)` 收成
单元素 → 求交为空 → **静默回落整本账**。修:两种形状都收。
9. 微信读书偶发很慢 —— 真根因:syncNow 扫 60 本 × 每本 1~3 次网关调用(单次超时 20s),
全同步阻塞在列表页请求里。修:总时长预算 + 单次超时 8s + 自动/手动分批。
10. 新增全局可见的联想接口库(6 个实测可用来源)+ 三种新解析类型。
11. 导航右上角「设置」:默认进"添加网址 + 全部网址",四类设置收进 ?set=1 视图。
12. 笔记:段落/清单支持 Markdown(自研渲染器,**不允许原始 HTML**),
实时预览走服务端(前后端只一份规则);底部附件上传;清单多级;
列表改**定尺寸卡片网格**(溢出显示摘要、图片缩放下方);按用户要求**去掉标签侧栏**。
|
||
|
|
404b1afee0 |
v27 个人笔记模块:分块(段落/图片/清单)+ 单层多标签 + 默认私有可共享
参考 flomo(时间流卡片 + 标签即筛)、Obsidian(图文混排的直觉)、
思源(「块」的粒度),但刻意只取轻的那一层:
- 分块只做 text / image / list 三种,可增删、可上下移;不做块引用 / 块 ID 双链
- 标签单层、多对多,只存字符串(**不建标签表**)——改名/合并这类重型能力一律不做
- 不做富文本工具栏、不做 Markdown 预览、不做拖拽排序(只有 ↑↓ 按钮)
- 可见性默认**私有**,单条可勾「共享给全家」;共享出去只给别人看,不给别人改
落地:
- 新增 app/Note.php(块清洗、标签归一、可见性、认领草稿附件)
- 新增 3 张表 notes / note_blocks / note_tags;Schema VERSION 26→27
- 🔴 attachments.target_type 是 ENUM:加 'note' 必须同时改建表串 +
新增 widenAttachTargetEnum()(老库不做这一步,传笔记图片会被严格模式拒掉)
- 路由 5 条:notes / notes/form / notes/save / notes/destroy / notes/upload
- 模板 templates/notes/index.php(列表)+ form.php(分块编辑)
- JS assets/js/note_editor.js(按需加载,同 person_picker 那套)
- 编辑器状态放 DOM 而非 JS 数组:移动块时用户已输入的内容一字不丢
|
||
|
|
c8839fa728 |
v26 第 26 批(5 条):财务多选按集合取图 / 首页全天置顶与红线定位 / 任务复选框去提示与看板颜色 / 导航搜索引擎切换与站点复用 / 详情页去复选框
- 财务:勾选多个标签时上面那张「总金额」图按**选中集合**统计(新增 ledger/total_chart 路由 + 单一容器 data-ck-total-host,按集合现取并缓存; Ledger::timeline 第二参数放宽为 string|array) - 首页日程:全天日程移出滚动容器固定置顶;进场把当前时刻红线居中 (scrollTop 计算,不用 scrollIntoView),触发条件不绑 $showNow - 任务:复选框去除 title 提示;看板可设颜色(Task::boardColor/updateBoard), 首页任务行用该颜色显示所属看板 - 导航:展示页补回 id=engine-tabs(切换搜索引擎无效的根因);补内置引擎; 新增站点输 URL 可复用他人已加过的站点(sharedByUrl/copyIconFile/adoptSite) - 任务详情页页头去掉复选框(已有「标记完成」按钮) |
||
|
|
a5e559b2b3 |
v25 第 25 批(9 条):名册分组不刷页 / 纪念日多人 / 首页等高 / 移出不回流 /
名册点击看详情拖动才排序 / 任务复选框与状态 / 财务标签多选 / 微信读书同步与统计 /
导航线上数据与图标
用户本轮 9 条需求(原话见 templates/ 与 app/ 内的逐条注释):
1. 名册:列表里给某人分组改为 AJAX 就地挪卡片(不再整页刷新)。
2. 纪念日:关联人改**多选**(复用 _person_picker,字段 person_ids[]);
自建纪念日的按钮与其它模块统一(改=蓝 .btn、删=黄 .btn-warn);
列表新增「关联人」列并给人名前加头像。
3. 首页:日程/任务两块**完全等高**(align-items:stretch + 固定总高 + flex 子项
min-height:0);修「0 件待办」——数字与右侧列表同源(原来各算各的)。
4. 🔴 名册里被移出的人过一阵又回来:根因是 PersonRoster::seedFromPersons()
**每次 Schema::ensure() 都无条件重灌**(线上实测每跑一次 +1 行)。
修:给它加自守卫(只有在 persons 还留着 group_id/sort_order 的**老库**才跑)。
5. 名册:点击=看详情、**只有拖 ⠿ 手柄**才是排序(原来整行既能点又能拖,语义混乱)。
6. 任务:所有任务前加复选框,任何位置点勾即完成;
状态语义照业界整理为 To Do / Doing / Done 并写出说明(用户要求参考别人的做法)。
7. 财务:类别标签改**多选非互斥**(再点一次取消该类别),上下两图 + 图例 + 表格
全部按同一个集合联动;高亮条刻意不给「合计」(多选下各类金额不能相加)。
8. 微信读书:新增**懒同步**(访问时顺手做,6h TTL,只补空字段不覆盖用户填的)、
封面图**拉下来存本站存储**(原来只存外链 → 看不到)、接官方 /readdata/detail 统计。
9. 导航:线上数据只搬了一半(5 板块 29 站 → 7 板块 67 站 + 67 图标)、
展示页头像不显示 / 搜索按钮错位 / 点登录 404。三个真根因:
· 相对地址在 /nav/ 下全 404 → 新增 Nav::abs();
· 搜索容器丢了 id="search"(CSS 是 #search button.submit 绝对定位);
· Nav::iconKey() 与 SimpleNav 的 icon_key() **算法不一致**(少一次 md5 归一化)
→ 搬来的图标文件名对不上;且 uploads/ 整目录 Require all denied。
新增两个**免登录公开出口** nav_icon.php / nav_avatar.php。
顺带修掉一个「数据损坏级」的真 bug(做第 4 条线上反证时发现的,之前一直没人注意):
app/Region.php 三处用中文标点当 trim/ltrim 的 charlist,而 PHP 的 charlist 是
**按字节**匹配的 —— 「。」= E3 80 **82** → 字节 82 进了可剥集合 →
「层」= E5 B1 **82** 被切掉尾字节 → 非法 UTF-8 → backfillAddress 的 UPDATE 报
`1366 Incorrect string value` → **地址拆分升级从上线起一直在失败被跳过**(每请求报一次)。
改成字符级 preg_replace('/…/u');线上实测 ensure() 现在无报错、待拆行数真的在下降。
验证战绩:
- e2e 1289/0(新增 Region 字节级用例等)
- 真机探针:选人 19/0 · 首页/导航 24/0 · 导航模块 34/0 · b18 暂存+确定 10/0 ·
第23批 52/0
- 桌面几何 428/0 · 移动端几何 680/0 · 交互 92/0 · 窄屏 24/0
- 老库升级 153/0(含 anniversary_people 迁移 + 「再跑 ensure() 名册行数不变」)
- 部署静态断言 180 OK / 0 FAIL · 反向验证 3/3 + Region 三变体各被两条断言同时抓住
- 线上 26 个文件与本地**逐字节一致**;展示页 200、图标 66/66 200、登录 200、
越权/不存在均 404、签发短语后 ensure() 不再报错。
|
||
|
|
09e30d27d4 |
v24 第 24 批:选人冲掉已有成员 / 首页改版 / 个人导航 / 微信读书 / 财务图
用户 6 条原话(要点):
①「选择名册里面人员的那个弹出框有个问题,就是会把页面里面已有的人员给冲掉。
比方说电影里面的观影人或者是旅程里面的参与人…这是一个 bug 应该通篇检查。」
并按「输入拼音 → 空格选中第一个 → 输入框清空回焦 → 再输下一个 → 回车=确定」重做交互。
② 首页简要信息改成:问候 + 姓名 + 日期 + 星期 + 农历 + 节气/节日/纪念日/生日,
下面「今天几条日程 / 几件待办」,天气**彩色图标**放**最右侧**,温度风力,最下面地址。
③「家里的镜框这里呃全部不要了」→ 删模块卡片墙 + 未来七天;日程与任务**左右各半**、
高度固定、内部滚动且**不显滚动条**;日程**掐头去尾** + 当前时刻**红线**;
任务显示标题+状态+**所在看板名**;最下面往年的今天。
④「把个人导航项目完整搬过来,放在读书后面」;免登录展示页 `/nav/?<短语>`;
右上角给**登录**入口;展示页头像用**用户自己的头像**。
⑤ 读书加笔记 + 导入微信读书笔记(用户明确「**用微信读书 api key**」)。
⑥ 财务页第二张多线图不随类别标签筛选。
用户拍板(AskUserQuestion):① 首页删「模块卡片墙 + 未来七天」两块;
② 微信读书用官方 API Key;③ 导航短语「自己起名 + 可一键换随机码」都做;
④ 6 条一次做完再上线。
════ 一、 🔴 第 1 条:复现出的真 bug(全站性)════
**根因**:选人清单是**打开弹框时才建 DOM**(懒建,为性能),而承载"已选"的
checkbox 就在那份清单里 → **没打开过弹框就提交**时表单里一个值都没有 →
后端把"表单里没有 attendees"理解成"用户要把人全删掉" → **静默清空**。
实测复现:库里同行人 `2,3,4` → 只改标题保存后变成 **NULL**。
修法:模板加**承运字段**(`.pp-carry` 隐藏 input,承载当前已选);
JS 建清单时**撤掉**它(否则同一个 name 会带两份值)。
⚠️ 裸 POST(完全不带 attendees)**本来就该清空** —— 那是显式地"不选任何人",
所以断言也必须照着"浏览器实际提交的样子"(带承运字段)来测。
**键盘流程**(用户第 1 条后半):空格 = 选**当前可见列表里第一个还没选的**
(⚠️ 必须跳过已选中的 —— 否则会把它**取消**,与"我要选人"正好相反,本轮真机跑出来的缺陷);
选中后清空输入框 + 光标回输入框;回车 = 确定。空格必须 `preventDefault`
(否则输入框里多一个空格字符)。
覆盖范围:4 个多选(`attendees[]` / `members[]` / `shares[]` / 修订 `members[]`)
+ 全部单选(归属人 / 负责人 / 纪念日人等)。
════ 二、 首页改版(第 2、3 条)════
自上而下只有三块:
① 信息面板:问候(早/中/晚 + 姓名)→ 日期 / 星期 / 农历 → 日子标记
(法定节假日 / 节气 / 传统节日 / 纪念日 / 生日,各一色)
→ 今日数字(今天几条日程、几件待办)| **最右**:彩色天气图标 + 温度 + 风力 + 地址
② 日程 / 任务 **左右各半**(各 360px 固定高、内部滚动、**不显滚动条**三套写法)
日程是**日视图形式的刻度**、**掐头去尾**(首末日程前后各 1h)、**当前时刻红线**
任务是标题 + **状态** + **看板名** + 截止
③ 往年的今天
新增 `Home::todayMarks()`(把五类来源合成一句话:"今天是国庆节 · 大暑")与
天气的 `tone` 色调档(彩色图标靠 CSS 上色,**不用 emoji** —— 避免旧系统豆腐块)。
删掉:`mod-cards` 模块卡片墙、`未来七天`。
════ 三、 个人导航(第 4 条,SimpleNav 完整搬运)════
`app/Nav.php`(4 张 `nav_*` 表 + 稀疏排序 + 短语)· `app/NavFetch.php`
(抓取与图标生成,27KB,从原项目 functions.php 机械搬运,**原文那批"线上实测"注释全部保留**)
· `nav_show.php`(**全站唯一免登录页**)· `nav_suggest.php`(联想代理,凭短语门禁)
· `templates/nav/index.php`(管理页:板块/站点/引擎/设置 + 拖拽排序)。
搬运只改两处(不改会坏):① 独立 SQLite → 家书的 MySQL;② **所有查询加 `person_id`**
(原项目是单用户系统,不加这一维全家人会共用同一份导航)。
免登录展示页:`/nav/?<短语>`(也认 `/nav/?<短语>` 不带参数名);
短语用户自起名(4~32 位字母数字`_-`,全局唯一、保留词拦截)或一键换随机码;
右上角**登录**入口(进家书登录页);头像用**他自己**的头像;空态给一句话。
路由三处同步(`router.php` / `.htaccess` / 部署脚本 5c **独立标记块**)——
这是本仓库第三次复用这条规律:**每加一个"站根上的独立入口",就必须是自己的标记块**。
⚠️ 线上重写规则**顺序有语义**:`^nav/suggest\.php$` 必须在 `^nav/?$` 之前
(后者会把前者吞掉)。
════ 四、 微信读书(第 5 条)════
用户提示「用微信读书 api key」是对的 —— **我的知识过时了**:2026 年官方已开放
(网关 `POST https://i.weread.qq.com/api/agent/gateway`、`Bearer wrk-` 认证、
接口 `/user/notebooks`(游标 lastSort 分页)、`/book/bookmarklist`、
`/review/list/mine`、`/book/chapterinfo`)。
`app/WeRead.php`:Key 用 `Crypto::encrypt` **加密落库**、界面只回显掩码
(官方原话:这串 Key 等价于账号凭据);导入走「拉预览 → 勾选 → 导入」,
落 `book_notes`(去重 = kind + 内容 + 位置)。
⚠️ 业务参数必须**平铺在顶层**,不能包进 `params`(官方文档专门列了这个反例)。
另一条路「手动粘贴」保留(`books/notes_import`,早已有)。
════ 五、 财务第二张图(第 6 条,**上一批我引入的 bug**)════
`g.hidden = true` 对 **SVG 的 `<g>` 无效**(它没有 `hidden` 属性)→ 线条一条都没被藏;
**而我的探针也读同一个属性** → 两边一起绿(假绿)。
改为 `style.display`(恢复用 `''` 而不是 `'block'`);探针改读
`getComputedStyle(el).display !== 'none'`(落在**渲染结果**上)。
⚠️ "探针与实现读同一个属性"是**互相掩盖**的典型,比单纯写错断言更危险。
════ 六、 验证 ════
e2e **1218/0**(本批新增 102 条)· 真机探针 **19/0**(选人)+ **24/0**(首页/导航)
+ **34/0**(导航模块)· 桌面几何 0 失败 · 移动端几何 674/0 · 老库升级 149/0
· 部署静态断言空跑 **310/0** · **反向验证 13/13**(逐条改回旧行为,点名断言都变红)
· 线上验收:9 个新文件全在、4 张 nav 表建好(schema_version=24)、
三条重写规则顺序正确、免登录页 / 空短语 / 错短语 / 联想门禁 全部响应正确。
════ 七、 本批的"夹具与断言"教训(都写进注释了)════
· `calendar_events.all_day` **默认值是 1** → 插夹具不写它就成了全天事件 →
`time` 被抹成 `''` → "刻度掐头去尾"看着像没做(白查三轮)。
· 日历本要挑 `kind='custom'`(我原以为"普通本没有 kind",实际**普通本就是 custom**)。
· 量"同高/同排"要拿**内容区**(去 padding)比,不能拿 border-box(`.topline` 有 14px 上内边距)。
· 判据不能把**边界上的正确行为**当 bug(22 点有日程时末刻度**就是** 23:00)。
· 「HTML 写了、CSS 没写」e2e 永远查不出来 —— 导航展示页的登录按钮就落到了左边
(实测按钮右 116px / 整行右 1210px),只有真机量几何才抓得到。
· 反向验证里 e2e 会**重铺副本**把注入覆盖掉 → 给 e2e 加了 `FB_PATCH_FILE` 钩子
(重铺完自动打补丁),13 例里 8 例"没牙齿"其实是这个假象。
════ 八、 新增/改动文件 ════
新增:`app/Nav.php` · `app/NavFetch.php` · `app/WeRead.php` · `nav_show.php` ·
`nav_suggest.php` · `templates/nav/index.php` · `templates/books/weread.php` ·
`assets/css/nav-show.css` · `assets/js/nav-show.js`
改动:`app/Schema.php`(v24 + 4 张 nav 表)· `app/Module.php`(+nav)· `app/Home.php` ·
`index.php` · `templates/_person_picker.php` · `assets/js/person_picker.js` ·
`templates/home.php` · `templates/ledger/index.php` · `templates/books/_settings_menu.php` ·
`assets/css/app.css` · `router.php` · `.htaccess`
|
||
|
|
ae3060f552 |
v24 第 23 批:资源页四色按钮+四个弹框 / 财务页类别标签联动上下两图
用户 2 条原话:
①「资源页面,还可以更简洁,资源打开后,没有正文就不显示(没有正文)这句话了,
直接显示附件。附件列完后,列出来人员,所有者、共享给谁了。然后其他的都不要。
把编辑、共享、附件、删除,四个按钮,放在每个资源的右上角,和标题一排,
点击后分别是编辑信息、设置共享人员,包括删除共享人等,附件,点击后弹框,
允许用户拖动上传。编辑黄色、共享绿色、附件蓝色、删除红色。
按钮点击后的功能,都是弹框。」
②「财务,统计功能和金额变化图表那里,中间,有各类标签,存款保险等。现在点击后
没有任何效果,这个需要做处理,点击任何一个标签,则统计这个标签下的所有财务条目的
总数,并在上下两个图上,画出来。」
用户拍板的两个岔路(AskUserQuestion):
· 筛选某个类别时,上面那三个总数(总资产/总负债/净资产)**保持整本账口径**,
不跟着切 —— 避免屏幕上只剩一个类别、却被误读成「我家只有存款」。
当前看的是哪一类,由概览卡里一条高亮条说明(含合计与笔数 + 清除筛选)。
· 「附件」弹框**既能拖动上传、也列出已有附件并可删**(附件管理只留一个入口);
展开区的附件墙因此变成**只读**。
════ 一、 资源页(templates/resources/index.php 重写)════
**展开区只留「看」**(用户:"然后其他的都不要"):
· 正文 —— **没有就不占位**(不再写「(没有正文)」那句话);
· 附件墙 —— 只读(可预览/下载,不再带删除按钮);
· 人员行 —— 列在附件之后:**所有者 + 共享给谁**。
原来堆在展开区底部的四坨内联表单(补传附件 / 共享设置 / 编辑 / 删除)**全部撤掉**。
⇒ 全页 <form> 的数量**不再随条目数增长**。这不是洁癖:账目页与名册页都因为
「每行内联一套表单」被密码管理器(Bitwarden)逐个扫描、把主线程占死过两次,
是用户报过的真问题。资源页这次一次做对。
**标题行右上角四色按钮**(用户点名配色):
编辑 `btn-warn`(黄)· 共享 `btn-ok`(绿)· 附件 `btn-info`(蓝,本轮新增该类)·
删除 `btn-del`(红)。四个都在 `<span class="res-ops">` 里,`margin-left:auto` 吸附右端。
**四个动作全在弹框里**(全页只有一份 `#resPanels`):
· 编辑 —— 标题 / 正文 / 归属人(用选人组件的对外接口灌数据,且**限定在弹框内**查找
——本页有多个同名字段,只按名字找会命中第一个,财务页在这上面栽过);
· 共享 —— 选人 + 权限(只读/可读写)+ **已共享给谁、逐个「取消共享」**
(用户原话:「设置共享人员,**包括删除共享人**等」);
· 附件 —— 已有附件(名字 / 大小 / 下载 / 删除)+ **拖动上传区**
(复用全站 `_stage_drop.php`:点击或拖动、拖进来立刻上传);
· 删除 —— 说明「不能撤销,会连 N 个附件一起删」,红色确认按钮。
三条结构性约束(断了浏览器会重新解析 HTML,症状很隐蔽):
① `<summary>` 里只能放 **phrasing content** → 按钮组必须是 `<span>`,`<form>` 绝对不能进
(这也是「四个动作必须进弹框」的一条额外理由:它们本来全是 form);
② 点按钮必须 `preventDefault()` —— `<summary>` 内任意点击都会 toggle `<details>`;
`stopPropagation()` 挡不住它(那是**默认行为**,不是冒泡);
③ 弹框容器自己声明了 `display:flex`,就必须补 `[hidden]{display:none}` 规则,
否则「关不掉」(项目在 `.pv` 与 `.pp-item` 上各栽过一次)。
════ 二、 财务页(templates/ledger/index.php + Ledger.php + chart.php)════
🔴 **「点击后没有任何效果」的根因(本轮定位)**:
图表卡那条类别 chip 的 JS 在找 `[data-ck-card]` 这个属性来显隐「每账目一张小卡」,
而 v24 第 11 批 #7 按用户要求把「每账目一张小卡」换成了**一张合并的多线图** ——
属性随之消失,**而这段 JS 没人删**。于是点标签:`.is-on` 会亮、画面上什么都不变。
典型的「看着有交互(按钮会变蓝)、实际没作用」:**JS 找的钩子已经不存在了**。
另外,概览卡里那些标签(存款/基金/保险…)原来是**纯展示的 `<span>`**,压根不可点 ——
用户说的"中间,有各类标签"多半也指它。本轮把两处**并成同一套筛选入口**。
一处 `applyKind()` 同时做四件事(缺一件用户就会觉得"没生效"):
① 两处标签条的高亮**同步**(它们是同一个筛选器的两个入口,只给一处绑事件必然状态不一致);
② 上面那张「总金额」图切到**该类别的曲线**(按类别**预渲染**好,靠 hidden 切换
—— 图是服务端画的 SVG,点了再请求就等于整页刷新,那就不是"点标签"是"点链接"了);
③ 下面那张多线图只留该类的线(`chart_multi` 已给每条线标了 `data-ck-line`)
+ 图例同步;
④ 下面账目表只列该类条目(`data-ck-row`)。
另外加一条概览高亮:「正在看『存款』:合计 ¥X · N 笔」+「清除筛选」。
服务端口径:
· `Ledger::timeline($ledgerId, $kind = '')` —— **在取时点之前**按类别过滤。
⚠️ 反面做法(先算整本再筛)会把「被筛掉那些账目的创建日」留在时点集合里,
图右端就凭空多出几个"其实什么都没变"的时点;
· `Ledger::summary()` 顺手给出 `by_kind_n`(每类**笔数**)——
用户要的「统计这个标签下的所有财务条目的总数」;
· `chart_multi` 给每条线包一个 `<g data-ck-line="kind">` —— **纯标注、不改几何**。
同一份数据多次渲染必须逐字节一致,所以属性值只能是 kind 本身,不许拼时间戳之类的。
· 三个总数字**不随筛选变**(用户已确认)。
════ 三、 本轮修掉的三个真问题(都不在"功能"层面)════
① 🔴 **覆盖式改 CSS 漏了子属性 → 四个按钮竖排、标题行被撑到 175px 高**
`.res-ops` 有一条**旧规则** `flex-direction: column`(那是给"展开区四个内联表单竖排"
写的)。我改完模板后只在文件末尾**追加**了 `display / gap / margin-left`,
**没写 `flex-direction`** → 四个按钮仍然竖着排。
真机几何实测:`ops.top=382.9` vs `title.top=445.6`(差 63px)、标题行高 175px。
⇒ **改一个类的语义时,旧规则要就地改/删,不要在后面叠一条覆盖** ——
叠加的写法天生容易漏子属性,而且**下一次改的人只会看到其中一处**。
这次把那条规则就地改掉,并删掉确已废弃的 `.res-size` / `.res-share-add`。
② 🔴 **静态断言被注释里的字面串撑着 → 恒绿(比恒红危险)**
部署断言原本是 `grep -qF 'class="res-ops"' 模板`。反证时我把**真代码**改坏,
断言**照样通过** —— 因为模板注释里(解释"为什么按钮组用 span"时)引用了同一个字面串。
**恒红会被发现,恒绿会一路伪装成"有覆盖"。**
⇒ 判据锚定到代码行(`grep -qE '^[[:space:]]+<span class="res-ops">'`),
并且注释里不再写完整字面串。
③ **`str.replace(old, new, 0)` 是「替换零次」** —— 写反证脚本时我给某例传 `0`
想表示"替换全部出现",实际什么都没改 → 报"断言没牙齿",白查一轮。全部替换是 `-1`。
════ 四、 验证 ════
- e2e **1171/0**(本批新增 55 条)· 真机浏览器探针 **45/0**(新增)
- 部署静态断言空跑 **309/0** · 桌面几何 428/0 · 移动端几何(360/375/390/414)684/0
- 老库结构升级 149/0
- **反向验证 18 例**(`_fb_b23_falsify.py`):按钮组拿掉 / 附件按钮摘掉蓝类名 /
弹框容器拿掉 / 写回「(没有正文)」/ 两处上传区 id 撞名 / 取消共享表单删掉 /
按钮组改用 div / 去掉 preventDefault / 删掉 [hidden] 规则 /
写回 flex-direction:column / 「清除筛选」不响应 / 线条不标类别 /
上图不预渲染 / 账目行不标类别 / timeline 去掉类别筛 / summary 不给笔数 /
部署断言两条 —— 逐条改回旧行为,点名断言都变红,还原后复绿。
真机探针验的是**行为与几何**(curl 级断言看不见的东西):
四按钮横排且贴标题行右端 · 点按钮**不会顺带展开**这一条 ·
弹框 ✕/Esc/遮罩 三种方式都关得掉 · 点标签后**上面那张图真的换了**、
下面多线图只剩该类 · 账目表跟着收窄 · 高亮条出现且「清除筛选」能回到全部 ·
375px 窄屏两页都不横向溢出。
════ 五、 新增/改动文件 ════
重写 `templates/resources/index.php`
`templates/ledger/index.php`(筛选条合并 + 预渲染各类曲线 + applyKind)
`app/Ledger.php`(timeline 加类别参数 + summary 补 by_kind_n)
`app/chart.php`(chart_multi 给线条标 data-ck-line)
`index.php`(ledger 路由多传 $kindLines)
`assets/css/app.css`(.res-ops 就地改 + 四色按钮 + 弹框外壳 + .led-focus + 窄屏)
|
||
|
|
6ceae11293 |
v24 第 22 批:名册加 CardDAV 同步 + 一把通用 DAV 口令(日历与通讯录共用)
用户 2 条原话:
①「名册,增加cardav同步功能。」
②「caldav和cardav,都增加一个创建口令的功能,这样,就使用用户名和口令来同步,
避免在第三方客户端进行同步时自己的密码泄露。」
用户拍板的两个岔路(AskUserQuestion):
· 口令范围 = **一把通用 DAV 口令**(日历 + 通讯录共用),不是两个 ——
手机上填一次就都通;两把会让用户要在两个页面各配一遍、还容易只配一半。
· 同步范围 = 与**名册页完全一致**(Person::roster(kind='member')),不另发明判据。
════ 一、 口令统一:caldav_credentials → dav_credentials(Schema v23)════
表**改名**(不是新建一张 carddav_credentials):
· 老站 RENAME → 已配好的口令一字不动,**所有设备不用重配**;
· 新装时两表都不在 → 跳过(直接 RENAME 一张不存在的表会抛异常、把升级打断);
· 两表并存(上次升级跑一半 / 有人手工建过)→ 搬行(INSERT IGNORE)+ DROP 旧表。
⚠️ 迁移**必须**是 RENAME + 存在性守卫(新增 Schema::hasTable),
只改建表语句里的名字的话,老库会**再建一张空表**、旧表留在原地没人读 →
用户所有已配好的同步设备立刻全部 401(而且改密码也修不好:新口令写进了新表)。
Auth:caldavIssue/Revoke/Auth/hasCalDav → davIssue/davRevoke/davAuth/hasDav,
新增 davLastUsed()(UI 上显示"最近使用")。
口令管理入口搬到**个人设置 → DAV 同步**(它管的是"我的凭据",同时服务日历与通讯录;
放在日历页会让只想同步通讯录的人在日程里找半天)。
· 旧路由 calendar/caldav_token|caldav_revoke 保留为 302 → 老书签不 404;
· 日历本页只留地址 + 一个指路按钮(密码类的东西只该有一个管理入口,
两处各放一套按钮迟早对不上:这页重置了、那页还显示"已配置")。
════ 二、 CardDAV(全新)════
· app/Vcard.php —— 名册 → vCard **3.0**(iOS/DAVx⁵ 推荐;4.0 在 iOS 上会被降级丢字段)。
FN 必须有(缺了 iOS 会**整张卡丢掉**)、UID 用 person id(稳定,不随改名变)、
标签能对上固定词就走 TYPE(手机上显示成"手机/工作"),对不上就原样放进
X-ABLabel(**不猜**成 HOME —— 猜错了用户看到"家"其实是"奶奶家")。
· app/CardDav.php —— books/rows/one/cardOf/etag/parsePath;ROOT='/carddav/'。
· carddav.php —— OPTIONS / PROPFIND(0,1) / GET / REPORT;**写方法一律 403**。
只读是与 CalDAV 同样的取舍:与其"半支持"(客户端以为改成功了、服务端却没落库),
不如明确拒绝,用户至少知道"手机上加的联系人不会同步回来"。
· **地址空间分离、鉴权同一套**:/carddav/ 与 /dav/ 各自独立(资源类型与命名空间
完全不同,混在一棵树里要靠猜客户端意图,猜错的表现是"连上了但一本都看不到");
但两者调**同一个** Auth::davAuth、读**同一张** dav_credentials 表 ——
这正是"一把口令管两种同步"的落点,也保证不会出现"日历能连、通讯录连不上"。
· 路由三处:router.php(本地 php -S)+ .htaccess(书面来源)+ 部署脚本 5c 段。
⚠️ 线上用**独立标记块** `# >>> FamilyBook CardDAV`:老站已经有
`# >>> FamilyBook CalDAV`,把新规则并进那一段会被"标记在不在"的幂等判断
**整段跳过** → /carddav/ 永远 404,而本地(走 router.php)完全正常。
· 打包白名单加 carddav.php(站点根下的独立入口最容易漏 —— dav.php / upload_stage.php
都漏过一次,症状都是"线上那个功能 404、本地一切正常")。
════ 三、 🔴 三个真 bug(全是"本地校验全绿"型,靠 vCard 往返测试抓出来)════
把 App\Vcard(导出)与 App\ContactImport(导入)各自单测都是绿的,
但**导出→导入跑一遍**立刻翻出三个:
① Vcard::fold **漏了"续行补前导空格"**(照抄 Ics::fold 时抄漏了最后一步)。
后果:折出来的那几段变成**独立的属性行** → 客户端把半个名字/半个地址当垃圾丢掉。
而"每行 ≤75 字节""确实折行了"全是绿的 —— 只有「**折行可无损还原**」这条断言能抓。
② ContactImport 用 `explode(';')` 给 ADR / N 分段 —— PHP 的 explode **不认识转义**,
值里写了 `\;`(字面分号)照样在那里拆开 → 地址「青岛市,市南区;某某路1号」被
从中间切断,后半截(含省名)跑到别的段上;导进来的人地址只剩前半句、
而且看着像"省没填"。手机通讯录里带分号的地址("XX路1号;3单元")不算罕见。
→ 新增 `splitVcardSegments()`:**按未转义的分号**切,切完再逐段 unescape。
⚠️ "先 unescape 再分段"是同一个错的另一种写法(本轮第一版就这么改,往返照样红)。
③ Vcard::adrField 的**分号数量对、但位置错一位**(省落到了"邮编"格)。
第一版 `;;` + 街道 + `;;;` + 省 + `;` —— 总数也是 6,可多出来的那个挤在前面 →
解析器读段 4 拿到空串。**段数对、总数对、只有位置错一位**,
所以"数分号个数"的检查发现不了它。
════ 四、 顺带修的一个 ETag 一致性 bug ════
同一张卡在 PROPFIND(清单)与 REPORT(取正文)里原本算出**两个不同的 ETag**
(一个用不带结尾 CRLF 的正文、一个用带 CRLF 的)→ 客户端认为这张卡一直在变、
**反复重拉**(同步永远不收敛),而服务端看不出任何异常(两边都合法)。
→ 新增 `CardDav::cardOf()`,把**正文与 ETag 成对返回**,结尾 CRLF 只加在正文上、
不参与 ETag。修法不是"两处改成一样",而是**让它们没有机会不一样**。
════ 五、 UI ════
· 个人设置页「DAV 同步」卡:两个地址(/dav/ 与 /carddav/)+ 用户名预填 + 一次性明文
(只显示这一次,走一次性会话槽位)+ 生成/重置/撤销 + "最近使用"。
· 名册页 CardDAV 卡:地址 + "这 N 个人"(人数与名册页一致,用户当场能对上)+
指路口令的按钮。**不放**生成表单。
· 日历本页改口径:只留 /dav/ 地址 + 指路链接。
════ 六、 验证 ════
- e2e **1106/0**(本批新增约 45 条:CardDAV 全协议、越权 404、只读 403、
一把口令开两把锁、撤销后两处**同时**失效、口令一次性/不落库、ETag 一致性)
- **vCard 规格体检 36/36**(新工具 `_fb_b22_vcf_spec.php`:转义/折行/多值/标签/
空名兜底 + **导出→导入往返一致**)
- **真机探针 28/0**(新工具 `_fb_b22_ui.py`:两块 UI 渲染、**在浏览器里点按钮**生成
口令、刷新后明文消失但状态留下、名册页人数对得上、日历本页改口径、
撤销后两处 401、全程零 JS 报错)
- 桌面几何 428/0 · 移动端几何(360/375/390/414)684/0 · 结构升级 149/0
- 部署静态断言空跑 **307/0** · 线上部署 134 项自检 0 失败
- **反向验证 14/14**(`_fb_b22_falsify.py`):迁移不干活 / 读旧表 / 断入口 /
去掉 addressbook 声明 / 只读守卫失效 / 不校验口令 / 越权不再查名册 /
折行不补空格 / 分段改回 explode / ADR 错位 / UI 卡拿掉 / 明文不再一次性 /
撤销不删凭据 —— 逐条改回旧行为,对应断言都变红,还原后复绿。
════ 七、 🔴🔴 线上实测挖出的**最重要**的一条:DAV 鉴权在 Apache 上一直是坏的 ════
部署后在线上以 **Web 用户(www)** 身份做端到端验收时,DAV 全部 401 ——
而本地 e2e 全绿。追下去发现两层原因:
① **测试假象**:我把 DAV 请求放进了一个**临时探针文件**里,用 `curl` 打它。
但 Apache 请求 `.php` 文件时**不套用站点根 .htaccess 的 URL 重写** ——
所以探针里的请求根本没被转给 dav.php/carddav.php,拿到的是**探针自己的 401**,
不是 DAV 的。第一轮"全 401"有一半是探针自己造的假象。
② **真问题**:Apache 默认**不把 `Authorization` 头交给 PHP-FPM** ——
`$_SERVER['PHP_AUTH_USER']` 与 `HTTP_AUTHORIZATION` **全是 null**。
实测(这台服务器 Apache 2.4.53,直接监听 80/443,**没有 nginx**):
基线 : {"PHP_AUTH_USER":null,"HTTP_AUTHORIZATION":null}
`CGIPassAuth On`: {"PHP_AUTH_USER":"abc","HTTP_AUTHORIZATION":"(有)"}
⇒ **线上 CalDAV 从来没被真实客户端连上过**(服务端代码是对的、环境少了这一行)。
本轮 e2e 用 `php -S`,那里 `PHP_AUTH_USER` 是**自己填好的**,所以一路全绿 ——
又一个"本地好好的、线上不行"。
修法:部署脚本 5c 段追加**独立标记块** `# >>> FamilyBook DAV auth`,内容
`CGIPassAuth On`(Apache **2.4.13+ 的核心指令**,不需要额外模块、**不用动宝塔面板**)。
仓库 `.htaccess` 同步写上(它是那段规则唯一的书面来源)。
修完后线上端到端**全部通过**(以 www 身份、真实 HTTP 服务、真实数据):
· CalDAV:`PROPFIND → 207`、**8 本日历本**、`PUT → 403`
· CardDAV:根 `207` + `<card:addressbook/>`;清单 `207`、**1089 张卡**、1089 个 ETag;
整本 vcf `200`、1089 张、全是 3.0、`text/vcard`;**最长行 75 字节**(真实数据上
折行真的发生了);`REPORT → 207` 带 `address-data`;`PUT → 403`;
`OPTIONS → 200 DAV: 1, 2, 3, addressbook`
· 导出的卡数 **1089 == 名册人数 1089**(口径一致)
· 口令错 → `401`
临时探针用完即删并复验 404;临时插入的口令用完即清(不动用户已配的凭据)。
════ 八、 关于 CalDAV「本来是好的」这件事 ════
严格说:**服务端实现一直是对的**,缺的是 Apache 那一行 `CGIPassAuth`。
也就是说这个仓库里的 CalDAV 之前**上线即不可用**(只是没人用真实客户端试过)。
本批顺手把它一起修好了 —— 现在 /dav/ 与 /carddav/ 都能被 iOS / DAVx⁵ 正常连上。
|
||
|
|
41572b77e7 |
第 21 批收尾:按需补「我的日程」本(老库也能拿到)
线上实测发现第一版没覆盖到的情况:把 'custom' => 「我的日程」加进
`Calendar::DEFAULT_KINDS` **是不够的** —— `seedDefaults()` 只在
**安装**与**结构升级**两条路径上被调用,而线上这套库早就升完级了,
于是老用户永远拿不到那一本。线上实测:owner=1 可写的普通本 0 本
(唯一那本 custom「工作」是用户自己建的)。
后果不只是"少一本":新建日程的"放在哪本"里全是派生本、默认还选中排最前的
**观影** → 用户直接打字点「加上」,建出来的是一条**观影记录**(建错东西,
事后极难发现:日历里看着是多了条日程,观影列表里莫名多一条)。
修法:
- 新增 `Calendar::ensurePersonalBook()` —— 按需补,幂等判据是
`owner_id + kind='custom'`(**刻意不看名字**,否则用户把自己的本子改名之后
会又冒出一本「我的日程」)。
- 由 `calendar` 路由在打开日历时顺手调一次(一次请求只多一条 SELECT)。
- 模板里再兜一道:默认选中的不是可写普通本时显式提示
"你还没有自己的日程本,上面选的是一本派生本(加进去会变成观影/旅程记录)"。
验证:e2e **1064/0**(新增 5 条:删本 → 打开日历自己补回来 → 名字对 → 幂等 →
改名后也不会又冒一本);线上以 Web 用户(www)身份实测 before=1 after=1
(正确幂等,不动用户自己那本);真机探针 75/0 · 桌面几何 428/0 · 部署自检 0 失败。
⚠️ 测试侧顺带记一个坑:夹具删"有日程的本子"时 `calendar_events.calendar_id`
外键是 **RESTRICT**,而用**多语句** `mysql -e "SET FOREIGN_KEY_CHECKS=0; DELETE...;"`
时一旦有一句被挡下,**整个 -e 静默失败**(返回码 0、无 stderr),
症状是夹具自检报"还有 2 本"、后面断言一路红,单独手跑同样语句却是通的。
改成**一条一条发并检查返回码**后就好了。
|
||
|
|
f0437e6c93 |
v24 第 21 批:无刷新删除与新建日程 / 实际行程不回退 / 行程按时间排 / 统一上传框 / 时间范围控件修复(用户 8 条)
用户 8 条原话要点:
①「名册页面,删除一个联系人后,能否不刷新页面,删除成功后,直接把这个人,
从当前页面的 dom 中删除,页面保持不动。」
②「观影模块,海报和票根以及照片都带进来了,但是没有缩放,把页面撑破了,
应该缩放成缩略图。」
③「个人设置页面,天气设置那里,把:只影响你自己首页上的那一格 / 填个城市名就行
(如「青岛」)… 这些提示,都删除。把城市单独放一行,精度维度放在一行。」
④「旅程模块,实际行程编辑后,点击保存,竟然又变成计划行程了,这个不合理,
实际行程永远是实际行程,不会再变回计划行程了。」
⑤「旅程模块,旅程列表,行程列表,都是按照行程的开始时间排序的,最新的在最上面,
而不是按照编辑修改时间。」
⑥「旅程的详情页面,右上角的按钮,设置在最右侧,和其他模块保持统一。」
⑦「所有上传图片等附件的地方,都不再放按钮,而是放一个框,点击这个框实现点击上传
按钮的功能,拖动图片进这个框里,实现拖动多个上传功能。框里面写一句话:
点击或拖动进行上传,其他的功能和现在保持一致。」
⑧「日历中新建日程,无论是点击按钮还是点击日历中间,都是采用弹框的形式进行添加,
这样不影响当前日历的显示,添加完成后,动态修改当前视图,不要刷新。添加日历日程,
开始时间和结束时间那里,用我们统一的时间范围选择器重做,现在功能无法使用。
选择日历本,带上颜色。」
🔴 五个真根因(全都不像 bug):
① 🔴 **④「实际行程变回计划」= 表单字段同名合并**(本批最要紧的一条)
暂存上传的令牌字段原叫 `stage[]`,与行程表单里那个隐藏的 `stage`(plan/actual)
**同名**。PHP 解析表单时同名会**合并进同一个键**:
`stage=actual` + `stage[]=tok` → `$_POST['stage'] = ['actual','tok']`
(`[]` 只是"往同一个键追加"的记号,**不构成另一个键名**。)
于是路由里 `(string)($_POST['stage'] ?? 'plan')` 得到 `"Array"` →
`in_array('Array', ['plan','actual'])` 为假 → **静默回落成 'plan'**。
⚠️ 只有"拖过图片的行程"会犯(没拖就没有 `stage[]`)→ 症状是"时好时坏"。
修法:令牌字段改名成 `stage_token[]`(改的是**字段名本身**,不是"是数组就取第一个"
绕过去 —— 同名合并是 PHP 的表单语义,绕过去只会把坑留给下一次加字段)。
**另加一道语义保险**:编辑路径**一律沿用库里那条自己的 stage**,不读表单 ——
编辑表单里根本没有改阶段的控件(改阶段走详情页那几个显式动作)。
这样即便将来又冒出某个字段名冲突,也不可能把「实际」悄悄变回「计划」。
② 🔴 **⑧「时间范围选择器无法使用」= 三道门叠在一起**
① `_event_fields.php` 的**结束端默认带 `hidden`**(要点「+ 添加结束时间」才出现);
② `app.js` 的 `upgradeRange()` 有「**两端都得可见**才升级」的判断 → 永远不成立
(实测 `data-semi-range-done` 恒为 `null`);
③ CSS `html.js .dt-range:not(.is-semi-range) .dt-input { visibility: hidden }` →
未升级时原生控件**也是不可见的** ⇒ 屏幕上一片空白、点哪儿都没反应。
修法:两端**始终可见**("没有结束"由清空后半段表达,不再靠开关);
并把旧的「+ 添加结束时间 / 去掉结束时间」开关整套删掉。
⚠️ 顺带挖出:`upgradeRange()` 里 `group.insertBefore(host, a)` 在"两端各自被
`.field` 包着"时 `a` 不是 `group` 的子节点 → 直接抛
`Failed to execute 'insertBefore' on 'Node'`。这个异常**以前从没触发过**
(被 ② 的提前 return 挡住了)—— 一旦放出来就炸。修成插在"端点最外层包装"之前。
⚠️ 观察器加了 `attributes: true, attributeFilter: ['hidden']`(并把 childList
分支的 `return` 去掉):弹框/折叠面板/tab 里"稍后才露出来"的日期控件从此都能自动升级。
③ 🔴 **⑧「新建日程默认建出观影记录」= 产品缺口,不只是显示问题**
`Calendar::DEFAULT_KINDS` 原来只有 6 本**派生**本(观影/旅行/农历/节假日/传统节日/
节气),**一本可写的普通本都没有**。于是"放在哪本"里全是派生本、默认选中排最前的
**观影** → 用户直接打字点「加上」,建出来的是一条**观影记录**
(日历里看着是多了条日程,观影列表里莫名多一条)。这是"建错东西",事后极难发现。
→ ① 默认本补成「我的日程」(kind='custom'),模板里默认选中也改成**优先 custom 本**;
② 选择器里把**纯派生只读本**禁用并标「(自动生成)」
(直接不列会让人以为本子丢了;列为可选又会白选一次再吃错误)。
⚠️ 顺带发现 `Calendar::READONLY_KINDS` 把 `movie`/`trip` 也算进去了,而
`event_add` 里**紧跟着**就有两段专门处理它们的分支(「已加进观影」/「已加进旅程」)
—— 那两段**永远走不到**(校验先一步把请求打回去了)。判据要按**能不能写**分,
不是按"是不是派生"。
④ 🔴 **②「观影编辑页图片撑破」= `.mv-slot` 这个类在样式表里根本不存在**
第 21 批加模板时只写了结构没写样式 → `<img>` 按**原始像素**渲染:600×900 的海报
在 236px 宽的格子里撑出 942px 高,还把整页横向顶破(实测 docW 1297 > vw 1280)。
→ 补 `.mv-slot` 规格:固定 108×162 缩略图 + `object-fit: cover`。
⚠️ **高度必须写死** —— 不写死就又会由原图比例决定,等于没修。
⑤ **⑤行程列表排序 = `category, position` 主导**
`position` 默认同一个数(旧迁移给的多是 0/100),于是顺序被 position 挤乱。
实测线上 #222 渲染成「返回青岛(10-5) → 出发去菏泽(9-30) → 去姥姥家(10-2)」。
→ 改成 `ORDER BY (starts_at IS NULL), starts_at, id`。
⚠️ 方向是**正序**(早的在前,已跟用户确认);旅程列表仍是 DESC(最新在最上)。
两者方向不同是**刻意的**:一个是"挑哪趟旅行",一个是"读这趟旅行的行程"。
⚠️ 详情页**按阶段分三组**渲染(计划/实际/未成行),排序只需在**组内**有序。
本批新增能力:
- **统一上传框**(⑦):`templates/_stage_drop.php` 现在**同时**负责
「sr-only 的真文件框 + 看得见的上传框 + 暂存清单」,调用方只 require 一行。
框是 `role="button" tabindex="0"`,JS 转发 `input.click()`(键盘 Enter/Space 也通),
拖文件进框走既有的暂存上传(拖一个立刻传、可续拖、失败标红、未传完拦住保存)。
⚠️ 框用 `<div>` 不是 `<button>`:里面要放暂存清单(含「撤掉这张」按钮),
按钮里嵌按钮是非法 HTML(第 21 批刚在 `<form>` 嵌套上栽过同一个跟头)。
⚠️ 文件框用 `.sr-only` 不是 `display:none`:后者不可聚焦,带 `required` 时
Chrome 会报 "An invalid form control is not focusable" —— 点了什么都不发生。
落了 11 处:行程照片 / 观影海报·票根·合影 / 资源 / 任务 / 日程附件 / 书籍 /
联系人导入 / ics 导入 / 观影补传 / 资源补传。
⚠️ 清单与提示行移到框**外面**(放框内会变成"一个大框套五张图"),
所以 app.js 改从 `.upfield` 容器里找它们。
- **无刷新删除(①)与无刷新新建日程(⑧)**:路由按 `X-Requested-With`
(或 `Accept: application/json`)区分调用者 —— 带它回 JSON、不带照旧 302
(无 JS 的兜底**不变**,两边的反向都写了断言)。
· 名册删除:成功 → 摘卡片 + 就地改「共 N 人」与分组人数 + 派发 `input` 让
filter.js 自己重算(**不抄它的判据**);失败 → 把原因说出来(不刷页);
拿不到 JSON → **退回原生提交**(绝不静默失败)。
· 日历新建:JSON 带齐前端插入当前视图要用的字段(时间/颜色/链接/是否全天/cal)。
月视图**按时间插入**(不能 append —— 服务端只渲染前 3 条、按时间序取,
append 会让"新加的早间日程"掉进「+N 条」里看不见,刷新后又跑到第一位);
周/日视图按"24h = 100%"算 top/height 插块。
· 观影 / 旅行本是**可写派生本**:AJAX 时不再跳详情页(跳走就离开了日历),
改在提示行给一个「去看详情 →」链接。
- **⑧日历本带颜色**:原生 `<option>` 画不出色块(浏览器不允许在 option 里放元素),
所以做成「色块 + 下拉」并排,色块跟着当前选中的本子变色。
- **③天气卡片**:三句提示全删;城市单独一行、纬度/经度同一行(`.loc-row`)。
删的只是说明文字,三个输入框 + 保存 + 状态回显都保留。
- **⑥设置按钮贴最右**:原来顺序是「… → 设置 ☰ → 删除 🗑」,设置被删除键挤到**倒数第二**;
改成删除在前、设置恒为最后一个(与观影/日程/任务/资源同一个手势)。
验证(本地全套):
- e2e **1059/0**(本批新增约 40 条断言)· 结构升级 149/0 · 桌面几何 428/0
- **真机探针 75/0**(`_fb_b21_ui.py`):AJAX 删除真的没导航 / 卡片真被摘掉 /
滚动位置保持;缩略图几何(108×162,不再溢出);上传框(框在、原生框不可见、
点框真打开文件选择框、连拖两张都上去且清单可见);日历弹框(打开时网格一动不动、
范围控件真的挂上且可见、能弹面板、色块跟着换、无刷新提交后当前视图里立刻出现、
周视图也能插时间块、全程无 JS 报错)。
- **反向验证 12 条**(`_fb_b21_falsify.py`):逐条把修复改回旧行为看断言是否变红 ——
令牌字段改回 stage[] / 编辑读表单 stage / 排序改回 position / 设置按钮挪走 /
框不转发点击 / 文件框不再 sr-only / 删缩略图规格 / 删除不回 JSON /
结束端改回 hidden / 日历本默认改回第一个 / 天气提示加回 / 弹框改回内联。
- 部署脚本静态断言段本地空跑 **307/0**。
|
||
|
|
c3d00ad66a |
v24 第 20 批:拖动即时上传 / 观影图片带出 / 行程保存修复 / 日程红线与删除范围与多提醒 / 悬停统一(用户 9 条)
用户 9 条原话要点:
①「观影模块,编辑观影,已经保存的海报和票根、合影,都没带进来。」
②「个人设置页面,"只影响你自己看到的样子"这个提示删除,不要了。我怎么称呼家人,
这个卡片,不要了。我的资料这个卡片,不要了。」
③「旅程模块,编辑行程,点击保存没有反应。」
④「所有上传图片视频和附件的模块,都允许多次拖动添加文件上传,拖动一个进去,
就开始上传,还可以继续拖动,继续上传,最后统一保存即可。」
⑤「添加行程,线路名、承运方,都不要了。车辆,只在出行方式选择自驾时才出现,
其他情况下默认不出现。出发地和到达地,应该在同一行。」
⑥「日程模块,周视图和日视图,应该在视图中间显示一条红线,用来表示当前进行的时间…
用一个不断从上到下的红线,来表示当前时间在视图上的刻度和位置。」
⑦「对于重复的日程,当点击删除的时候,应该提示是删除这一次,还是全部都删除,
还是删除这一次及以后的。」
⑧「日程的提醒,是可以多选的,就是说,一个日程,我可以选择让它 5 分钟提醒,
10 分钟提醒等等。」
⑨「很多按钮的悬停效果非常奇怪,感觉纯粹是换了一个背景色…应该调整所有按钮的悬停效果,
保持统一,但是不要单纯的换一个不协调的背景色。」
🔴 三个真根因(都是"看着不像 bug"的地方):
1. **③ 点保存没反应 = `<form>` 嵌套**(HTML 非法):`trips/node_form.php` 的照片缩略图上
套了 `<form action=photo_del>`,而整块在**外层行程表单内部**。浏览器解析到内层
`</form>` 时**把外层表单提前关掉** → 「保存」按钮**掉出表单**(真机实测
`button.form === null`)。源码里完全看不出,**只有真浏览器量 `btn.form` 才现形**;
且**只有这段行程已经有照片时才触发** —— 所以表现为"有时候好、有时候坏"。
→ 按钮留在原位(布局不动),用 HTML5 `form="id"` 关联到**表单外**的独立表单。
2. **⑤ 车辆藏不住(第 17 批就写过、一直没生效)**:`.field` 自己声明 `display:flex`,
作者样式压过浏览器默认的 `[hidden]{display:none}` → `hidden` 形同虚设。
**本项目第 4 次栽同一个坑**(.att-item / .pv / .pp-item 各一次)→ 补 `.field[hidden]`。
3. **④ 拖动即时上传的卡片"看不见"**:模板给 `.stage-list` 加了 `hidden`(没人上传时
不留空边框),JS 建卡片时忘了 `list.hidden = false` → 卡片在 DOM 里但整条清单
`display:none`、尺寸 0×0。**HTTP 层断言全绿**(HTML 里确实有卡片),
**只有真机量尺寸才发现** —— 由新写的真机探针抓到。
本批新增能力:
- **拖动即时上传**(第 4 条):新增 `upload_stage.php` + `app/StagedUpload.php` +
`templates/_stage_drop.php`。拖进来 → **立刻** POST 到暂存目录 → 回 `stoken` →
放进隐藏字段 `stage[]` → 保存时在 `attach_save_files()` 里**认领**(移文件 + 建附件行)。
`media.php` 加 `?stage=` 预览(拖完立刻看得见缩略图)。
⚠️ 三个必须踩准:**串行队列**(并发会让缩略图顺序乱跳)、**提交守卫**
(还有没传完就 preventDefault,否则"拖了但没保存上"必然发生)、**卡片清单要 un-hide**。
⚠️ 海报/票根走**独立字段名** `stage_poster`/`stage_ticket`(各自单槽位、要按比例重裁)。
⚠️ 落点**就近取**(`input.closest('.field')`):一个表单里多个上传区不能串到第一个去。
- **观影编辑带出已存图片**(第 1 条):编辑页显示海报 / 票根 / 合影 + 各自的删除入口;
新增 `movies/media_del`(只删不换也能删,槽位走白名单);`movies/update` 收编辑页
新选的照片时**合并**而不是替换(否则一编辑就把已有合影冲掉)。
- **行程表单**(第 5 条):去掉线路名 / 承运方(**库里的值保留**:UPDATE 时不参与,
与 seat_info/gate 同一套);出发地/到达地包进 `.loc-row` 真同一行;车辆按自驾显隐。
- **当前时间红线**(第 6 条):位置在服务端算(与 `.tg-block` 同一套"24h = 100%"坐标),
前端每分钟自己挪(对齐到下一分钟整再起定时器),跨午夜 reload;只画在**今天那一列**。
- **重复日程删除三档**(第 7 条):与「编辑」那三档同名同序(全部 / 这一次及以后 /
只这一次)。「只删这一次」→ `skipOnce`(**绝不删整条系列**);「这一次及以后」→
老系列截 UNTIL,**必须清掉 COUNT**(两者并存时 COUNT 优先,不清就截不断)。
- **提醒多选**(第 8 条):同列存逗号串(`5,15,60`);Schema 把它从 **INT 改 VARCHAR**
(INT 存逗号串会被隐式转换,多档会静默只剩第一档);ICS 导出吐**多个 VALARM**。
- **悬停统一**(第 9 条):原来 14 种互不相干的 hover 底色 → 统一成**两套手法**:
有底色 `filter: brightness(.92)` 同色系加深 + 轻抬升;描边白底 `--hov-veil` 中性薄雾
+ 边框微显。放在 CSS **最末**用一条兜底收口(新按钮自动继承),并把十几条旧规则
清成"交给统一段"。`person_picker.js` 同时改为按需加载。
验证(本地全套):
- e2e **1004/0**(+21 条本批断言)· 升级 149/0 · 桌面几何 428/0 ·
**真机探针 25/0**(连续拖两次 / 红线位置与颜色与几何 / 车辆显隐 / 出发到达同行)。
- **反向验证 5/5**:①删除按钮改回嵌套 form ②卡片清单不 un-hide ③观影编辑不渲染海报
④车辆隐藏失效 ⑤「只删这一次」做成删全部 —— 五种坏法全部被抓住,还原后复绿 1004/0。
过程中还**抓到两条弱断言**(只查 DOM 有无、不查可见性/是否真渲染),已一并加强。
- 部署脚本静态断言段本地空跑 **305 条全过**。
|
||
|
|
1756c6265d |
v24 第 19 批:拖动上传 / 返还按钮 / TripLog 补迁 / 参与人回填 / 预览翻页 / 行程按钮(用户 8 条)
用户 8 条原话要点:
①「为所有的图片视频附件上传处,提供拖动上传的功能」
②「旅程详情页面,增加返回按钮,点击后返回到旅程列表」
③「已有行程的出行方式,包括航班号,怎么没从 triplog 系统迁移过来?」
④「题图右侧有一句话显示,但点编辑进去却看不到这句话」
⑤「编辑行程,参与的人没有带进来,还是空的。同样的问题也出在观影编辑时」
⑥「预览的时候左右有箭头可以直接看下一张;md5 的文件名放在预览图下面没有意义,去掉;
如果是文件,则在图标上显示文件名」
⑦「计划行程的修改/完成/未成行横着排列放行程右上角,修改黄、完成绿、未成行红;
实际行程的编辑删除也放右上角,编辑黄、删除红」
⑧「选人弹框暂存区太小了看不见人;前面的复选框没用,去掉」
🔴 抓到的两个真根因(都不是"没写对",是两处看似无关的地方):
1. **⑤ 参与人不回填 = 两个独立原因叠加**
(a) `Trip::node()`(单条)只 `SELECT *`,**不带 members/attachs**;而
`Trip::nodes()`(列表)是带的。编辑页走前者 → `$ppSelected=[]` 永远空。
同一处还让编辑页的照片墙永远不显示。→ 单条也挂 members/attachs/花销。
(b) `person_picker.js` 的 `init()` 末尾**无条件** `refresh()` → `syncChips()`
第一句 `chips.innerHTML=''` 再从**懒建**的候选清单重建 —— 首屏清单还没建,
一个 input 都没有 → **服务端渲染的 chips 全被清掉、补不回来**。
症状极隐蔽:curl 看到的 HTML 里有 3 个 chip,浏览器里数出来是 0。
→ `syncChips` 加 `if (!built) return;` 守卫 + `ppSetSelected` 前先 `box.ppBuild()`。
2. **④ 那句话 = 详情页读了两个字段**:`display:summary` + `events.note`(老字段,
表单里早没有输入框)→ 老数据的 note 在详情页冒出来、却无从修改。
→ 详情页只渲 summary;老数据给一句"点这里补简介"的提示;
由 `_fb_b19_backfill.php` 一次性搬进 summary。
本批新增能力:
- **拖动上传**(第 1 条):app.js 自动扫全页 `input[type=file]` 挂 DnD,**不改任何模板**
→ 全站十几处上传点一次到位;`data-pick-input` 指向的**预览图**也当落点
(题图/头像的 input 是 sr-only,往图上拖才自然)。三个必须踩准的点:
`dragover` 必须 preventDefault;`dt.items.add` 真写回 `input.files`;
写回后**手动派发 `change`**(`input.files=` 不会自己触发,预览/裁剪器都听 change)。
MutationObserver 补挂动态出现的框。
- **预览左右翻页**(第 6 条):候选 = 同页全部 `[data-prev]`(文档顺序),
`‹ ›` 按钮 + 键盘 ←→(**只在图片上**接管方向键,视频/PDF 里方向键另有含义);
只有一个附件时箭头隐藏(点了没反应比没有更糟);右上角带"第几张/共几张"。
- **附件墙**(第 6 条):缩略图下**不再写**(哈希)文件名;文件的名字压到**图标里**;
`att-doc-name` 两行封顶 + 省略号(长名不撑变形)。
- **行程按钮**(第 7 条):`.node-ops` 由 `column` 改 `row` + 贴顶;新增
`.btn-warn/.btn-ok/.btn-del`(**实心**黄/绿/红)—— 与全站淡红描边的 `.btn-danger`
刻意分开(改它会连带改掉十来处「删除」的样子)。「编辑」保留(改标题/加花销的唯一入口)。
- **选人弹框**(第 8 条):暂存区 148px 死高度 → `min-height:132px; max-height:46vh`,
chip 头像 20→28px;多选加 `.pp-compact` 隐藏复选框(**只 `display:none`,
input 仍在 DOM** —— 提交语义全靠它,绝不能 remove)。
上线补迁(`_tooling/_fb_b19_backfill.php`,幂等、不覆盖已有值):
- ③ TripLog 的 `travel_modes.mode_type` + `trip_number/line_name/carrier`
→ FamilyBook 的专列。`drive_own/drive_rental` 都映射成 `drive`(FamilyBook 的
"自驾"是一个方式,"哪台车"由 vehicle_id 表达);车辆按**车牌**对上;
顺手删掉 detail 里那段重复的「班次:…」。线上实跑 18 条、跳过 2 条。
- ④ `events.note` → `trip_details.summary`(只搬 summary 为空、note 有内容的,2 条)。
验证(本地全套 + 线上):
- e2e **960/0**(+23 条本批断言,含"成员清空后编辑页也不显示"的反向)· 升级 149/0 ·
城市解析 32/32 · 桌面几何 428/0 · 真机 UI **27/0**(拖动/翻页/按钮几何与配色)·
真机选人 chip **2/0**。
- **反向验证 4/4**:①`Trip::node` 去掉 members ②`syncChips` 去掉守卫
③详情页把 note 渲回来 ④附件墙把文件名写回去 —— 四种坏法全部被抓到,
且还原后 e2e 复绿 960/0。
- 线上:部署成功(双版本 l 通过 + 19 条站点自检全过);补迁后 18 段行程有出行方式、
7 段有车次、10 段挂车、2 个旅程有简介;线上 `Trip::node()` 实调
members/attachs 都回来了(部署前是 0)。
|
||
|
|
a2b447bee4 |
v24 第 18 批:选人弹框改「暂存 + 确定」两段式(用户第 18 条)
用户原话:「在弹出框里输入一个人的首字母…筛选出这个人,点击这个人的头像或者是名字,
把这个人的信息**暂存到这个选择框的上半部分**。这时候他可以继续在输入框里面输入另外
一个人的拼音首字母,等所有人都选完之后,**点击确定按钮**完成多选然后再返回原来的界面儿,
把他选中的多个人都放入到之前的页面儿里。」
改动:
- templates/_person_picker.php:多选弹框加**暂存区**(搜索框之下的"上半部分")
与底部「取消 / 确定」;这两块**只在 `$ppMode='multi'` 时渲染**。
- assets/js/person_picker.js:
· 点候选人(**click**,不是 change —— 用户点的是"头像或名字")→ 进暂存区、
弹窗**不关**、输入框清空并重新聚焦("继续输入另一个人的首字母");
· 再点同一个人 = 从暂存区撤掉;暂存区里每个 chip 也带 ×;
· 「确定」= 关弹窗(勾选本来就在 input 上,等于已生效)+ 重画外面 chips;
· 「取消」= 按打开时的快照还原;
· **没点确定就关**(× / Esc / 点遮罩)→ 问一句"要保留吗",**绝不静默丢**;
· 单选保持旧行为(点一个即选中并关闭)。
- assets/css/app.css:.pp-stage / .pp-actions 等(含 `[hidden]` 规则 —— 本项目老坑)。
🔴 两个设计取舍(都写在代码注释里):
① **暂存状态就用 `input.checked` 表达,不另存一份数组**。字段的提交语义只由
`input.checked` 决定(表单直接提交它们),另存一份就必然要"确定时写回 input"——
漏做就是"我选了、保存后没人"(第 11 批栽过同类)。所以确定 = 关弹窗。
② **单选完全不动**:它的字段名(person_id / owner_id)在后端是**一个值**,
多选会直接改坏数据。模板里包在 `if ($ppIsMulti)` 里,e2e 断言钉住"单选不许有"。
🔴 真机探针(新套件 _fb_b18_picker.py)抓到两个 jsdom 测不出的真 bug:
① **单选点"已选中的那个人"→ 浏览器不触发 change**(值没变)→ 弹窗一直开着
→ 用户以为"点了没反应"。补一刀 `if (!stagedMode && box.checked) closeModal(true)`,
并在 jsdom 里加了同口径回归断言(runSingle())。
② 人数显示按 id 当 class 查(`pp-stage-n`)→ 取到 null → 永远显示 0(chip 却正常)。
验证:
- e2e 937/0(+17 条本批断言,含单选回归)· 升级 149/0 · 城市解析 32/32 ·
桌面几何 428/0 · 移动端 688/0 · 交互 92/0 · 窄屏溢出 24/0 ·
**选人弹框真机 10/10**(新套件,含"暂存区几何上在搜索框之下、清单之上")·
题图真机 4/4。
- 反向验证 3/3:退回"点一个关一次" / 去掉关闭时的询问 / 给单选也渲染暂存区
—— 三种坏法全部被断言抓住。
|
||
|
|
c30ca99d22 |
v24 第 17 批:城市聚合 / 高德地址 / 表单精简(用户 10 条)
用户 10 条原话要点与落地:
- ①「统计出来的不是目的地,也不是车站,而是城市…城市要聚合,有多段行程,
都有青岛作为出发地或者目的地,那么就只记一次」
→ trip_nodes 补 from_city/to_city(Schema v22),Trip::cities() 改成
「出发地 ∪ 到达地」去重、按最早时间 + 出发地优先排。
详情页信息区显示的就是这个聚合结果(第 4 条:「图片右侧显示的是聚合的城市,
而不是终点地址」)。
- ②「类型不够丰富,你看看 triplog 里面都有哪些,都拿过来」
→ NodeType::TRIPLOG_TYPES(休息/突发/购物/拜访亲友),在 all() 里读时自愈补齐。
- ③「出行方式,只有选择自驾,才让选车辆;车辆下拉框不需要出现什么财务里登记的车,
只把车辆列出来就行了」→ DRIVE_MODES 收窄成 ['drive'];
Vehicle::pickable() 只回车辆库;车辆页删掉「财务里已登记的车」那块。
- ③「飞机、铁路,只记录车次、航班号就可以…座位什么的不记录了」
→ seat_info / gate 从表单与列表**彻底移除**(列保留,老数据不丢;
saveNode 的 UPDATE 也不再写这两列,否则会抹掉迁来的数据)。
- ③「出发地和到达地,都用高德地图的接口,记录详细地址」+「参与人弹框选择」
+「给参与人标注角色这个功能去掉」
→ 新增 templates/trips/_loc_field.php(高德联想 + 详细地址 + 坐标 + 城市 +
导航链接,没配 key 也能手输);参与人改用全局弹框选人组件;
角色输入框删除,saveNodeMembers 改成差集增删(否则老 role_tag 被静默抹掉)。
- ④「triplog 中的行程花销数据,没有同步过来,需要迁移过来」
→ 新增 _tooling/_fb_b17_backfill.php:迁地址(20 条)、花销(4 笔 ¥8584)、
车辆(3 台)。线上已执行并核对(笔数与金额一分不差)。
- ④「行程中,时间明明具体到了时分秒,怎么不显示?」
→ Trip::nodeSpan():行程时间精确到时分(同一天只写一次日期、跨天写月日、
两头都没时分就不写 00:00)。
其他:
- App\Geo 扩展 cityFromPoint / cityFromText / navUrl / jsConfig。
中文地址提城市修掉四种错法(白名单→黑名单、取最后→取第一个、直辖市最先认、
离线表只能兜底),并加了「五大连池 ≠ 大连」的位置守卫。
- config.example.php 加 amap 段(key / security_js / nav_src)。
验证:
- e2e 920/0(+29 条本批断言)· 升级 149/0(+15 条 v22 断言)·
城市解析 32/32(新套件,样例全是 TripLog 线上真实地址)·
桌面几何 428/0 · 移动端 688/0 · 交互 92/0 · 窄屏溢出 24/0。
- 反向验证 5/5:城市退回读 place / 只算到达地 / 时间退回只到日期 /
打车也要车 / 类型不补 —— 五种坏法全部被断言抓住。
- 部署:服务器自检 19/19;线上 schema v22;补迁后花销 4 笔 ¥8584 与源库对齐。
|
||
|
|
2e51d3f5e4 |
v24 第 16 批:旅程 / 行程两级(术语固定 · 题图16:9 · 五项统计 · 独立行程页 · 设置三页)
用户十条,逐条落地:
1. 「题图应该按照 16:9 显示,因为添加的时候是按这个比例裁剪的,现在的显示比例不对。」
→ 详情页题图容器从 4:3 改成 16:9,与 ImageSlot 的 cover 槽位同一个数字
(部署断言比对这两处,避免哪天改一处漏一处)。
2. 「题图的右侧,显示的是参与人都有谁、去了几个城市、一共去了几天、一共几段行程,
一共花了多少钱,这些信息,都从该活动下的所有行程统计总结,简介那一段话保留。」
→ 新增 Trip::summary():参与人(各段并集)/ 城市(去重)/ 天数 / 段数 / 总花销,
全部读时算(与"时间城市派生"同一条原则,不存冗余)。
天数按**日历天**含首尾(8/1→8/3 = 3 天,不是 24 小时整除的 2 天)。
城市与时间从标题旁挪进信息区,简介保留。
3. 「活动页面,不需要上传图片,图片视频,都是针对一次具体的行程的。」
→ 删掉旅程页的照片上传板块与照片墙;照片一律挂在**行程**上
(行程表单里有上传,附件 target_type='trip_node')。
4. 「计划行程、未成行的行程,都不要显示……只要是没有就是 0 条,那个类型就不显示了,
现在显示的是 0 条,这样没意义。」
→ 空分组直接跳过不渲染;全空时给一句引导(否则新建旅程页面一片空白)。
有内容的分组照常显示(计划 / 实际 / 未成行三种)。
5. 「行程的编辑功能,点击后会把页面撑破。编辑行程,打开新的页面进行编辑。」
→ 行程的新增与编辑改成**独立页面**(trips/node_new / trips/node_edit)。
原来内联在详情页:N 份表单互相撞元素 id、密码管理器逐个扫描变卡,
正是"把页面撑破"的成因。
6. 「添加计划行程和实际行程的按钮,都放在右上角。点击后打开新的页面进行添加。」
→ 详情页右上角两个按钮(+ 计划行程 / + 实际行程),指向独立新增页。
顺带:标题旁边不再放时间(时间移到题图右侧信息区)。
7. 「添加和编辑行程的内容,你去找 triplog 里面的添加节点功能,跟那个一样。」
→ 字段全量照搬 TripLog 的 nodes/form.php:
类型 / 标题 / 起止时间 / 出行方式 + 车辆 / 线路名 / 承运方 / 车次航班号 /
座位 / 登机口 / 出发地 / 到达地 / 内容 / 参与人(含角色)/ 照片 / 花销。
交通字段不做条件列(先填高铁车次、后来改成自驾,填过的东西不该丢)。
8. 「右上角的按钮,需要增加一个设置按钮,用来设置车辆管理、出行方式、节点类型。
注意,我们在财务中,已经把车辆添加进去了,所以这里不用重复添加那里面的车辆,
直接读取就可以,但是可以添加其他的车辆,比如租的车,借别人的车。」
→ 新增旅程设置菜单(三页):车辆管理 / 出行方式 / 节点类型。
车辆**两个来源刻意不合并**:财务里 kind='car' 的账目(那是资产,没有车牌车型)
由 Vehicle::fromLedger() 只读列出,选车下拉里标「来自财务」;
自家 / 租的 / 借的车录进 vehicles 表。不复制财务的车(避免同一台车两处维护)。
节点类型做成**可自定义**的表(TripLog 同款),内置 5 种不可删;
行程里存类型**名字**,所以改类型名会级联更新已用它的行程。
9. 「活动都改名吧,改成旅程,新建旅程,旅程包含行程,这个名字就固定下来。」
→ 术语固定:**旅程**(容器)包含**行程**(单元)。
顶层按钮「新建旅程」、详情页三组叫「计划的行程 / 实际行程 / 未成行」、
列表「N 段行程」、设置菜单「旅程设置」。上一批的"活动"叫法全部换掉。
10. 「新建旅程里面,怎么还有备注啊,同行的人啊,这些都不要了,这些都是从行程里面
总结出来的。在新建旅程和编辑旅程的时候,都不要了。」
→ 旅程表单只剩:标题 → 题图 → 简介 + 公开开关。
备注与同行的人全部移除(参与人 = 各段行程参与人的合集)。
⚠️ 但**老数据里活动级那份参与人名单不能被清空**(那时还没有行程级参与人),
所以路由"没传这个键就不动",并加了断言钉住这一点。
数据结构(Schema v21,老库自动升级):
· 新表 vehicles(自家/租的/借的)、node_types(可自定义)、trip_expenses(行程级花销)
· trip_nodes 补 9 列:出行方式 / 车辆 / 线路名 / 承运方 / 车次 / 座位 / 登机口 / 起终点
· trip_node_members 补 role_tag(司机 / 摄影担当这类角色)
· 花销金额存**分**,解析走字符串拆小数点(128.55*100 在浮点下会差一分)
验证:e2e 891/0(+46 条本批断言)· 升级 134/0 · 桌面几何 428/0 · 交互 92/0 ·
窄屏溢出 30/0(扩到行程表单 + 设置三页 + 详情页)· 题图点击 4/0 · 静态体检全绿。
**反向验证 6/6**:题图退回 4:3、标题旁加回时间、空分组照旧渲染、编辑退回非独立页、
车辆不读财务、总花销不汇总 —— 六种坏法全部被断言抓住。
部署静态断言新增第 16 批一组(含"空分组不许渲染""行程页不许有照片上传"
"旅程表单不许有备注与同行的人""题图必须 16:9"等反退化防线)。
部署后服务器自检 19/19,六个关键文件 md5 与线上逐一核对一致。
|
||
|
|
7138314dc7 |
v24 第 15 批:活动 / 旅程两级语义(术语 · 派生时间城市 · 表单三项 · 题图可点)
用户原话四条,逐条落地:
1. 「旅程模块,按钮是记一次活动,而不是记一趟旅程。旅程是活动里面的概念。」
→ 顶层一律改称**活动**:按钮「记一次活动」、页标题「活动」、`N 次` / `N 段旅程`;
活动详情三组改叫「计划的旅程 / 走过的旅程 / 没成行的」;
节点表单「这段旅程叫什么」「阶段」;flash 文案 17 处同步。
(导航条仍叫「旅程」——用户就是这么称呼这个模块,列表页加副标题说清两级关系。)
2. 「活动不需要开始和结束时间,是根据活动中的旅程,最早的旅程开始时间,和最晚的
旅程结束时间,计算出来的。目的地也不需要…去的城市,也是根据旅程,得到的。」
→ 活动**不再存**这三样,全部**读时算出来**:
· Trip::derived() 最早开始 / 最晚结束 / 城市去重
· Trip::cities() 按最早出现时间排(先后顺序本身是信息,不是字母序)
· Trip::attachDerived() 列表页批量补(避免 N+1)
· Trip::activitySpan() 没有旅程时说「还没有旅程」,不用「日期待补」
create/update 不再写 events 的起止与地点(老值保留不动,页面不读)。
列表排序/搜索、首页「最近去了」改走子查询。
3. 「题图那里,没有办法上传图片,点击哪里都没反应。」——**真 bug**
「点图 = 开文件框」那句 JS 只写在 people/form.php 的内页脚本里(头像专用);
第 14 批加的旅程题图与图书封面照抄了同一套 DOM 却没有对应的 JS,
于是图块有 cursor:pointer 与 hover 高亮(看着能点),点下去毫无反应。
→ 做成声明式通用绑定:图上写 data-pick-input="#fileInputId",
app.js 统一接管(一个页面多个也各绑各的),并删掉旧的那份专用实现。
三处(头像 / 题图 / 图书封面)全挂上。
4. 「添加活动的页面,顺序是这样的:添加标题,上传并裁剪题图,然后一段简介,就可以了。」
→ 主区就这三项,顺序照原话;时间/城市不做输入项(只留一句说明它们从哪来,
不说清用户会满页找"怎么没地方填时间")。
备注 / 公开 / 同行的人收进「更多设置」折叠区——功能一个没丢。
顺带修掉两处会炸的实现问题:
· ORDER BY 引用聚合别名(first_at)在 MariaDB/MySQL 报 1247,且只在运行时炸、
php -l 看不出 → 改成 ORDER BY MIN(starts_at),否则整个旅程模块 Fatal。
· e2e 里"详情页显示了派生时间"原本写成整页查找,下面每段旅程自己也渲染日期,
于是把派生值塞成 null 也照样绿(借光假绿)→ 判据收进页头那一段。
验证:e2e 845/0(+15 条本批断言)· 升级 134/0 · 桌面几何 428/0 · 移动几何 688/0 ·
交互 92/0 · 题图真机点击 4/0 · 读书窄屏 15/0 · 家谱 34/0 + 10/0 + 反向 3/3 ·
观影按钮 60/0 + 反向 3/3 · 本批反向验证 4/4(题图绑定抽掉→红、派生值不挂→红、
城市退回读 location→红、时间字段加回表单→红)· 部署静态断言新增两组(含"活动不许
再写起止时间/目的地"与"ORDER BY 不许引用聚合别名"的反退化防线)。
部署后服务器自检 19/19,五个关键文件 md5 与线上逐一核对一致。
|
||
|
|
89b956205d |
v24 第 13 批:旅程模块重构(双轨 → 一条记录 + 状态机)
用户原话:「旅程这个模块,我准备大改…如果按照计划行程和实际行程来分别管理,
会变的很复杂…根据我的想法,重构这个模块。」
—— 这不是加需求,是把「双轨」架构换成「一条记录 + 状态机」。
## 用户确认的三个决定
1. 两层:**活动**(顶层容器)→ **行程**(挂其下,字段沿用原节点)
2. **修正不立即转实际**:修正只是记录,行程仍标"计划(已修正 N 次)",
要再点一次「标记为实际」才转实际
3. 老数据**按现有轨道自动迁移**(category=reality → stage=actual)
## 状态机(本次重构的心脏)
trip_nodes.stage:
· plan 计划行程 —— 唯一以「计划时间」上日程的状态,三个动作都能用
· actual 实际行程 —— 上日程(实际时间)+ 进「往年今日」
· skipped 未成行 —— **不上日程、不进往年今日**,但旅程里仍显示并标注
(用户:"还是要显示,只不过要标记上")
旧的 category(plan/reality) 保留作兼容,业务一律看 stage(saveNode 同步维护两者)。
## 三个动作(Trip.php)
· reviseNode —— 记一次修正。🔴 **第一次修正前先把原计划存成 seq=1 快照**,
否则用户要的"之前的计划行程数据要保留,为了以后做对比"就落空。**不动 stage**。
可无限次(seq 递增)。
· completeNode —— 实际=计划,直接转 actual,**不产生修正历史**(没有变化就没有历史)。
· markActual —— 把(已修正过的)计划正式转 actual。
· skipNode —— **不是真删**,只置 stage='skipped'(用户明确"不要真的删除,只是做一个标记")。
· restoreNode —— 撤销未成行(点错了能改回来)。
界面上的「彻底删除」与「标记未成行」是两个并列动作,弹窗文案里写明区别。
## 修正历史(Schema v19,新表 trip_node_revisions)
每次修正一行:实际起止 / 实际地点 / 说明 / 参与人快照。
第 1 条 = 原计划快照,第 2..n 条 = 各次修正 —— 详情页用表格展示,可直接对比。
## 日历与往年今日(用户点名的联动)
· 日历:`n.stage IN ('plan','actual')` —— 去掉旧的 `status<>'cancelled'`
(那是双轨口径;现在"未成行"是 stage)。实际行程标题加 ✓ 前缀便于区分。
· **活动本身绝不上日程**(用户:"活动因为可能跨越的时间比较长")。
· 往年今日:旅程部分从「整趟 events」改成**实际行程**(stage='actual'),
标题带活动名前缀;未成行的不出现。
## 验证
新增 _trip_e2e12.py **30/0**:建活动 → 加行程(可选计划/实际)→ 修正(仍计划 /
历史保留原计划 09:00 / 可无限次)→ 标记为实际 → 完成(无历史)→ 标记未成行(未真删)
→ 日历四种情形(已转实际✓ 新建实际✓ 完成✓ 未成行✗ 活动✗)→ 往年今日只看实际。
回归:e2e 787/0、升级 129/0(含 v19 路径)、batch_e 91/0、batch10 46/0、batch9 38/0、
picker 28/0 + 11/0、crop 17/0、formbloat 9/0、semi 27/0、geom 5/5、mobile 688/0、
interact 92/0、drag 5/0。
e2e 两条旧断言("双轨两块/两轨各一条")按重构语义更新为"三组渲染 / stage 各一条
+ category 与 stage 同步"。
部署断言新增 1 大组,含**反退化防线**:skipNode 若被改成 DELETE 立即变红。
线上自检 19/19;线上真实账号验收 16/0。
线上老数据迁移实测:#222 计划3+实际1、#221 实际4、#220 实际13 —— 一条没丢。
|
||
|
|
ecf121772f |
v24 第 12 批:5 条(删除按钮位置/日期控件三修/日历跳转/节气节日/财务图)
用户 2026-10-05 的 5 条。第 11 批交付后用户追问"确定这些需求都做完了?"—— 没有, 这 5 条是进一步要求。实现过程中查出 4 个真 bug。 #1 详情页删除按钮紧贴「开通账号」 上一版放在页面最下方独立区块(danger-zone),用户要「就放在开通账号按钮下面, 紧挨着,长度大小和开通账号一样,颜色改成红色」。→ 搬进「登录账号」卡片内 (.del-inline + 细分隔线 + .btn-block-lg)。 线上几何实测:同一张卡片、左边缘对齐 255==255、宽度一致 990==990、红色 rgb(192,83,63)。 #2 日期控件三修(两个真 bug + 一次闪烁) · 灰底:Semi **外层** .semi-datepicker-input 自带 rgba(46,50,56,.05)、高 32px; 内层 wrapper 白底 40px → 灰的露在外面。实测「灰底元素」= 1 个 → 修后 0 个。 · 不垂直居中:外层 32px < 内层 40px,实测 gapTop=1 / **gapBottom=17**(文字贴顶) → 修后 gapTop=1 / gapBottom=1。 · 闪烁:实测首 **200ms** 原生输入框可见(要等 3.4MB React 解析)。 用 no-js/js 二态:<html class="no-js"> + head 内联脚本立刻换 js + CSS `html.js .dt-input:not(.is-semi) .dt-native { visibility:hidden }` (配同高骨架 ::after 防布局跳动)。实测「出现过原生可见」= False。 #3 日历跳转保留视图 + 下拉日历 原来写死 view=day(在月视图点跳转会被踢进日视图)。→ 表单带当前 $view(月还带 ym); 选择器从"居中弹窗"改成**按钮下方下拉** .cal-jump-drop。 实测:月视图跳 8/15 仍在月视图且是 8 月;周视图跳 8/15 → 落 8/10 那一周; 日视图跳 8/15 → 显示 8/15。 #4 传统节日 + 二十四节气两个系统本(Schema v18) Holiday::SOLAR_TERMS(24 项 C 常数近似公式 + 例外表)+ traditionalRange (农历推导;除夕用"次日是否正月初一"反推,写死腊月廿九会有一半年份错)。 实测 2026:节气 24 条全对(清明 4/5、冬至 12/22);春节 2/17、除夕 2/16、中秋 9/25。 ⚠️ ENUM 必须显式 ALTER(对已存在的表不生效)——同一坑第二次踩,VERSION 17→18。 #5 财务(一个真 bug + 一个假起点 + 一个真 bug) · chart_line 读 `cents` 而 timeline() 返回 `net` → 键名不匹配,**总资产图每点读成 0** (正是用户说的"图上是 0")。修:兼容 cents ?? net ?? asset。 实测 timeline net=800000(8000 元),线上 polyline 有真实起伏。 · 去掉 timeline 人为插的"前一天=0"假起点(用户:"总资产不应该是 0, 应该是所有账目创建时的金额的总数")。 · ppSetSelected 只用 document.querySelector 按名字找,而财务页有**两个** person_id 组件(「记一笔」+ 修改弹框)→ 永远灌到第一个。加 root 参数、调用方传 panel。 验证:e2e 786/0、交互 92/0、升级 129/0(+3 条 v18 断言)、batch_e 91/0、batch10 46/0、 batch9 38/0、picker_unify 28/0、picker_lazy 11/0、crop 17/0、formbloat 9/0、semi 27/0、 mobile 688/0、drag 5/0、geom 5/5;第 12 批专项:财务 7/0、日历 19/0。 部署静态断言新增 5 组(含反向验证:注入回退 → FAIL=1)。 线上自检 19/19;线上真实账号验收 **30/0**(含真实浏览器几何量删除按钮位置)。 |
||
|
|
5d80b9480b |
v24 第 11 批:7 大条 20 子项(首页/名册/观影/裁剪/日程/财务)
用户 2026-10-04 第 11 批,7 大条: - #1 首页:未来七天过滤掉「农历」(每日一条会淹掉真日程);右侧日历本标签 从灰字(.dash-tag)改成**实心色块**(底色=日历色、白字、text-shadow 兜底浅色)。 - #2 名册:分组改**居中模态框**(用户要"像选观影人一样")。修掉"点第二次失效" ——根因是旧实现用**捕获阶段**监听 document click 做"点外面就关",与触发按钮 的 click 打架;模态把关闭收敛到遮罩/✕/取消。卡片「编辑」后加「删除」 (→ people/destroy);详情页底部删除改成 btn-danger + btn-block-lg 大红按钮 + 两步确认。 - #3 名册详情页重排:.detail(260px+1fr) → .pcard(头像左上 + 右侧姓名/标签) + .detail-stack 全宽往下。旧布局头像卡比右栏矮,头像下留一大片空白。 - #4 观影:影院下拉换**自定义组件**(原生 <option> 画不了色块)——按钮+列表、 每行色点,值仍写隐藏 input[name=cinema_id];「管理影院」→「新建影院」弹框 (带 back=movies/new,存完回本页并 ?cinema=<id> 自动选中)。 修「开始/结束时间标签跑到控件下面」:upgradeRange 原来 insertBefore(host, group.firstChild) 把控件插到了 label 之上 —— 改成插在第一个原生控件之前。 - #5 裁剪改 **Cropper.js**(assets/vendor/cropper/,按需加载走 fb-cropper meta): 弹框 overflow:hidden + 固定画布(**无滚动条**)、**去掉缩放条**改滚轮 wheelZoomRatio、 viewMode:1 + autoCropArea 保证框小于画布、8 个触点全露。 结果仍写 *_crop = 归一化 x,y,w,h(提交契约不变)。 - #6 日程:内置节假日原来每天只标**一天**(国庆只出一条)→ FIXED 改成 [名字, 放假天数] 按区间铺;月视图从周日改**周一**起(表头同步); 跳转日期从"长条+原生框"改成**单个 btn-icon**,点开弹 Semi 日期选择器。 - #7 财务:单条曲线无盘点时只回一个点(图看着像全 0)→ 改成"从创建日(0)画到今天"; 图下条目从"每账目一张小图卡"改成**一张 chart_multi 多线图 + 颜色图例** (Ledger::LEGEND_COLORS 按 id 取模,保证颜色稳定);账目页卡顿同源治理 (行上只留按钮、表单全页一份 #ledPanels);修改按钮改弹框并回填归属人。 ## 顺带修的真 bug(本批最要紧的三条) 1. **观影表单提交静默失效**:「新建影院」弹框被误嵌在观影主表单 <form> 内。 HTML 不允许 form 嵌套 → 浏览器拆开内层,弹框里 required 的"影院名称"变成 主表单必填项、又因 hidden 无法聚焦 → 点「记下这场电影」什么都不发生 (Chrome: invalid form control with name='name' is not focusable)。 → 弹框移到主表单之后。部署脚本加防线:按顺序配平 form 开闭判断是否嵌套。 2. **裁剪取消没清空文件框**(换 Cropper 时丢)→ close(cancelled),4 个取消入口 传 true 清空;否则"点了取消"仍会把没裁的原图传上去。 3. **裁剪预览退化成原图**(同批丢)→ 用 canvas 按 Cropper 的原图像素矩形重画, toDataURL 写回预览,恢复"所见即所得"。 ## 连带修复:选人组件懒渲染(线上实测的必要性) 线上真实数据下账目页 6939 KB / 16788 个 input、观影表单 1194 个 input —— 根因是 _person_picker.php 首屏就把**全部候选人**渲成 <label>+<input>。 改成:首屏只留「不选」+ data-pp-people(JSON),**打开弹框时**才 buildList(); 无 JS 兜底走 <noscript>(HTML5 规定启用脚本时其内容按纯文本解析,不产生节点)。 新增 window.ppSetSelected / decorateItems(动态项要补算首字母)。 实测账目页 6939 KB → 544 KB。新增 _fb_picker_lazy_check.py(11/0 + 反向 2 红)。 验证:e2e 786/0、交互 90/0、半集成 27/0、batch_e 91/0、batch10 46/0、batch9 38/0、 crop 17/0(+反向)、picker_unify 28/0、picker_lazy 11/0(+反向)、formbloat 9/0、 upgrade 126/0、mobile 688/0、drag 5/0、geom 5/5。 线上自检 19/19;线上真实账号验收 7/0(含"主表单无隐藏必填""线上 Cropper 接管+取消清空")。 |
||
|
|
b2ca4f336e |
v24 第 10 批:名册真性能(根因是每卡内联表单)等九条
- #6 名册线上真实数据(1184 人)卡顿:根因是**每张卡片内联一整份分组弹层**—— 全页 2369 个 form / 27234 个 input / 22496 个 radio / 98100 DOM 节点, 密码管理器(Bitwarden 等)逐个扫描 form 把主线程占死(用户报的就是这个冲突)。 改为卡片只带 data 属性 + 全页**唯一一份**共享浮层 #rosterMenus。 实测:form 2369→3、input 27234→25、DOM 98100→17659、解压 7694→2306 KB、 长任务 1/52ms → 0。新增哨兵 _fb_formbloat_check.py(含反向验证)。 - #5 日视图翻页失效:day_columns() 无条件吸到周一,天视图只有 1 列 → 永远显示那周周一。 改为仅 span>1 时吸附(周视图行为不变)。 - #2 财务「欠款」类别(负债);并修一个真 bug:负债填负数会让净资产**反向变大**。 写入口 normalizeAmount 归一化 + summary/timeline 侧 abs 防御 + Schema v17 纠正老数据。 - #3 账目行「盘点」「修改」两个并列按钮(各自就地弹面板);图移到账目上方, 总金额图跨整行 + 每个账目一条线 + 类别筛选条;charts() 改为覆盖全部账目。 - #4 日程新增「农历」「节假日」两本不可删除只读日历(app/Holiday.php): 两级自动更新 —— 内置规则(公历固定 + 清明节气 + 农历节日)保证断网也有; 官方 JSON 镜像自动抓取补齐调休。抓取**不在渲染路径联网**(响应后 fastcgi_finish_request + 频率闸门),缓存落 storage/cache。仅派生本不可写日程。 - #6 日程页设置按钮前加「跳到某一天」(选中即跳)。 - #7 起止范围改用一个 Semi dateTimeRange 控件(观影/旅程/节点/日程共 4 处); 新增 window.fbSetDT / fbUpgradeRange;修范围重挂(React 非受控组件须 unmount 再 createRoot) 与范围里 required 未摘除导致表单无法提交两个真实问题。 验证:e2e 786/0、batch10 44/0、batch9 38/0、batch_e 91/0、upgrade 126/0、picker 28/0、 semi_integrate 27/0、formbloat 5/0(+反向 3 红)、mobile 688/0、interact 97/0、drag 5/0、geom 5/5; 线上自检 19/19 + 线上真实账号巡检 28/0(含 Firefox 实测)。 |
||
|
|
52cc1ecccb |
v24 第 9 批:名册九条(分组按钮/加入名册/生日月日/地址四级/千人性能/拖动修复/删除人员/返回名册/Semi DatePicker)
- #131 名册卡片加「分组」按钮(details+radio,显示当前分组,选中即改) - #132 修复「从已有人里加入名册」无反应 - #133 生日只显示月和日(阳历 MM-DD / 农历中文名),PersonQuery::birthdaysOf 取代 birthdayOf - #134 地址结构化为 国家/省/市/区+详细:regions 表(3429 行全国区划)+ Region.php + ?r=regions 级联接口 + 表单 .addr-grid + 老地址回填(Schema v16) - #135 千人名册性能:.person content-visibility:auto + 名册页 ob_gzhandler (实测 服务端 1.15s/3.8MB → 0.17s/87KB;客户端导航→load 1.35s → 0.37s) - #136 拖动排序改 Pointer Events(Chrome 下 HTML5 DnD 在 DOM 变动时断) - #137 个人详情页底部「删除人员」 - #138 编辑资料页加「返回名册」 - #139 所有日期选择换 Semi Design DatePicker(渐进增强:原生四字段保 POST 契约, 懒加载 vendor semi UMD,值回写原字段;工作经历 type=month 一并升级;宣纸主题对齐) 验证:e2e 785/0、batch9 37/0、upgrade 126/0、picker 28/0、mobile 688/0、interact 97/0、 batch_e 91/0、import 21/0、douban 22/0、drag 5/0、semi-integrate 28/0、semi-probe 17/0、 geom 5/5;部署后线上自检 16/16(含 4 个 semi 资源 200 + regions 路由)。 |
||
|
|
864e281265 |
v24 第 8 条:凡「选人」都走同一套弹窗 + 拼音首字母(补上唯一漏做的一条)
用户原话:「名册详情页面,添加关系那里,选择一个人,应该也是和观影人一样, 点击弹窗,用拼音首字母选择,否则人多的时候,这个选择起来太麻烦了。」 - 成员详情页:①「添加关系 → 对方」 ②「分享他的名片给…」→ 全局选人组件(单选) - 任务:③ 列表页「共享给(多选)」 ④ 列表页「负责人」 ⑤ 详情页「负责人」 (共享候选人先把自己过滤掉 —— 共享给自己是空操作) - 纪念日:「与谁相关」→ 单选,且保留原下拉第一项「不指定」(ppAllowEmpty) - 「对方」是必填,但组件里是原生 radio(HTML 没有"必选一个 radio"的可靠约束) → 页面补一道 submit 拦截,否则用户点了按钮会静默无反应 顺带修一个用户没提、我自己抓到的真 bug: _person_picker.php 原用**文件顶层**的 `static $ppSeq` 做 id 计数 —— PHP 里被 include 的文件,顶层 static 初值**每次 include 都重新绑定**(与函数体内的 static 不同)→ 同一页 include 两次都拿 pp1 → DOM id 撞车(getElementById / <label for> 全抓到第一个)。实测 include 三次得到 pp1,pp1,pp1。改用 $GLOBALS['ppSeq']。这个坑只有同一页出现两个组件时才暴露,且资源页第二个组件 只有真有资源时才渲染 —— 所以写成**单元级**探针(同一请求 include 两次)。 另修一个构建层隐患(本轮部署时踩到,白查了一轮): 本机 core.autocrlf=true,一次 git stash 往返就把 6 个文件全改成 CRLF;而部署 打包取的是工作树 → 包里带 CRLF 上去 → 部署脚本里 grep -qxF '<整行 CSS 选择器>' 的校验在 Linux 上一条都匹配不到(报错却指向"CSS 少了某条规则",极易误诊)。 → .gitattributes 加 `* text=auto eol=lf`(必须排在 -text 那几条之前), 并在打包前加一道行尾体检 _fb_eol_check.py(失败时点名文件)。 验证:反向验证对着 git stash 出来的真·改动前状态跑 → 11 通过 / 17 失败; 新增 _fb_picker_unify.py 28/0;e2e 785/0;几何 428/0;移动端 688/0;交互 97/0; 升级 126/0;家谱 34/0;专项 91/0;导入 21/0;豆瓣 22/0;行尾体检 + 两道新静态 门禁均实测可失败。线上 6 文件 md5 一致、CR=0。 |
||
|
|
4377fa995f |
观影:豆瓣简介补上导演/主演/上映年份等主要信息
用户原话:「观影模块,添加电影的时候,从豆瓣获取的信息不够,
应该把导演、主要演员,上映年份等主要信息,也都放在简介中。」
**改了什么**
- 新增 `Douban::summaryText($meta, $title, $withPlot)`:把主要信息拼成一段可读文本。
格式(只有有值的行才出现,绝不写"导演:"这种空壳行):
《片名》(年份)
导演:X 主演:A、B、C 类型:… 片长:… 制片国家/地区:… 又名:… 豆瓣评分:…
<空行>
<剧情简介原文>
标签词与详情页 `db-meta` 用的同一套(年份/导演/主演…),同一字段在两处叫同一个名字。
- `movies/douban` 接口新增 `summary` 字段,**在服务端拼**。
为什么不在前端拼:这段文本会**写进 movie_details.info**(落库、与豆瓣脱钩),
拼法必须可测、只有一处;拼在 JS 里既测不到,也没法在"重新抓取"之外复用。
- 前端 `apply()` 改为填 `data.summary`(原来只填 `meta.plot`),
并补了两处**原来缺失的反馈**:
· 简介里已有用户写的内容 → 明确告知"没有覆盖它"(并提示清空后重抓);
· 豆瓣确实没有可用简介文本 → 也明确说一声。
之前这两种情况都是**静默什么都不做**,用户会以为"抓取坏了"。
简介框 rows 3 → 6(内容变多了),placeholder 也说明了会自动带上主要信息。
**验证**
- 新增 `_fb_douban_summary.py` 22 条,覆盖:字段齐全 / 缺字段不留空壳行 /
全空返回空串 / 只有剧情时不带多余空行 / 接口确实吐 summary /
前端写入的值只能是 summary(按 `infoEl.value = …` 的实际赋值断言,
不是"文件里有没有某个词")/ 仍然只填空不覆盖。
- **反向验证**:把拼装改回"简介=plot"→ ① 组 8 条立刻红(片名/年份/导演/主演/
类型/片长/国家/评分全部缺失),还原后复绿。
- **线上真连豆瓣**实测(`哪吒之魔童降世` / `流浪地球`):year/director/actors/
genre/runtime/country/rating/plot 全部有值,拼出的简介如
《哪吒之魔童降世》(2019)/导演:饺子/主演:吕艳婷 / 囧森瑟夫 / 瀚墨 …/
类型:剧情 / 喜剧 / 动画/片长:110分钟/豆瓣评分:8.4 + 剧情原文。
- 部署脚本加 4 条静态断言(summaryText 在服务端、接口吐 summary、
前端不许再只拿 meta.plot、摘不到要提示)。
- 回归:e2e 775/0 · 豆瓣简介 22/0 · 第 5 轮专项 91/0 · 导入 21/0。
|
||
|
|
b5725b449a |
v24 第 6 轮:名册导入 / 两种备注 / 多段工作经历 / 家谱卡片与首字母检索修正
用户 2026-10-03 第二批(8 条)。逐条落点:
1. **名册导入**(新):支持 **vCard 3.0/4.0(.vcf)** 与 **CSV/TSV**(中英文表头都认)。
新增 `app/ContactImport.php`(纯解析)+ `app/ContactImporter.php`(判重+落库)。
判重按 **手机号(哈希,精确)→ 姓名(大小写无关)**;命中已有的人**只补空字段,
绝不覆盖**(批量覆盖没法回退)。
解析上要处理的三件事都处理了:**折行接回**(长名字/地址会被按字节折断)、
转义还原(`\,` `\;` `\n`)、多值收全(TEL/EMAIL/ADR 可能出现多次)。
UTF-16/GBK 的 vcf 也认(手机导出的常见编码)。
2. 成员详情页:右上角「移除」→ **「返回」**(回名册);「删除」移到页面**最下方**
(在「开通登录账号」下面),并改成 `<details>` 两步确认 + 写清会连带删掉什么。
3. 编辑资料里的「这个人的一句话」**去掉** —— 那句话只能**本人**改。
⚠️ 落法是「这一栏只在改自己时出现」:用户要的是"别人不能替他写",
而不是"这句没人能填";服务端也只在 `$isSelf` 时读表单,否则管理员一编辑就把它清空。
4. **工作单位 / 职务 / 职称可以有多段**(新表 `person_jobs`):表单是一组"经历行"
(单位/职务/职称/起止年月/备注,可 +/×),详情页按"现在 + 历史"列出。
`persons.org/title` 保留,语义变成"当前那段的快照"(列表页等只要"现在在哪儿"),
升级时把老的 org/title 回填成一段经历。
5/备注分两种:「自我介绍」(`persons.note`,他本人写的、别人看得到)与
**「我对他的私人备注」**(新表 `person_notes`,每人每对象一条,**只有写的人自己看得到**)。
详情页有独立的一块;表单里只在"编辑别人"时出现。
6. 成员表单:**头像放到最前**、去掉「选择文件」按钮(点那张默认头像即选文件);
表单标签统一 15px/600(原来 13–14px,和 12px 的灰字提示分不出层级)。
7. 名册列表:⋯ 前面加**「编辑」**按钮(改资料是高频动作,藏进 ⋯ 要点两下)。
8. 家谱:
· **首字母检索 bug**:输入 `yy` 会搜出「岳父/洪勇」。根因是前端把
**姓名与称呼的首字母拼成一串**再判子序列 —— 洪勇的称呼是"岳父" → `hy`+`yf`=`hyyf`
→ `yy` 跨字段命中。改成**逐字段独立判**(服务端本来就是逐字段判的,两边现在同口径)。
· **卡片仍比头像宽**:头像是 **1:1(正方形)** 槽位,卡片里却写成 96×72 的长方形 +
`object-fit: contain` → 照片被缩到 72 宽、左右各留 12px 白 → 看着"框比头像宽"。
改成**正方形头像区 + `object-fit: cover`**(90×128 卡片,88×88 头像区)。
· 右上角加**「我的家谱」**按钮(只在看别人的家谱时出现 —— 看自己的时候点它没意义)。
**测试新增**(都能变红,且做过反向验证):
- `_fb_import_test.py` 21 条:新人新建 / 手机号命中不重复建人 / **已有字段一个都不被覆盖** /
缺的字段被补上 / 反复导入幂等 / 只给姓名也能命中 / 认不出的文件被明确拒绝,
外加 ⑱ **"有单位但没有经历行"的人**(最容易漏的覆盖路径)。
- `_fb_roster_filter_test.js`(jsdom,用真实渲染出的 HTML)8 条:直击
「输 yy 不该命中 岳父/洪勇」;反向验证时把实现改回拼接写法 → ① 立刻红。
- 改动过程中被测试抓出**两个真 bug**并已修:
① 导入时给"已有单位的人"也插经历 → `PersonJob::syncCurrent()` 回写把这人的单位**覆盖**了;
② `syncCurrent()` 在经历表为空时会把 `persons.org/title` **清空** ——
而它被"编辑资料"这类常规路径调用一次就触发,属于**随手一保存就丢数据**。
现在空表直接 return("经历表不是唯一的一份")。
**战绩**:e2e **775/0** · 导入 21/0 · 第 5 轮专项 91/0 · 家谱 34/0 · 几何 428/0 ·
移动端 688/0 · 交互 97/0 · 升级 126/0。
线上:schema **v15**、指纹一致、`person_jobs`/`person_notes` 已建、
6 条老的"工作单位"全部回填成经历、13 个文件 md5 与本地一致、错误日志无新增。
|
||
|
|
218cdf74fc |
家谱:修「线又错位」(我上一版的居中写法在库里不成立)+ 框宽收成头像宽 + 去掉悬停提示
用户 2026-10-03 反馈三条。**第一条是我上一版引入的回归,先说清楚**。
一、「画线又有点错位了」—— 上一版把居中从 transform 换成 `inset:0 + margin:auto`,
这一步在**这个库里不成立**:
含块是库的 `div.card`,它在 Cartesian 模式下只有 **1.8px 宽**
(宽度由内部一个空 div 撑出来,卡片是溢出绘制的)。
`inset:0` + `margin:auto` 的"居中"依赖"容器比元素宽" —— 容器只有 1.8px 时
auto 外边距分不出任何空间,卡片直接被摆在容器左上角、整体**右移 53.1px**。
实测(1600×1000、库缩放 0.9):**每一张**卡的中心都偏在节点中心右边 53.1px
(是固定的布局偏差,不是随机抖动)→ 连线仍然画在节点上,于是全部错位。
正解:`left/top: 50%` + **半个自身尺寸的负外边距**
(`margin-left: calc(var(--kin-w) / -2)`)—— 位置完全由布局决定,
**不经过 transform**,所以库那条 `:hover { transform: … }` 也顶不掉它。
⚠️ 负外边距用 calc 与尺寸共用变量,不写死 -59/-66(迟早脱节)。
⚠️ 这条路上已经栽过两次,两种错写法都别再回去:
· `transform: translate(-50%,-50%)` → hover 时被库覆盖,卡片跳半张(53×58px);
· `inset:0 + margin:auto` → 容器 1.8px 宽,卡片整体右移 53px、连线错位。
二、「外面那个框又变宽了;宽度和头像图片一致,下面显示名字就行了」:
卡片 118 → **98px**(= 96 头像 + 左右各 1px 边框),横向 padding 归零,
头像改 `width: 100%` 占满卡片内宽 —— 于是**边框刚好贴着头像**,
框里不再有多余的横向空白;头像下面就是名字(+ 与我的称呼)。
⚠️ 98×116 与 JS 的 `setCardDim` 必须同源(库按它算间距与连线落点),
所以两边都改用常量:CSS 的 `--kin-w/--kin-h` ↔ JS 的 `KIN_W/KIN_H`。
三、「鼠标放在名字上后显示了一行字:资料、以他为中心 —— 没必要,不要显示」:
那两行是我上一版加的悬停提示(`.kin-hint`),**整个删掉**(模板 + CSS)。
验证:
· 家谱可视化检查 29 → **34** 条全过,新增五条正是这次的守卫:
⑮ 每张卡正对节点中心(改坏时实测 dx=53.1)· ⑯ 框宽 = 头像宽(≤2px)
· ⑯b 尺寸不是退化的 0(防止"一起变 0"也算过)· ⑰ 无 .kin-hint
· ⑱ 头像下面是名字。
· 反向验证:把居中改回 `inset:0 + margin:auto` → ⑮ 立刻红(dx=44.1),
还原后复绿(断言不是永远绿的)。
· 连线端点独立核对:12 个端点里 9 个落在卡片附近,另 3 个是
**夫妻之间那段连接线**(画在两卡之间,本来就该在两卡之间)。
· e2e 773/0 · 第 5 轮专项 91/0 · 几何 428/0 · 移动端 684/0 · 交互 97/0。
· 部署脚本更新/新增静态断言:允许 transform 之外的居中方式但**禁止那两种错写法**
(transform 居中 / inset:0)、必须有 `margin-left: calc(var(--kin-w) / -2)`、
JS 与 CSS 的宽度必须一致、`kin-hint` 不许回来。
· 线上两个文件 md5 与本地一致;kin-hint=0、--kin-w: 98px 在位。
|
||
|
|
6930191ac6 |
家谱:修「鼠标移上去卡片会跳」+ 卡片拆成「头像进资料 / 姓名换中心」两处点击区
用户 2026-10-03 反馈两条。
一、「鼠标放在某个人的头像上,那个框会移动一下位置」——**根因是库的 CSS**:
库的 family-chart.css:517 有一条
`.f3 div.card:hover > div { transform: translate(0, -2px); }`
而我们的 .kin-card 正是 `div.card` 的**直接子 div**。卡片当时用
`transform: translate(-50%,-50%)` 居中 —— 于是鼠标一进卡片,这条规则就把
居中 transform **整份覆盖**成 translate(0,-2px)。
实测(1600×1000 视口、库缩放 0.9):
卡片 translate(-59px,-66px) → translate(0,-2px)
整张卡向右下跳 **53.1 × 57.6 屏幕像素**(正好半张卡),并带 0.2s 过渡,
所以看起来就是"框移动了一下"。
修法两条:
① 居中不再依赖 transform,改用 `inset: 0` + `margin: auto`
(绝对定位 + inset 全 0 + margin auto = 在包含块里居中);
② 把库那条 :hover 位移归零(`.kin-chart.f3 div.card:hover > div { transform: none }`)——
2px 的"上浮"仍在,实测 dy=-1.8px。在 app.css 里覆盖(它加载在库 CSS 之后,
同特异性后写的赢,不需要 !important),**不改 vendor 里那份文件**
(升级库会整份覆盖、改动静默消失)。
⚠️ 660 别改回 translate(-50%,-50%) 居中 —— 那正是这次的 bug。
二、「以他为中心」按钮不要了,改成把卡片分成两处点击区:
· 头像 .kin-ph → 进这个人的资料页(people/show&id=)
· 姓名 .kin-nm → 以他为中心重画家谱(?r=kin&focus=)
两个都是 <a>(而不是一个 a + 一个 button):点名字换中心本质就是"换个 URL
重新渲染"。换成 <a> 之后,原来那段"给按钮绑 click + keydown +
mousedown/pointerdown/touchstart 三处阻止冒泡"的代码**整段删掉** ——
那是"用按钮干链接的活"才需要的补丁;顺带键盘 / 中键新窗口打开都天然可用。
两张悬停提示("资料" / "以他为中心")分挂到头像、姓名下面,只在指针悬停时显形。
服务端那份"谁有姻亲在图外"的 branch 计算也一并删掉(渲染没了,留着是死代码)。
验证:
· 家谱可视化检查 26 → **29** 条全过,其中新增两条正是本次的守卫:
⑬ 卡片不靠 transform 居中;⑭ 鼠标移到卡片上**不位移**(原来是 53×58px)。
· 反向验证:把 CSS 改回旧写法 → ⑬⑭ 立刻变红且实测 dx=53.1 dy=57.6;
还原后回到全绿(断言不是永远绿的)。
· 点击区实测:点姓名 → /index.php?r=kin&focus=9;点头像 → /index.php?r=people/show&id=9。
· e2e 773/0(四条家谱断言跟着新口径更新)· 第 5 轮专项 91/0 · 几何 428/0 ·
移动端 684/0 · 交互 97/0 · 升级 126/0 · 部署反向验证 3/3。
· 部署脚本新增三条静态断言(不许 transform 居中 / 必须有 inset:0 /
必须把库的 :hover 位移归零),防止以后被改回去。
· 线上 templates/kin/tree.php 与 assets/css/app.css 的 md5 与本地一致;
kin-focus 已清零、r=kin&focus= 在位、CSS 里 'inset: 0' 8 处。
|
||
|
|
e0562b8627 |
v24 第 5 轮:十条清单(天气填城市 / 输入即筛选 / 看板列=看板 / 账本盘点折线图 / 纪念日分类)
用户 2026-10-03 的十条,逐条落点: 1. 天气改为**个人设置里填城市**(每人一份):新增 app/Geo.php,内置 197 个 地级市坐标(离线、逐条核对,生成脚本 _tooling/_fb_geo_gen.py)。填坐标仍 作为内置表没有时的出口;老的家庭级坐标保留为兜底,升级当天天气不会凭空消失。 2. 所有"找个人"的地方**输入即筛选**:新增 assets/js/filter.js(纯前端显隐, 不发请求、回车不刷新)+ app/PersonQuery.php 把服务端判据收敛成一份 (名册 / 挑人 / 高级搜索共用,避免三处实现漂移)。 3. 名册列表:有昵称就不重复显示本名、行变矮、补齐手机/阴历阳历生日(带年份)/ 邮箱/工作单位;「清除」与「搜索」同款同尺寸。 4/5. 日程:撤掉左侧日历本栏改满宽,功能(显隐/颜色/导入/默认视图)全收进 右侧 ☰;**色块高度改为按起止时长**(旧实现一律 30 分钟,三小时的会跟 十五分钟的同高);点日历空白处即建日程,开始时间=点的那一格。 6/10. 资源:搜索提到最上;按"最近改动/最新新增"排序,默认只显示最新 10 条并 给「显示全部」出口。 7. 任务:看板的**列=用户自定义的看板**(不再是写死的待办/进行中), 拖动/左右键=换看板(改 board_id,不动 status)。 8. 财务:概览在上账目在下、记一笔改右侧按钮、账本设置(共享/改名/新建/删除) 收进 ☰、默认账本名「xxx的账本」、"挂在谁名下"默认自己且用标准选人框; **新增盘点**(新增 ledger_entry_snapshots 只增不改的历史表,旧值不被清空) + 上面画金额变化折线图(app/chart.php 服务端画 SVG,零依赖)。 9. 纪念日:手动添加的与名册生日派生的**分两块**、手动的在上;右侧设置可增删 分类(家庭级,删分类不强改已用的历史)。 本轮另外修掉/加固的四类问题(都由新增的静态或运行时守卫兜住): - index.php 少写 2 个 use 导入(Pinyin / UserSetting)→ 名册与个人设置 返回 200 但正文是 Fatal;顺带修了一处早已存在的 Attachment 未导入。 → 新增 _fb_import_check.py(1f 段)。 - 一次不精确替换弄丢了 app/Calendar.php 开头的 `<?php`,整份源码被当 HTML 吐给浏览器,而 `php -l` 报"无语法错误"(纯文本输出确实合法)。 → 新增 _fb_php_open_check.py(1e 段)。 - 模板片段 unset 了宿主模板的变量(账本片段 unset($canWrite))→ 正文里一片 Undefined variable 而 HTTP 仍 200。同 _datetime.php 早年 $t 的坑。 → 新增 _fb_fragment_scope_check.py(1g 段)+ _fb_batch_e.py 的 26 页无告警巡检。 - 窄屏下页头动作区不换行 → 周视图横向溢出且按钮被挤成三种高度(用户提过三次 "按钮要同高")→ .page-head .actions 加 flex-wrap。 验证战绩(全绿): e2e 773/0 · 第 5 轮专项 91/0 · 结构升级 126/0 · 几何 428/0 · 移动端 684/0 · 交互 97/0 · 家谱 26/0 · 按钮 60/0;三轮反向验证各 3/3 抓到,还原后复绿。 线上:schema v14、指纹一致(9df6c9fccc6796db)、snapshots 表已建、197 城、 8 个关键文件 md5 与本地逐字节一致、app/ 403 无源码泄漏、错误日志无新增。 |
||
|
|
02f5d072a4 |
家谱卡片重做 / 首页控制台 / 名册改列表 / 重复日程与旅程公开
用户 2026-10-03 提的四条:
① 家谱卡片重做
· 去掉「⇄ 姻亲」按钮,改成**每个人**卡片右下角都有「以 TA 为中心」
(点了以他重画一张家谱,涵盖原来的姻亲场景)。
· 头像外框改**长方形**(96×72,宽度贴合头像),object-fit: contain 不裁切,
姓名/称呼在头像下方。
· 修「点右下角按钮误进成员页」:卡片不再整张是 <a>,
只有姓名是链接,右下角是独立的 <button>(物理上分开,不抢点击)。
② 家谱文案与按钮清理
· 去掉副标题与下方图例说明;整块去掉「看全部 / 回到我 / 回到我的家谱」
(顺带消掉"出现两个回到我的家谱")。镜头由库自动 fit,拖拽/滚轮即可。
③ 首页控制台
· 标题下显示个人 slogan;去掉右侧三个统计;「我的资料/我的家谱」换成图标按钮;
去掉与导航条重复的「名册」按钮。
· 去掉「接下来的日子」(生日归到"未来七天"里,不再重复)。
· 新增统计信息块:按时段随机问候 + 今日天气(Open-Meteo,免 key,可留空不显示)
+ 近七天日程/待办/逾期数。
· 新增各模块小结卡(任务/日程/纪念日/观影/旅程/财务/名册/家谱/资源)。
· 个人设置新增「首页上的模块」:可勾选与拖动排序(每人一份,存 user_settings)。
· 保留「往年的今天」。
④ 名册改列表
· 卡片网格 → **竖向列表**:左头像、中间姓名+与我的关系+手机号+生日。
· 修拖动「只能往后不能往前」:落点改为显式记录"插到谁前面/后面",
并排除被拖元素自身。
· 去掉那句拖动提示(能力保留)。
· 搜索新增**姓名首字母**检索(y / yy / yyq 都命中「杨永全」):
新 app/Pinyin.php,intl 优先 + 内置常用字表兜底(本地无 intl 也能测);
纯字母/数字查询词不走 SQL LIKE(拼音与加密手机号在库里都比不了)。
附带上两轮的收尾:
· 菜单点外部自动关闭(全局事件委托,覆盖所有 details.menu)。
· 旅程 is_public:列表与日历只显示公开的;详情页不拦(不丢数据)。
· 重复日程:新 app/Repeat.php(RRULE 解析/生成/展开,含"工作日"),
新表 calendar_event_skips(RFC 的 EXDATE);日历按规则展开;
编辑支持三档「全部 / 这一次及以后 / 仅此一次」。
· 结束时间可添加/去掉,并随开始时间联动。
验证:端到端 772 项通过 / 0 失败;几何 368、交互 97、移动端 516、家谱 26、
升级 123 全绿;SQL 列名静态校验 0 处可疑;部署 11 条线上探针全过。
|
||
|
|
8f80f3e835 |
v24 第 2 轮:19 条清单收尾(附件预览/CalDAV/任务分类看板/全局选人/家谱/纪念日/财务)
用户 2026-10-02 提的 19 条需求,本轮完成剩下的 8 条 + CalDAV 服务端:
- ③ 附件:图片/视频给缩略图,PDF/Word/Excel 给**同尺寸**图标;可预览的在本页
弹层里看(不再新开标签页),渲染不了的点了直接下载;预览层右上角恒有下载按钮。
- ⑦b CalDAV:把「我的日历本」按 RFC 4791 只读子集分享出去(OPTIONS/PROPFIND/
REPORT/GET,写方法一律 403)。用**专用口令**(库里只存 sha256),不是登录口令。
- ⑨⑩ 任务:分类与看板都由用户自建;已完成的任务从看板消失,改在「已完成」页
按分类查看/搜索;设置入口在「新建任务」旁的 ☰。
- ⑪ 全局人员选择器:一个片段铺到观影/旅程/资源/日历共享/任务共享/财务共享;
修掉了老组件「名字和拼音都搜不动」的 bug —— .att-item 自己声明了 display:flex,
压过了浏览器 [hidden]{display:none},过滤根本没生效。
- ⑫ 家谱:头像改方形不再裁切;姻亲不在图上的卡片给「⇄ 姻亲」入口,
以 TA 为中心重画 + 「返回我的家谱」。
- ⑬ 纪念日:阴历/阳历都能设且按对应历法重复;名册里每个人的生日默认可见
(实时派生,不复制一份);全部作为全天日程进日历。
- ⑮ 财务:账本可共享,双方都能记账、都能看全部账目,资产−负债=净资产;
金额一律以「分」存,不用浮点。
验证:端到端 724 项通过 / 0 失败;几何 368、交互 97、移动端 516、家谱 24、
升级 123 全绿;SQL 列名静态校验 0 处可疑;部署 11 条线上探针全过。
|
||
|
|
c9037f79be |
家谱 v22:改按 family-chart 官方用法接,并放开代数上限
用户指出「只需要按照 json 格式,把以我为中心的关联数据全部取出来,
全都封装进去就行了,id 用用户表的 id,性别名字生日头像都是现成的,
只需要把配偶、父母和孩子放进去用 id 关联」——并强调「之前一定是你
调用的方法不对」。
== 验证结论(先量再改)==
用**官方示例自己的数据**(donatso 的 data.json,13 人,格式完全官方)
跑了一遍:库画出 11 人,**配偶 Andrea 的父母 Zadro/Zadra 一个都没画**。
那两位就在数据里、rels.parents 也指向他们、格式与官方示例一字不差。
→ 说明「按格式全塞进去就能画全」在**姻亲**这一项上不成立:
库的算法只走「树根的祖先链 + 后代 + 树根的兄弟姐妹 + 各人配偶」,
**不会从配偶继续向上/向下 walk**。这是库的算法边界,不是格式/关联问题。
== 但用户的批评有一半是对的:调用方式确实有我没做足的地方 ==
官方示例是:<div id="FamilyChart" class="f3"> + 引 family-chart.css。
我之前**既没加 f3 类、也没引库 CSS**,而是自己在 app.css 里手补
`.kin-chart svg.main_svg{width:100%;height:100%}` 去绕开 300×150 那个坑 ——
属于"用歪门邪道达到同样效果"。现在按官方来:
· 容器加 `f3` 类(库的 CSS 全部以 .f3 作用域)
· 引入官方 family-chart.css(新增 vendor 文件)
· 在 app.css 里把库的深色主题变量覆盖成家书宣纸色
(--male-color / --background-color / --text-color 等)
· 自己的 svg 撑满那条规则删掉(官方 CSS 已提供)
== 放开代数上限(用户要求「全部取出来」)==
官方示例**不调** setAncestryDepth / setProgenyDepth —— 库在"未设置"时
会跳过深度截断,等于不限代数。我之前写死了 2/3,真实家庭一超过就被砍。
现在不设这两个值。服务端 familyMap 的 up/down 也从 (2,2) 放到 (9,9)
(这两个参数只影响**代数标号**,超出会压到边界档导致曾祖辈与祖辈混层;
遍历本身是走遍整个连通图的,人员收录数不受影响)。
== 工具同步 ==
- familybook_deploy.sh:新增 v22 三条契约(vendor CSS 在 / 模板引了它 /
容器有 f3 类),并加**反向断言**「不许再出现 setAncestryDepth|setProgenyDepth」;
旧的"卡片尺寸/间距"断言同步。
- _fb_kin_geom_falsify.py:注入②的原锚点(setProgenyDepth)已随本次删除,
换成「禁掉上溯循环」——树根退回「我」,伯父/堂兄/堂嫂/堂侄/侄子那批旁支
会离开画面,J 项必须红。3/3 全抓住。
- ⚠️ 又踩一次「注释撞断言字面量」:新写的注释里提到 setProgenyDepth 函数名,
把「不许再设代数上限」这条反向断言直接打红。改注释避开字面量后恢复。
== 战绩 ==
e2e 549/0 · 桌面几何 368/0 · 移动端 516/0 · 交互 97/0 · 升级专测 123/0
家谱可视化 24/0 · 家谱版面 10/0 · 反向验证 3/3 · 静态断言空跑 RC=0
部署 + 线上自检全过。
|
||
|
|
c1a97c7d81 |
家谱 v21:改回 family-chart 库渲染
用户明确要求「还是用 family-chart 来实现吧,刚才查了这个库可以完美实现
我想要的效果,之前一定是你调用的方法不对」。自研区间法(v20)虽然
20/20 全显示且通过全部版面检查,但用户觉得不够顺滑细腻,改为交给成熟库。
== 关键修复(都是真跑出来的坑)==
1. **IIFE 首行就 return**:本地变量 `var f3 = root.querySelector(...)` 与
全局库 `f3` 重名,`typeof f3 === 'undefined'` 因变量提升恒为真 →
整段 JS 一行没执行、画布永远空白。改名 `f3Canvas`。
2. **「回到我」不动卡片层**:库的 zoom 行为绑在 `#f3Canvas` 上,不是
`svg.main_svg`。拿 svg 的 `__zoomObj` 去驱动只会动 SVG 连线层,
HTML 卡片层(`#htmlSvg .cards_view`)留在原地。改取 `#f3Canvas`。
3. **SVG 退化成 300×150**:库自带 CSS 写的是 `.f3 svg.main_svg{width:100%;
height:100%}`,而我们的容器类名是 `.kin-chart`(不是 `.f3`)、也没引
库的 CSS → SVG 根元素用浏览器默认尺寸,所有画到框外的连线被裁掉。
症状很隐蔽:卡片层是 HTML div 不受影响,于是表现为「卡在、线没了」。
补 `.kin-chart svg.main_svg{width:100%;height:100%}`。
4. **儿子/女儿这一辈不显示**:树根是最高祖先,`progeny_depth` 必须覆盖到
「我」的下一代 → 从 2 调到 3。
5. **连线是白的看不见**:库给 path 写了 inline `stroke="#fff"`,普通 CSS
覆盖不了,必须 `!important`。
== 免费版能力边界(已实测确认,非调用问题)==
库的算法只画「树根的祖先链 + 后代 + 树根的兄弟姐妹 + 各人配偶」,
**不会从配偶继续向上/向下 walk**。20 人夹具里岳父/岳母/舅子/外甥
(4 位姻亲)画不出来(换树根、调 depth 都救不了)→ 图上 16/20。
服务端 data-datum 仍含全体(含姻亲),名册/称呼不受影响。
== 验证战绩 ==
- e2e 549 通过 / 0 失败
- 桌面几何 368 / 0
- 移动端几何 516 / 0(家谱断言改到 family-chart 选择器)
- 交互 97 / 0;升级专测 123 / 0
- 家谱可视化 24 / 0(含「svg 不是 300×150」这条,正是坑 3 的捕手)
- 家谱版面 10 / 0
- 反向验证 3 / 3(间距压扁→叠卡;depth=1→缺人;写坏 centerOn 平移量→偏离中心)
|
||
|
|
e22202271a |
v20: 家谱布局推倒重写为区间法(tidy tree)—— 一次成型,无多轮推挤修补
用户反馈:"错的更离谱了,这次连位置都不对了"(v19 实测"我"跑到离父母 800px 的最右侧)。
根因:v18/v19 是「游标铺放 → 防重叠 → 同代重排 → 再防重叠 → 再硬推」
叠了四五层修补,每一层都在破坏上一层的"父居中于子女",多轮互相拉扯后整棵树漂移。
区间法规则(一次成型,天然不叠卡):
① 每棵户子树自底向上算需求宽度 needW = max(自身宽, Σ子女宽 + 间距);
② 自顶向下分配互不重叠的 x 区间,子女依次铺在父区间内;
③ 父户居中于子女的实际跨度。
跨树边(岳父母户也把妻子列为子女)只走「主父户」进树递归
(主位卡是我方血亲的那个父户,服务端给每张卡标 blood),
其余父户只留给画线段 —— 岳父母的子女横梁照样连到妻子,布局不重复铺人。
验证(20 人夹具 + 真实渲染):
· 版面五判据 A-E 全过(8/8):不重叠 / 线在卡后 / 下垂线从夫妻横线中点出 /
同代有空隙 / 横线在两卡缝隙;行分布:兄姐弟妹紧贴我、堂表在伯父下方、
岳父母贴妻子右上、外甥在舅子下方;
· 连线完整性:该连上一代的卡 0 缺线、悬空竖线 0 段;
· 反向验证 3/3:注入坏法(下垂线从左卡出 / needW 砍半 / 中点取左卡)全部变红。
|
||
|
|
65378ff48e |
v19: 家谱画线重写 —— 先定节点坐标、再按节点画线(用户建议的思路)
用户反馈(原话):「还是有问题,主要是线的问题,很多线画的不到位,要么是连的不到位,
要么就是和下一代的节点没连上。你是用 convas 画的吗?应该把每个节点的位置都摆好之后,
再根据节点进行画线」。
我按这个思路把画线整段重写了,并用脚本把「哪张卡没连上」**量成数字**(新工具
`_tooling/_fb_kin_linecheck.py`)。量出来的真问题与修法:
1. **两套坐标混着用**(这是"连不到位"的根因):
横梁端点用了 `placed[k].uc`(**户中心**),而卡片真实中心是 `pos[id].x`;
「子女接父母」的竖线起点取"卡顶",横梁 y 却按"父户那一代的 y"推 —— 不在同一坐标系。
→ 现在统一:**先落定所有 pos[],再由 pos 反推每户的画线锚点**(left/right/mid/top/bottom),
画线只引用这些锚点(新 `anchor[]`,模板里显式标注了这条契约)。
2. **横梁 y 用错了代**:原用 `nextGenY(我的代数)` —— 但**叔伯的子女与我同代**(不是下一代),
于是横梁画到别处、孩子接不上。
→ 改成由**孩子自己的卡顶**算:`busY = min(孩子卡顶) - SP/2`。
3. **「接入父母横梁」用错了锚**:原是"本代卡顶 - SP/2"(只在相邻两代才成立),
姻亲旁支一错位就接不上(实测 6 张卡"有父母却没有线连上去")。
→ 改成查**父母那一户的 `busY`**。
4. **同胞被别家孩子插在中间**("线乱"的真正病根):
实测我这一代原为 哥哥(161) 嫂子(275) **堂兄(389) 堂嫂(503)** 老杨(617) ——
堂兄夹在哥哥和我之间,父母连子女的横梁被迫横跨,看着不像一家人。
→ 新增 `groupSiblings()`:同代按「父户的中心 x」重排,让同胞挨在一起;
重排后再跑防重叠 + 父户居中(收敛)。
5. **户宽算错 26px**(防重叠一直推不开的原因):
边界用 `n * SLOT`,而单人户实际是 `IW`、双人户是 `2*IW+SP` —— 处处偏大。
→ 全部改成真实宽度(新增 `halfW()`),并加 `hardAntiOverlap()` 按最终坐标硬推、循环收敛。
**战绩**(`_fb_kin_linecheck.py` 是这轮新写的诊断工具,它自己也被修了三次判据 ——
"端点恰好等于卡边"→"区间覆盖"→"半个槽位",前两版都误报):
· 20 人夹具:「应连上一代却无线」= **无**;「接不上的竖线」= **0 段**;重叠 0 对
· 版面质量 8 项全过(含 A 不重叠 / D 有空隙 / C 下垂线从中点出)
· **真实线上数据(11 人)**:重叠 0 对、19 段连线、无 JS 报错
· e2e 553 条全过
⚠️ 顺带发现的数据问题(**不是代码问题**,已告知用户):
真实数据里 **爸爸与妈妈之间没有登记夫妻关系**(妈妈是单独一户),
所以图上他们是并排但不算一对;补上「配偶」后会自动合并成一户。
|
||
|
|
8cfcb8d978 |
v18c: 无子女的旁支贴其父母旁边 + 版面检查按 v18 重写
夹具(20 人)暴露的真问题:`舅子`(我妻子的兄弟、自己没有子女)
被兜底逻辑推到整代最右侧(离我很远),还与同代的人撞上
(实测 D 项「同代相邻卡片之间留有空隙」最小间隙 -36px)。
修法:没有子女、也不在主干上的旁支,改成**贴到它父母的户旁边**
(而不是"推到最右"),并与同代已有的人依次避让。
工具侧随 v18 更新:
· `_fb_kin_geom.py` —— B 项由「不穿卡」改成「**连线压在卡片背后**」的 z-index 判据:
v18 按参考实现把线画在卡片后面,"线从卡中心连到卡中心"是设计如此,
继续用"不穿卡"判会恒红(这是我的检查脚本跟不上的地方,不是产品问题)。
C/D/E 三项保持。**20 人夹具 8 项全过**。
· `_fb_kin_geom_falsify.py` —— 按 v18 的锚点重写,注入 3 处 **3/3 抓住**:
下垂线从某一张卡出来 → C 变红
拆掉防重叠兜底 → D 变红
父母不居中于我之上 → D 变红
· `_fb_geom_mobile.py` 卡片比例 118:92 → 88:106;移动端几何 520 项全过。
全套:e2e 553 / 桌面几何 368 / 移动端 520 / 家谱版面 8(反向 3/3)——
部署脚本本地空跑与线上自检全过。其他功能未动。
|
||
|
|
693eb64d65 |
v18b: 修「配偶站到了我左边」—— 户的主位改用「与我的血缘距离」判定
用户上线后如果细看会发现的问题:线上真实数据里 户 = [红围脖(妻子), 杨永全(我)] —— 妻子被当成了"本家血亲"排在我左边。 根因:服务端判"哪张卡是这一户的血亲位"用的是「**他有父母在本图上**」—— 而一对夫妻**两边父母都可能在图上**(我的父母 + 妻子的父母),两边都成立, 于是取到第一个(妻子)。用真实数据量出来的证据: 户4 = [(3 红围脖), (1 杨永全)],两边都有 parents。 修法:改成按「**与「我」的血缘距离**」判定 —— 先用一次**不带配偶边**的 BFS 算出本家血亲集合(与 Kinship::bloodRelatives 同义),两边都在集合里时, 取"离我更近"的那一位当主位。修完线上实测: 户5 = [杨永全, 红围脖] ← 我在左、妻子在右,符合参考实现「配偶排右手边」。 顺带把移动端几何里的卡片比例断言从 v17 的 118:92 更新到 v18 的 88:106 (卡片形状换了,旧常量会让它误报)。移动端几何 520 项全过。 验证:真实数据(11 人)渲染 —— 0 重叠、11 条连线、无 JS 报错; 位置核对:我在 751、妻子在我右 863、年长的姐姐在我左 458、 父母在我上方居中(665/777)、儿子在我右下(975)、外甥在姐姐下方。其余功能未动。 |
||
|
|
11c13a4e9d |
v18: 家谱改成「仿亲友+」App 的画法(按用户给的参考实现)
用户反馈(原话):「不行,非常乱。我给你个链接 https://gitee.com/coku2333/FamilyTreeView 你去读这个代码,尤其是它那个仿亲友+app 的效果,就是我想要的,按照这个做。」 参考实现是 Android 的 NewFamilyTreeView(ViewGroup + onDraw 画线)。我读了它的 README 与源码,把它的**布局规则**逐条搬过来(不是搬代码,是搬算法): ① **从下往上定位**:先孙辈(+2)→ 子女(+1)→ 我(0)→ 父母(-1)→ 祖辈(-2)。 因为"子层位置定了,父层就定了" —— 父居中于子之上。 ② 每一代维护 **genLeft / genRight 两个游标**(该代已用到的左右边界); 往右铺从 genRight 起、往左铺从 genLeft 起(对应它的 setPart)。 ③ **配偶并排在主位右侧**(一对夫妻 = 相邻两张卡)。 ④ 兄姐往左、弟妹往右;舅姨(母系)往右、叔伯姑(父系)往左 —— 都是"以我/父母为中心"向两侧展开,而不是按格子平铺。 ⑤ 我居中于我的子女之上;父母居中于我之上;祖辈居中于父母之上。 ⑥ **连线画在卡片背后**(参考是 ViewGroup.onDraw 先画线再绘子 View)—— 所以线可以从卡中心连到卡中心,被卡面挡住即可,不用绕路。 ⑦ 卡片改成参考的窄长比例(它 50×80,我们 88×106):头像占上半、姓名与称呼在下。 画线(沿用参考的三段式,子女一律从夫妻横线中点出): 夫妻横线(两卡中心之间)→ 从**横线中点**往下垂到横梁 → 横梁分到每个孩子。 上一版(v17「整齐树」)的问题:线虽然不穿卡了,但整张图看着像表格 —— 每一代从左到右平铺、兄弟姐被推到最外侧、伯父排在爸爸右边。这一版才对上参考的观感。 验证(按用户要求只做简单测试): · `_fb_kin_v18.py`(新)—— 20 人夹具渲染:**0 重叠**、20 张卡、28 段连线、无 JS 报错; 各行分布确认:祖辈居中于父母之上(575/653)、伯父在爸爸左边(403 < 575)、 兄姐在我左(442/519)、弟妹在我右、堂兄紧贴伯父下方。 · e2e 553 条全过(家谱段断言随 v18 更新为 takeSlot/placePart/putCentered/midX) · 修掉的既有 bug:`parentsOf` 的键是**户索引**,而多处按**人 id** 去查父母 —— 混用会永远查不到,导致"户的主位永远取第一张卡"(伯父那户取成伯母)、 父系/母系判定全错、叔伯姑全跑到右边。新增 personParents 映射统一。 · 修掉的布局 bug:兄姐/弟妹原本从"整代游标末尾"起 → 被推到同代最外侧; 堂表亲调 placePart 会与我的兄姐重叠(实测抓到 1 处)→ 改成紧贴其父母下方。 另:其他功能一行未动(用户明确要求)。 |
||
|
|
4b38c89142 |
v17: 家谱连线重排 —— 整齐树布局,线不再乱
用户反馈(原话):「人数对了,但是连线什么的还是乱的,实在不行,用一个画图谱的库试试也行,
以自己为中心进行连线。」
先量清"乱"在哪(_tooling/_fb_kin_diag_lines.py + _fb_kin_geom.py,用真实数据):
· 同代卡片**会重叠** —— 一对夫妻宽 254px,却按固定 200px 一格摆
(实测相邻两户重叠 52px,最小间隙 -118px);
· 夫妻横线画在**卡片内部**(y = 卡中心 + 半卡高 − 6),线从卡身上穿过(实测 2~4 处);
· 子女不落在父母下方,横梁跨得老远(实测一条横梁横跨 314px,从别人的卡上方经过)。
改法(不看"库"了 —— 实测第三方库只画得出 20 人里的 9 人,换库解决不了"画谁"):
① **同代按「户宽」逐个排开**(不再用固定格距)+ 防重叠兜底;
② 同一父户的子女**排成紧挨的一整块**,块中心对到父户中心 —— 横梁因此只覆盖
自己那几个孩子,绝不跨到别人头上;
③ 夫妻横线**只画两张卡之间的那一段缝隙**(不再从卡中心连到卡中心);
④ 子女下垂线仍从**夫妻横线中点**往下(这条是用户点名的要求,保持不变);
⑤ 子女下垂线的竖向段从**卡下沿**起,不再穿过卡片下半部分。
验证(就这一个模块):
· `_fb_kin_geom.py`(新)—— 20 人夹具 **8 项全过**:
A 任意两张卡不重叠 B 没有连线穿过卡片 C 有子女的户下垂线全从横线中点出
D 同代相邻卡片留有空隙 E 夫妻横线恰好落在两卡缝隙上
(⚠️ 该脚本用 svg.getScreenCTM() 把连线转成屏幕坐标再与卡片比 ——
第一版直接拿 SVG 用户坐标比屏幕坐标,B/D 两条恒真)
· `_fb_kin_geom_falsify.py`(新)—— 反向验证 **3/3 抓住**:
注入「横线从卡中心连卡中心」→ B 变红
注入「下垂线从某一张卡出来」→ C 变红
注入「拆掉防重叠」→ A 变红
· `_fb_kin_check_v17.py`(替代旧库版)11 项全过;e2e 550 条全过;家谱功能专项 22 项全过
顺带修掉的两个**假绿**(都不是本轮引入,是既有断言失效):
· `_fb_btn_check.py` 的「危险按钮不是品牌实心」写死了 `--brand` 常量,
而 v14 起模块主题色会覆盖 --brand → 注入 bug 后按钮变紫仍被判"正常"。
改成**现场读** `getComputedStyle(document.body)['--brand']`。
同时补上「页面上得先有危险按钮」这条前提断言(原先 danger 为空时整段是空跑)。
· `_fb_static_dry2.sh`(部署脚本静态断言的本地空跑脚本)是**手工快照**,
部署脚本改过后它还是旧的 → 拿旧断言量新代码、报出已经修好的失败。
改成每次从 `familybook_deploy.sh` **现抽**那段再跑。
另外:`_fb_geom_mobile.py` 的家谱选择器/尺寸断言随结构从旧库改为 v17
(.kin-card / svg.kin-lines / path.kin-link / 118:92),窄屏几何 520 项全过。
|
||
|
|
80f5c4f0fc |
v15: 家谱重写 —— 以我为中心检索全体亲属 + 自己排版画线
用户 2026-10-02 的反馈(原话):
「显示不齐全,应该显示本人的上一代,这一代,和下一代的所有人…以本人为中心去搜索,
只要有关系的,都检索出来进行展示」
「夫妻之间有一条横线…这对夫妻下面所有的子女,都应该从夫妻之间的这个横线出来,
而不是从这对夫妻的其中一个出来,这样可能会出现一个 bug,就是如果从一个出来,
例如从父亲出来,母亲登录进去竟然看不到自己的孩子」
「数据仍然不全,差很多」
根因(实测,不是猜):第三方库 family-chart **只画了 20 人里的 9 人**。
_tooling/_fb_kin_cover_probe.py 的硬数据:
画出的:祖父/祖母/父亲/母亲/我/妻子/哥哥/儿子/女儿(9)
没画的:伯父、伯母、岳父、岳母、嫂子、堂兄、堂嫂、舅子、侄子、堂侄、外甥(11)
库的算法只画「树根的祖先链 + 后代 + 树根的兄弟姐妹 + 各人配偶」,
**横向亲戚(伯父那一支)与姻亲旁支(妻子的兄弟)永远进不来** ——
换树根也救不了(换到最高祖先,"妻子的兄弟"照样不在树里)。
「母亲看不到孩子」还有第二层原因:孩子只登记了父亲时,母亲那一侧根本没有边。
改法(不再依赖库的树算法):
· app/Kinship::familyMap() —— 以「我」为中心的两轮贯穿检索,带 seen 防环:
父母(-1 代)/ 子女(+1 代)/ 配偶(同代)/ 每一层的配偶
/ 配偶的父母与子女(姻亲旁支由此进来)/ 兄弟姐妹的子女(侄甥)
→ 上一代、这一代、下一代的**所有有关系的人**都在名单里。
· app/Kinship::householdChildren() —— 子女按**父母所在的户**归堆,
而不是按"某个人的 children"。于是孩子只登记了一位父母时,
照样挂到这对夫妻的户上(语义:这孩子是我们俩的)。
· templates/kin/tree.php —— 自己排版:每代一行、按「户」水平排开;
夫妻两张卡之间画短横线;子女一律从**这条横线的中点**垂下来
(x = (左卡中心 + 右卡中心) / 2,与"谁登录"无关)。
· 拖拽/缩放保留(用户明确要),改用原生 d3.zoom;不再引 family-chart。
验证(只跑这一个模块,按用户要求):
· _tooling/_fb_kin_v15.py —— 22 项全过:
✔ 20/20 全员画出(== 从「我」出发可达的人数)
✔ 7 对夫妻都有横线;7 个有子女的户**下垂线全部落在横线中点**
✔ 用户点名的场景一:删掉「母亲→儿子」这条边(模拟只登记父亲)后,
下垂线**仍然**从夫妻横线中点出来,孩子仍在图上
✔ 用户点名的场景二:给母亲开账号、**用她的手机号登录**,
家谱里能看到自己的孩子,且与父亲视角画的是同一批人
✔ 截图存档(全图 / 母亲视角 / 窄屏)
· _tooling/_fb_kin_v15_falsify.py —— 反向验证 2/2 抓住:
注入「从其中一张卡出来」→「从横线中点出来」变红
注入「只走直系血亲」→「每个人都被画出来」变红
· e2e 550 条全过(家谱段改到 v15 的判据上)
顺带修的既有问题:
· app/Kinship.php 老库升级回填按实际存在的列动态拼(缺列不再 1054 炸断升级)
· _fb_e2e.py 家谱断言改到 kin_v15_nodes(旧解析器在新页面上只会解析出空)
|
||
|
|
71a7ec35a0 |
v14: 观影/家谱/日程/旅程/资源/任务六模块统一,首页改 dashboard
用户 14 条需求一次交付(1-13 功能 + 14 工作方式)。 观影(1-4) - 返回文案全模块统一为「返回观影」(不再有「返回列表」「返回观影记录」三种叫法) - 观影人一律用彩色人物标签 person_chip:底色由头像主色算出来(上传时存库), 瀑布流卡片也按人显示;五处观影模板全部改用它 - 人员显示统一走 person_name()(shown → 我给他起的称呼 → 姓名) - 影院管理页与年度报告页的上下两块用 .card + .card 统一给间距(原来紧贴成一整块) 家谱(5) - 树根从「我」换成**辈分最高的祖先**:库画的是"树根的祖先链+后代+树根的兄弟姐妹", 根=我 时伯父/堂兄/嫂子/侄子一个都不画(12 人夹具只画出 8 人) - focusMap 按**代数**圈定三代(不再按步数),normalizeLinks 把子女对称挂到父母双方 —— 修掉"母亲登录看不到孩子" - 新增「看全部 / 回到我」两个按钮,放在画布外(画布整块被库接管了手势) 统一事件原语(6-9) - 旅程:TripLog 全量迁入(3 趟旅程 / 21 段行程 / 72 张照片 / 5 个人)。 复用 events(kind='trip') + trip_details + trip_nodes(计划/执行双轨保留) - 日程:每人可建日历本(自定义色)+ 共享只读/可写;观影与旅行是每人默认两本, 内容是**从各模块实时派生**的(不复制,所以改了观影日期日历立刻跟着变); 没有具体时分 = 全天;月视图参照群晖 Calendar - 资源:挂在某人名下的附属信息(标题+正文+多附件),可共享给多人并设只读/读写, 默认折叠、可搜标题与正文 - 任务:看板三列 + 两级子任务 + 评论@人 + 共享 + 循环(完成时按原截止日推下一期) - 统一附件表:四个模块的附件共用一张,下载地址不落库、一律由 media.php 现算 模块化(10-11) - App\Module 是模块的唯一来源(加一行 = 导航条/主题色/个人设置三处自动出现) - 个人设置里可勾选要显示哪些模块、拖动排序(关掉的模块**不会**被兜底补回来) - 每个模块一套主题色(家庭级),只覆盖 --brand 三兄弟,不碰纸墨底色 其他(12-13) - 新增成员表单可选所属分组(编辑表单没有:分组是"我和他的关系",只归我自己) - 首页改 dashboard:接下来的日子 / 未来七天 / 手头在做 / 往年今日。 家人卡片从首页撤走(看人去名册) 验证(全部通过) - e2e 542 条(新增 4 模块端到端 60+ 条) - 桌面几何 368 / 移动端几何 520 / 家谱 12 / 交互 97 / 老库升级 123 - SQL 列名静态校验 0 处可疑(对着真实表结构) - 反向验证 18 条新注入全部抓住 顺带修掉的既有问题 - 老库升级回填 SQL 按实际存在的列动态拼(缺列不再 1054 炸断升级) - 全新安装时 Schema::ensure 跑在"人还没建"之前 → 补建日历本三处齐备 - 窄屏导航横向溢出(模块从 4 个加到 8 个后顶栏撑破视口) |
||
|
|
7f00d3e553 |
feat(kin,movies): 家谱改用第三方库 family-chart;观影设置按钮改纯图标并统一页头度量
家谱(v13): - 自研布局是死结,不是样式问题:连线的横杆是网格项,只能横跨「父母列→子女列」 整段区间,中间夹着与血缘无关的人(如媳妇那侧的岳父母)时必然从卡片上穿过。 换成 family-chart(D3 生态,MIT)后,排布只走本家血脉 + 配偶,姻亲不进画布, 这个死结在构造上就不存在了。 - 接入踩到并修掉的坑:main_id = data[0].id(「我」必须排最前); 卡片回调里自定义字段在 d.data.data(双层);库的 family-chart.css 是死文件 (全挂在 .f3 作用域,运行时没有 .f3 元素);库的 svg 撑满选择器永不命中 (必须自己补 .kin-chart svg.main_svg);setCanAdd/setOnCardClick 不在 chart 上 (写错会抛异常,把 updateTree 一起带走 → 整张图空白,页面照样 200)。 - 关掉 setSingleParentEmptyCard:库默认给孩子只登记一位父母的人自动补一张无名 配偶卡,看着像「凭空多出一个配偶」。 - 卡片用原生 <a> 链成员页,不依赖库的点击回调。 观影按钮(用户本轮原话): - 「记一场电影」与「设置」高度仍不一致 —— 根因是文字按钮高度由 padding+line-height 自然推出(43.8px),而 .btn-icon 写死了 40px。抽出 --act-* / --act-h 变量,两边同源。 - 设置按钮去掉「设置」二字,只留图标,图标换成三条横线(☰)。 - 设置按钮在所有页面排最右(.movie-set 用 margin-left:auto 顶到行尾)。 - 除设置外其他按钮都有颜色,与设置区分(主按钮实心 / 次要浅底 / 危险淡红)。 顺带修掉一个特异性 bug:删除按钮被 .btn 品牌底盖成实心红,与「编辑」难分辨。 - 设置下拉四个条目文字前都加图标(行内 SVG,不引外部字体)。 测试与部署: - 新增 _fb_kin_check.py(桌面,12 条)+ _fb_kin_falsify.py(6/6)证明 「整张家谱空白」这类只靠通用几何抓不到的坏法能被抓住。 - _fb_geom_mobile.py 家谱段重写为窄屏语义(库按容器 fit 缩放是正常行为, 改为断「比例没被压扁 / 缩放不至于看不清」),并加 _fb_kin_mobile_falsify.py(4/4)。 - _fb_btn_check.py(59 条)+ _fb_btn_falsify.py(3/3)覆盖观影页头按钮五条需求。 - familybook_deploy.sh / familybook_smoke.sh 的家谱断言改为第三方库形态, 设置按钮断言改为 btn-icon 纯图标 + 三条横线 + 条目图标。 - 新增 .gitattributes:vendor 库与 .min.js 标 -text,原样入库不被改写行尾。 |
||
|
|
4060c18f86 |
fix(kin,movies): 家谱连线重构为单一网格 + 观影设置统一页头
需求三点: 1. 观影模块「⚙ 设置」与「记一场电影」按钮高度不一致 → 统一页头动作区 四个选择器的 padding/font-size(.btn 与 .btn-sm 混用是根因);并把设置 下拉推广到观影全部页面(详情/影院/搜索/统计/报告/表单),落在返回按钮旁。 2. 家谱连线「线乱」→ 根因三条:每代各建一个网格(列宽最小值由内容决定, 各代不一致,窄屏实测 93.5px vs 164/78/78/78px);落点用百分比算而 grid 间距不参与百分比;连线行与卡片行不是同一套列。 改法:整棵树只留一个网格,辈分行/卡片行/连线行全是它的行,连线元素本身 也是网格项(靠 justify-self 落位);列宽固定为一卡之宽(不用 minmax/1fr, 否则宽屏卡片中心 ≠ 列中心)。 3. 亲属关系接口改白名单(parent/child/spouse)——原先未知 kind 被静默记成 配偶,夹具传 kind=sibling 让家谱断言长期拿错数据自证。 设置下拉抽成可复用片段 _settings_menu.php($setCurrent 高亮当前页、 $setCanWrite 控「影院管理」显隐),六七个页头不再各抄一份。 验证:e2e 456/0、桌面几何 268/0、移动端几何 504/0、交互 97/0、 家谱连线诊断 0 失败、静态空跑 exit=0、静态断言证伪 13/13、 反向验证 68/68 全抓住。 |
||
|
|
e9b3b8666b |
v11: 家谱按户排布(夫妻并排/长幼排序/父母括号/姻亲);观影设置下拉收拢影院·搜索·统计·年度报告;观影人头像
家谱(用户第 2 点,重写模板与 Kinship::tree): - 同代聚成「夫妻单元」(户):血亲当主位、配偶并排在其右,兄弟_姐妹按长幼(长子左) - 血亲可达性判「姻亲」:只走 parent/child 边从「我」可达者为本家血亲,妻子的父母 走不到 → 标「姻亲」并排到本代末尾(旧判据「上无父母且下无子女」对岳父母恒为假,是真 bug) - 父母括号连线:横杆盖住父母列到其子女各列,竖线落父母列中心、每子女列一道下线 - 称呼按长幼定死:姐夫/妹夫、嫂子/弟媳、大/小舅子、大/小姨子、大伯子/小叔子、 大姑子/小姑子、侄子/侄女、外甥/外甥女、堂/表 - 列宽地板 minmax(min-content,1fr):原 minmax(0,1fr) 在窄屏+户数多时把列压到卡片 (96px)以下 → 同代卡片互相重叠;.tnode 加 flex:0 0 auto 防被 flex 压扁 观影(用户第 3~5 点): - 顶栏删「影院」入口(影院是观影模块的子功能);观影列表右侧加「⚙ 设置」下拉, 内含 影院管理 / 高级搜索 / 统计 / 年度报告(后三者从 MovieLog 迁入, app/MovieStats.php + 三个只读路由,MySQL 方言;图表全用纯 CSS,不引 CDN) - 评价区删除「我」的长条徽标(被 flex column stretch 拉成整行宽的丑色带) - 观影人弹窗列表与已选 chips 带头像;syncChips 点名取 .att-name/.att-av (旧写法取「第一个 span」,v11 起那是隐藏占位,chip 上会没名字) 验证:e2e 432/0、桌面几何 268/0、移动几何 464/0、交互几何 97/0、 静态空跑 199/0、反向验证 v11 十条 10抓/0漏。本轮无 DB 变更(Schema 仍 v10)。 移动几何抓出两个真溢出并已修:高级搜索页 select 长选项撑破 .field、 原生 date 输入撑破 .date-range(360/375px)。 |
||
|
|
2c36335c3e | v10: 修影院编辑回填布尔化 bug;评分改 10 星制;观影人改弹窗+拼音首字母联想;豆瓣自动补充迁移(movie_details.douban_meta,schema v10) | ||
|
|
746f02581e |
v9: 观影对齐 MovieLog —— 瀑布流/影院管理/评价评论/起止时间
- events 起止时间精确到秒(starts_at DATE→DATETIME + ends_at),_datetime.php 复用 - 影院独立成表(cinemas,地方/名称/地址/主题色 + 50 色板),movie_details 归一为 cinema_id - 评价模型 MovieLog 同款:movie_reviews 每账号每片一条(1-10 分+正文+感受 key), movie_review_comments 两级平铺评论(回复的回复规范化到顶层、跨评价回复拦截) - 感受目录迁到 MovieReview::CATALOG(12 种,Emoji 11.0 老码位) - 观影列表改瀑布流(CSS grid 海报墙),详情页五大块 + 灯箱 + 评价区 - 升级路径:v8 老数据自动归一(影院三段按 (family,place,name) 去重建行、 评价回填、中文感受反查英文 key),回填全部幂等;老库补 cinema_id 列+索引+外键 - 修三 bug:建表顺序(cinemas 必须在 movie_details 前,否则全新安装 1215)、 layout 导航用键名当路由(影院链接指向 r=cinemas)、 _datetime.php 分钟循环变量 $m 覆盖宿主模板的观影记录(编辑页简介/备注回填被清空) |
||
|
|
ff353b1eee |
v8:观影落地(MovieLog 整体迁入统一事件原语)+ 三处 UI 撤除
- Schema v8:events / movie_details / event_attendees / event_photos 四张表, uk_event_source(family_id, source_ref) 作迁移幂等锚 - Movie 模型:时间线/详情/增删改、同行人整组替换、照片逐张管理、感受英→中映射 - ImageSlot 新增 poster(2:3) / ticket(free) / photo(free) 三槽位; free 槽位不裁剪、整图等比缩进框(只缩不放大) - OssDriver::copyObject:同桶 CopyObject(手写 V1 签名,x-oss-* 头按字典序进签名串) - 路由:movies 时间线/详情/新增/编辑/删除/补照片/删照片;同行人按家庭过滤; 访客只读(requireWrite) - UI 撤除:首页「我的一句话」、家谱页「补充我的亲属关系 / 看整个名册」两个按钮; 顶栏加「观影」入口 - 修潜伏 bug:ImageSlot::parseRatio 的正则用 / 作分隔符,字符类里的 / 把模式 提前截断(Unknown modifier ']')—— ratio() 只在上传 POST 里被调到,一直没被观察到 - 线上数据迁移(临时 token 门禁页,用完即删):MovieLog 219 条观影 → events, 14 位观影人新增进名册(带 MovieLog 头像),431 个 OSS 对象同桶拷贝 (movielog/uploads/<dir>/x → family/<dir>/x),review/feelings/影院三段/照片全量搬 |
||
|
|
809443d429 |
v7:家谱树与名册三处口径统一(树上的人自动互相可见)
用户反馈:我把一个人设成配偶、又设了我们的孩子,配偶登录后首页能看到
孩子、家谱也能看到,名册里却看不到——没分享的人却在家谱上看得到,
口径打架。
口径由用户拍板:**以每个人为中心上下三代那棵家谱树(= 首页「我的家人」
同一棵)里的人,自动进他名册(source=kin,默认归公共「家人」组);
树外的人三处都不可见。**
- PersonRoster::syncKinForFamily():整树同步(补缺、撤多余;已存在的
self/shared/seeded 行绝不碰,用户改过的分组/顺序保留)。幂等。
- 同步挂在事件上:建边 / 解边 / 开通账号 / 结构升级回填。
- Relations::addParent/addSpouse 建边后同步;remove/detach 解边后同步。
- 名册不许单独移走 kin 来源的人(服务端拦截 + 人话提示 + 卡片隐藏
移走按钮改提示语):他在首页/家谱照样看得见,单藏名册只会回到口径
打架;想不再看到他,解除关系即自动移出。
- 名册卡片给 kin 来源标「家谱」徽标(来历可辨)。
- Schema v7:widenRosterSourceEnum(⚠️ CREATE TABLE IF NOT EXISTS 不会
更新已存在表的枚举,v6 老库不扩宽则 strict 模式下插 kin 整条被拒、
且被容错吞掉——e2e 的 v6 旧库当场抓出)+ 按已有关系回填 kin 条目。
验证:e2e 309/0、升级专测 82/0、部署静态空跑 78/0、桌面几何 0 失败、
交互 97/0、移动几何 180/0;线上探针 ver=7、enum 含 kin、无升级告警。
|