来源:notes/documents/个人助理移动端入口调研:现状实测与方案分档.md

个人助理移动端入口调研:现状实测与方案分档

调研日期: 2026-09-24(也是所有来源的访问日期) 口径: 网页侧只读 web/ 的代码与 docs/adr/,实测在 web/ 的本地预览夹具上跑(npm run build + npm run preview:local -- --port=4317,视口 390×844、isMobile)。移动端写入侧只读 codex_personal_assistant/ 的既有链路与 ~/.cc-connect/config.toml。 外部事实只取一手资料;查不到的一律写「未能从一手资料确认」,不猜。


一句话结论

移动端不是「没兼容」,是没做完:窄屏降级(≤900px)已经存在并且在 390×844 上不产生横向滚动, 但有五个成立的问题——其中「索引抽屉选中后不关」和「带图片/表格/代码块的笔记整页被裁」是两个真实 阻断。方案分三档:A 只修网页(约一天,先把「能读」做对)、B 装成 PWA(在 A 之上,手机上多一个 图标与 standalone 壳)、C 原生/混合壳(只有在要「手机上写」或要离线/通知/分享时才值得)。 另有一条与三档都正交、且优先级最高的事:生产域名是 *.vercel.app,境内手机上很可能根本打不开。


一、现状实测

1.1 已经有的(不是从零开始)

  • 外壳在 @media (max-width: 900px) 里整体降级:脊变底部 56px 固定条、索引变页顶抽屉(默认只显示 当前项)、页边 gutter 降级为正文上方一行档号。这是 docs/adr/0009 定下的降级,不是补丁。
  • 390×844 下 documentElement.scrollWidth == innerWidth == 390,无横向滚动条。
  • 笔记正文不打包进 bundle:首屏只拉 126 条元数据,正文点开才拉。图片已经有 loading="lazy" 与 decoding="async"(lib/markdown.ts 的 afterSanitizeAttributes)。实测一篇 9 图的笔记只走了 1 张 88 KiB 的图。
  • 会话 cookie 是 HttpOnly; SameSite=Lax; Secure; Max-Age=2592000(30 天),移动浏览器与「添加到主 屏幕」两种场景下都能正常维持。

1.2 五个成立的问题

P1 · 索引抽屉选中后不关(阻断) Shell.tsx 的 indexOpen 是外壳里的局部状态,选中笔记、切换模块都不会把它关掉。390×844 实测: 点第 3 条笔记后 URL 变成 ?n=note_d19d41d7148b4f85(笔记确实加载了),但抽屉仍开,底边在 537px—— 盖住 64% 的屏幕,正文只剩最下面一条缝。用户看到的现象是「点了没反应」。 (indexOpen 定义在只挂载一次的 Shell 里,所以换模块也不重置。)

P2 · 带图片/表格/代码块的笔记整页被裁(阻断) img { max-width: 100% } 的百分比解析不出确定宽度,于是宽元素把整列撑开,.shell { overflow: hidden } 再把它裁掉,而且没有横向滚动可以捞回来。实测三篇:

笔记 最宽元素 结果
个人助理网页版技术栈梳理 无超宽元素 不裁
框架改造选型调研 TABLE 657px 整页右侧被裁
dg 板卡 X5 全模型跑测结果 IMG/PRE/TABLE 451px 整页右侧被裁

第二、三篇里正文一起被裁,不只是表格——标题、段落、代码块都在同一列里被切掉。 受影响面:126 篇笔记里 52 篇含表格行、43 篇含代码块、9 篇含图片。 容器链实测(dg 板卡那篇,括号内为 getBoundingClientRect().width):IMG(451) ← body/measure(451) ← page-canvas(483) ← page(483) ← shell(390, overflow-x: hidden)。缺的是页这一列的 min-width: 0 与宽元素的横向滚动容器。

这条同时说明现有验收方法漏检。 网页版页外空白与页边列:原型与方案 与 网页版阅读面与日程分栏:实施记录 都把「窄屏无横向滚动(documentElement.scrollWidth <= innerWidth)」 当作通过标准——本次实测那个断言成立,而内容正在被裁。因为 .shell 的 overflow: hidden 把 溢出藏进了 scrollWidth 之外。这条断言以后要换成「宽元素自身可横向滚动」。

P3 · 日程月历在手机上不可读 月历是固定的 7 列网格,App.css 的窄屏段是刻意「格子变窄就缩小,而不是把内容藏起来」(US18/20)。 结果实测:格宽 43px、.cell-event 字号 10px,标题被 ellipsis 截成 2–3 个字(个人助…、pg 部…、 还有 2 条)。这不是 bug,是当时的取舍在手机上不成立。

