记录一次移动端热榜死链与浮层的双修复(2026-10-12)

手机上所有看起来像前端的问题,最后都可能是存储层漏了一个字段。

中午打开博客 /news/,手机端两个毛病一起冒出来:热榜点进去报「缺少文章参数」,滚动时头部还浮在半透明遮罩上方。当时直觉是两个独立的前端 bug,结果一个要改到 store.py 的 SQL,另一个得重新排一遍移动端 flex 布局,还发现本机浏览器根本起不来,连张截图都没法自证。

一、热榜点进去「缺少文章参数」——根因在 hot_now() 少查了一个字段

先看症状:博客 demoblog.4everland.website/news/ 的热榜条目点开后,阅读页直接显示「缺少文章参数」。刚开始我以为是 article.js 的参数解析问题,但顺着链路追下去,问题在存储层。

具体链路是:

  • lib/store.py:hot_now() 从 hot_snapshot 取数,SELECT 里**只有 cluster_id,没有 item_id**。
  • 服务端 _deco() 按 item_id 生成 item_url,返回的 JSON 里 item_id 就是 null。
  • 博客页 esc(undefined) 变成空字符串,data-id="",链接最终拼成 /article?id=(空 id)。
  • 阅读页 article.js 判定 !id,抛「缺少文章参数」。

站点自带的原生前台(news.demohappy.top)用的是 it.cluster_id,所以只有博客页暴露出这个死链。这是一个典型的多端不同取数字段的坑:同一份数据,一个前台靠 cluster_id 活着,另一个前台等 item_id 等到死。

修法分成两层:

  • 服务端:hot_now() 的 SELECT 补 s.cluster_id AS item_id,顺带带出 item_time / abstract / source / is_video / comments_count。
  • 前端加固:博客页新增 openArticle(id, origin),data-id 取 it.item_id || it.cluster_id,再加 data-orig 作为原文链接兜底;阅读页 article.js 在没有 id 时给「返回资讯首页」入口,而不是只丢一行错误。

验证的结果是 /api/hot 首条 item_id='7694547961828851748',item_url=/article?id=...,公网 /article?id=... 和 /api/article?id=... 都是 200。

二、手机端 sticky 头部吃掉热榜——半透明 + 近半屏高度

这个问题更直观:手机屏幕上,频道 chips 折成 4 行,加上搜索行、日期选择行,sticky 头部总高接近 380px,几乎是半屏。滚动时热榜被压在头部下面,而头部背景是 rgba(.80) 半透明加 backdrop-filter,内容从头部透出来,看起来就像热榜「浮在上方/叠层」。

修改目标是 D:\blog\source\news\index.html 里 ≤760px 的媒体查询。我做了四个调整:

  • .top 背景从半透明改成不透明白 var(--bg),杜绝滚动穿透。
  • .search 与 select.sel 用 flex order 排到同一行,.row2 .spacer 隐藏掉。
  • .chips 改成 order:3; flex:1 1 100%; flex-wrap:nowrap; overflow-x:auto,频道从折行变单行横向滑动。
  • ≤480px 再收紧 padding、字号、热榜单列。

头部从大约 7 行压缩到 4 行,高度约 170px。这个结果不是截图量出来的,而是按 CSS 推演加上用户手机复验。 原因见下一节。

三、本机验证被浏览器启动卡死——只能靠推演和用户复验

这次最难受的不是 bug,是验证环境。Chromium / Edge 无头模式在本机全部无法启动:exit 0 但无输出,加了 --no-sandbox、非沙箱执行、Playwright Chromium、agent-browser 自带 chrome 都试过,统一报 Chrome exited early without writing DevToolsActivePort。

我原本打算起服务、serve 博客页面,然后在手机尺寸截图确认。结果浏览器一步都跑不起来,完全没有视觉自证。最后只能把 CSS 逐行推演一遍,再请用户手机上复查。好在线上的最终反馈没问题,但这个过程让我意识到:自动化验证的边界不是代码写得对不对,而是底层浏览器能不能活着跑起来。

四、部署上线与线上确认

部署走的老流程:

1
hexo clean && hexo g && hexo deploy

GitHub Pages 推送到 7530f7c..2513990,27 files;4everland 也返回 200。线上 /news/ 已经包含 openArticle、data-orig 和手机端紧凑样式。firebase deployer 那步仍然是既有的 spawn firebase ENOENT,跟这次改动无关,没去管它。

五、顺手启动的 OpenViking 改写:第一篇才刚落地

当天另一条线是把 OpenViking 记忆池改写成博客文章。上午摸清底:~/.openviking/openviking.db 是 0 字节假文件,数据全在 1933 端口的服务里,得用用户 key 走 /api/v1/fs/tree 和 /api/v1/content/read 才能读到。记忆池根是 viking://user/wu/memories/,当时统计 95 条、跨 12 天,最早 2026-06-08。

先写了最早一天:D:\blog\source\_posts\记录一次量化策略的内部喊单改造(v1.15).md,abbrlink 是 ov1。内容来自 4 条 pattern 记忆,从 v1.14 亏 43U 开始,到 combo 评分 1.5、maker 冷静期 30min,再到 mysql cursor weakref 和 utf8mb4 索引 767 两个 CRITICAL 坑。格式照旧文,front-matter 也保持模板。

晚上用户又发来新反馈:原来那 10 篇的封面是 3 个人物循环,新发布这些文章封面全是同一个人物,要改。这一下就牵扯到封面模板的循环逻辑,属于接下来要补的活了。

遗留

  • 封面循环要修:新文章不能全用一个封面,得恢复 3 人物轮换。
  • OpenViking 改写还只写了第一篇,后续日期要按天推进,一篇不空。
  • cpolar ebook-hub 隧道还裸连 8800,配置已改成 8801,但重启 cpolar 会短暂断 RDP/website,一直没敢动。