P4 · 底部模块条的触控区偏小 .spine-item 在窄屏是 padding: 2px 0,实测命中区:笔记 32×28、日程 32×28、任务 32×28、 其他文档 61×28。低于常用的 44×44。

P5 · 移动端视口相关的几处没写

  • .shell 用 height: 100vh。MDN 明确 vh 等价于 lvh(large viewport,浏览器界面收起时的高度), 文档原话是「If so, this could obscure content on a full-page display while the browser interface is expanded」——移动浏览器工具栏展开时底部条会被推出可视区。应改 100dvh。
  • 没有 theme-color、没有 viewport-fit=cover、没有 apple-touch-icon。当前 viewport-fit 不是 cover,所以 env(safe-area-inset-*) 这组变量也没有生效余地。

1.3 移动端写入今天已经能用

网页是只读的(docs/adr/0005),但手机上写入并不缺通道:

  • cc-connect 三个 bridge 都在跑:cc-connect-feishu.service、cc-connect-feishu2.service、 cc-connect-qqbot.service(systemctl --user 实测 active)。config.toml 里项目 my-project 的平台是 feishu,工作目录就是本工作区。所以在手机飞书 / QQ 里给机器人发 /note <内容> 就是完整的移动端 写入路径,走的是 plugins/personal-assistant-sync 那个 worker。
  • 飞书「日记」表 → 本地:codex-personal-assistant-feishu-source.service(systemd user timer, 5 分钟一次)跑 scripts/import_feishu_daily_log.py。手机上往飞书表里写,五分钟内进 SQLite。

也就是说「移动端兼容」缺的是读,不是写。


二、方案分档

A 档 · 只修网页

做:P1–P5 五条。不新增架构、不新增运行时依赖。

  • P1 让抽屉在 onSelect / onModule / pushState 后关闭(Shell 收一个 open 状态或加 useEffect)。
  • P2 页这一列补 min-width: 0,并给 .body 的 table/pre 包一层 overflow-x: auto;img 给确定的 包含块而不是靠百分比自解。顺手把 1.2 里那条验收方法改掉。
  • P3 月历在窄屏改为「当天/选中日展开、其余是点阵」,或把月历收成抽屉里的一栏——这是设计决策,要改 docs/adr/0013 而不是加一条 @media。
  • P4 底部条给到 44px 高的命中区。
  • P5 改 100dvh,补 theme-color。

代价:纯前端,一处布局决策(P3)需要过 ADR。收益:手机上从「能开」变成「能读」。 不解决:装不成图标、无离线、无通知、写入口仍在飞书/QQ。

B 档 · 在 A 之上装成 PWA

做:manifest.webmanifest + 192/512 两个图标 + theme-color + apple-touch-icon。

  • 不需要 Service Worker 就能安装。 MDN 说得很直接:「While not a requirement for a PWA to be installable, many PWAs use service workers to provide an offline experience」。Chromium 系的要求是 manifest 里有 name 或 short_name、包含 192px 与 512px 图标、start_url、display 和/或 display_override、prefer_related_applications 为 false 或不存在;外加 HTTPS。
  • Android 装完是桌面图标 + standalone,没有地址栏。iOS 只能手动「添加到主屏幕」。
  • 想再进一步:加 Service Worker 才有离线壳、share_target(安卓系统分享面板里出现你的 App)和 通知。share_target 在 MDN 上是 Limited availability(列出 action 必填、method 可 GET/POST), 实际只有 Chromium 系支持。
  • iOS 的通知有个前置条件:WebKit 在 Safari 16.4 的说明里写「iOS and iPadOS 16.4 add support for Web Push to web apps added to the Home Screen」——必须先加到主屏幕,否则没有 Web Push。

代价:两个图标 + 一个 JSON + index.html 几行;另外要处理 standalone 下的登录回跳(GitHub OAuth 会 跳出壳,回到浏览器)。收益:一个图标、没有地址栏、可能的离线与通知。 不解决:域名可达性(见下)。

C 档 · 原生 / 混合壳

两条路:

  • Capacitor:官方文档说明做 Android 需要 Android Studio + Android SDK 两样(JDK 由 Android Studio 自带安装),产出 APK 可以侧载、不必上架。适合「要一个真的 App」——能有分享入口、后台、 离线打包含静态资源、以及用 JS Bridge 补写入能力(这是 A/B 都给不了的)。
  • TWA / Bubblewrap:WebView 之外的另一条 Android 侧路,但它要求网站通过 Digital Asset Links 校验, 还要能托管 /.well-known/assetlinks.json。Bubblewrap 与 Android 官方文档本次都不可达,细节未能从 一手资料确认。
  • 自己写壳(WebView + addJavascriptInterface):最自由,也是唯一能顺手把「手机上写」做进产品的地方。 Android 官方 WebView 文档本次不可达,细节未确认。

代价:一台 Android 构建环境 + 签名 + 一条更新链路;iOS 侧还要 Mac。只有在要「手机上写」或要离线/ 通知/分享时才值得——而这些需求里,「手机上写」今天已经被飞书/QQ bot 满足了。

与三档正交的两件事

域名可达性(优先级最高) 生产域名是 https://vite-react-tawny-one-14.vercel.app(web/docs/operations.md),本仓库已经记录 *.vercel.app 被 DNS 污染、解析到 2a03:2880:...:face:b00c 这类假地址。本机要靠 mcli exec -- 走代理才能拿到 200。手机在境内运营商网络上大概率同样打不开——A 档和 B 档做得再好, 手机上打开是 404 就都没意义。 这条要单独先验、先解(绑一个自定义域名)。Vercel 侧加自定义域名的 官方细节未能从一手资料确认(vercel.com 文档本次未核实)。

写入口 docs/adr/0005 定的是只读。要在网页里写,需要一条新 ADR 与一套写接口,而不是 ui 上的改动。


三、建议

  1. 先验域名。拿手机在境内网络上打开一次生产域名。打不开就先解决域名,其余都排在它后面。
  2. 做 A 档,其中 P1、P2 是阻断级,P5 是几行 CSS。P3(月历)单独走一次设计决策。
  3. A 之后再加 B 档(manifest + 两个图标),成本很低,能在手机上换来一个图标与 standalone。
  4. C 档先不做。等出现「必须在地铁上离线读」或「必须有个真 App 收系统分享」这类具体需求再启动; 写入需求已经有飞书/QQ bot 覆盖。

四、来源

一手资料(均为 2026-09-24 访问):

  • MDN《Making PWAs installable》:可安装性对 Chromium 系浏览器的 manifest 字段要求、HTTPS 要求、 「Service Worker 不是可安装的前提」——https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Making_PWAs_installable
  • MDN《BeforeInstallPromptEvent》:标记为 Limited availability—— https://developer.mozilla.org/en-US/docs/Web/API/BeforeInstallPromptEvent
  • MDN《Web app manifest · share_target》:注册为系统分享目标、action 必填、method 取 GET/POST、 标记为 Limited availability——https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target
  • MDN《length》:vh 等价于 lvh、svh/lvh/dvh 与浏览器工具栏收缩的关系,且该页标注 Baseline Widely available——https://developer.mozilla.org/en-US/docs/Web/CSS/length
  • WebKit《WebKit Features in Safari 16.4》(2023-03-27):iOS/iPadOS 16.4 的 Web Push 只对「已添加到 主屏幕的 web app」开放;第三方浏览器也可提供「Add to Home Screen」—— https://webkit.org/blog/13966/webkit-features-in-safari-16-4/
  • Capacitor《Environment Setup》:Android 需要 Android Studio 与 Android SDK,JDK 由 Android Studio 安装——https://capacitorjs.com/docs/getting-started/environment-setup

仓库内依据:

  • web/docs/adr/0005-v1-read-only-notes.md(只读)
  • web/docs/adr/0009-shell-contract-modules-replace-index-and-page.md(外壳契约与窄屏降级)
  • web/docs/adr/0013-page-canvas-and-split-reading.md(页内构图)
  • web/docs/operations.md(生产域名、*.vercel.app 污染、预览夹具)
  • web/CONTEXT.md(Entry Channel、外壳、索引、页)
  • notes/documents/网页版页外空白与页边列:原型与方案.md、网页版阅读面与日程分栏:实施记录.md (窄屏验收方法,本次修正)

五、未能从一手资料确认

  • Chrome 官方的 installability 判定页(developer.chrome.com、web.dev 本次不可达)。可安装性结论改用 MDN 的《Making PWAs installable》支撑。
  • Bubblewrap / TWA 的官方文档与 Digital Asset Links 校验流程(docs.bubblewrap.io 不可达)。
  • Android 官方 WebView addJavascriptInterface 文档与 Android 分享 intent-filter 文档 (developer.android.com 不可达)。
  • Vercel 侧自定义域名的官方流程,以及 Vercel 官方对「中国大陆访问」的任何说明。
  • iOS Safari 16.4 之后「添加到主屏幕」是否读取 Web App Manifest、apple-mobile-web-app-capable 的废弃状态:MDN 相关页本次未取到。