Skip to content

feat(mobile): 支持将对话分享为图片 - #2188

Merged
MagicLizi merged 20 commits into
mainfrom
dash/mobile-share-conversation-image
Aug 10, 2026
Merged

MagicLizi merged 20 commits into
mainfrom
dash/mobile-share-conversation-image

Conversation

@dashhuang

@dashhuang dashhuang commented Aug 8, 2026 •

Copy link
Copy Markdown
Member

这次改了什么

摘要

为手机版任务对话补齐“分享为图片”能力:用户可以从消息操作栏进入分享模式,勾选多条消息后生成 PNG 并打开系统分享面板;iOS 在任务对话内完成系统截图时,还会自动进入同一套分享模式,并按截图瞬间实际可见面积预选消息。

变更类型

  • feat 新功能
  • fix 缺陷修复
  • refactor / perf 重构或性能优化
  • docs / test / chore 文档、测试或工程维护
  • 其他:

范围

  • 关联 Issue / 需求:Desktop 对话分享为图片 feat(desktop): 增加对话分享为图片 #1879 的 Mobile 补齐
  • 本 PR 包含:
    • 在消息复制按钮右侧增加 iOS 风格分享图标,并扩大移动端触控命中区。
    • 分享模式下隐藏消息操作信息,消息右移并在左侧显示选择框;支持全选,取消全选时恢复用户原先的勾选集合。
    • 将选中消息渲染为 PNG;跳选位置显示淡色 ⋯,代码块和宽表格在导出图中展开,页脚使用 Cindy 角色图与 Logo。
    • iOS 监听系统截图事件,按顶部栏、底部输入区和消息行的真实窗口矩形计算可见比例,自动勾选可见面积至少 10% 的顶层普通消息。
    • iOS 使用原生 WKWebView 生成 PNG;缺少原生 renderer 时使用隐藏 React Native WebView 降级。
    • KaTeX / Mermaid 资源构建时从根依赖同步为随包内联资源(当前生成文件约 4.36 MB);运行时仅在页面含富内容时注入,分享 CSP 不再放行外部 CDN,资源执行失败降级为源码。
  • 明确不包含:Android 系统截图自动触发;Android 真机分享验证;折叠工作组和子 Agent 内部消息的截图自动选择。
  • 用户可见变化:任务对话消息操作栏新增分享入口;进入分享模式后可以多选消息并分享图片;iOS 截图后可自动进入相同分享界面。
  • 是否存在 breaking change:无 API breaking change;但新增原生模块会改变 Mobile runtime fingerprint,存量安装需等待新版原生包。

UI 变化

  • iOS Simulator:已手工验证消息分享入口、选择态、截图触发选择、PNG 导出和系统分享面板。
  • 引用的设计规范:
    • docs/design-rules/DESIGN.md §4 Buttons:全选和分享主操作使用 pill 形态与语义层级。
    • docs/design-rules/DESIGN.md §5 Layout Principles:选择框使用统一间距阶梯,分享栏与消息内容保持清晰层级。
    • docs/design-rules/DESIGN.md §10 Light / Dark Dual-Mode Delivery Gate:颜色全部走 ThemeColors 语义 token,覆盖默认、选中、按下、禁用和加载态。
    • apps/mobile/docs/mobile-design-guide.md §5 间距 / 圆角 / 触控 / 安全区:可见图标保持紧凑,hitSlop / 外层命中区补足移动端触控范围。
    • apps/mobile/docs/mobile-design-guide.md §6 图标:复用 lucide-react-native 的 Share 图标,与 Desktop 分享动作保持一致。

怎么验证的

自动验证

bash /Users/dash/Code/XD/dash/Skills/git/scripts/run-unit-gate.sh /Users/dash/Code/Cindy/cindy-mobile-share-conversation-image
结果:通过(GATE_EXIT=0;apps/mobile 本轮 3293 项测试通过)

pnpm --filter mobile run --if-present typecheck
结果:通过

pnpm --filter mobile exec vitest run src/__tests__/conversationShareWebViewHtml.test.ts src/__tests__/selectableMarkdownHtml.test.ts src/__tests__/messageActionDesktopFirst.test.ts src/__tests__/sessionHeaderDesktopFirst.test.ts src/__tests__/sessionComposerDesktopFirst.test.ts
结果:5 个测试文件、48 项测试通过

pnpm check:i18n-glossary
结果:通过

pnpm check:dco
结果:通过,3 个 commit 均带 Signed-off-by

xcodebuild -workspace apps/mobile/ios/Cindy.xcworkspace -scheme Cindy -configuration Debug -sdk iphonesimulator -derivedDataPath /private/tmp/cindy-mobile-share-derived CODE_SIGNING_ALLOWED=NO build
结果:通过(BUILD SUCCEEDED)

git diff --check
结果:通过

node apps/mobile/scripts/ci-fingerprint.mjs compare --base /tmp/cindy-mobile-share-fingerprint-base.json --current /tmp/cindy-mobile-share-fingerprint-after.json
结果:确认 iOS / Android fingerprint 均变化;最新精确哈希以 CI fingerprint guard 为准

手工验证

  • 平台:iOS Simulator,iPhone 17 Pro,App com.xd.cindy。
  • Worktree:/Users/dash/Code/Cindy/cindy-mobile-share-conversation-image。
  • Metro:端口 8081,mobile:sim:whoami 已确认归属当前 worktree。
  • 手工验证时的 __DEV__ build label:dash/mobile-share-conversation-image@66fc9ba6e+b68a186b61。
  • 已验证:分享按钮位置与触控、消息缩进和选择框、全选恢复、截图触发、可见消息预选、PNG 生成、Cindy 页脚、iOS 系统分享面板。

未执行的验证

  • Android 真机 / 模拟器未验证;Android 仅预期支持手动选择、PNG 导出和系统分享,不支持截图自动触发。
  • Light / Dark 均已通过语义 token 实现,但未分别完成双模式实机目检。
  • rebase 到最新 origin/main 后未重新启动 iOS Simulator;rebase 后已重新通过 Mobile typecheck 与完整单测门禁。

风险

风险分类

  • 无已知风险
  • SQLite / migration
  • system prompt
  • 协议兼容
  • 权限 / 安全 / 用户数据
  • 存量插件兼容(批准状态 / 指纹 / manifest 校验 / 安装布局 / 包格式)
  • 原生层 / fingerprint / OTA
  • 跨平台差异
  • 其他:

影响与回滚

  • 影响范围:
    • 新增 Apple Expo Module,监听 UIApplication.userDidTakeScreenshotNotification,并通过非持久化 WKWebView 将分享 HTML 渲染为 PNG。
    • 分享内容生成前继续执行敏感文本脱敏;PNG 仅写入 app cache 临时目录,再交给系统分享面板,不新增会话数据持久化。
    • iOS 和 Android runtime fingerprint 均变化。冷更不可避免,因为 iOS 截图通知和原生 PNG renderer 必须进入原生二进制;存量安装无法通过 OTA 获得完整能力。
    • iOS 支持截图自动触发;Android 只支持手动分享,且尚未真机验证。
  • 发版节奏建议:随下一次 iOS / Android 原生版本统一冷更出包;合并前需要仓库指定把关人对本次 fingerprint / 冷更影响显式确认,不能按普通 OTA 功能发布。
  • 回滚 / 降级方式:revert 本 PR 可关闭 JS 入口;已安装新版原生包中的模块会保持未调用状态,不涉及数据迁移。若需要从二进制中移除模块,则随下一次冷更版本删除。

提交前检查

  • 已 review 完整 diff
  • 每个 commit 都带 DCO 签名(git commit -s,见 DCO)
  • UI 改动已在「UI 变化」注明引用的设计规范章节(不涉及 UI 则跳过)
  • 未提交凭证、令牌或授权文件
  • 已补充必要文档
  • 已确认测试结果或说明未执行原因

@dashhuang
dashhuang requested a review from a team as a code owner August 8, 2026 10:37
Copilot AI lite review requested due to automatic review settings August 8, 2026 10:37

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dd454cc285

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/mobile/app/sessions/[sessionId].tsx
Comment thread apps/mobile/app/sessions/[sessionId].tsx Outdated
Comment thread apps/mobile/src/session/ShareSelectionBar.tsx
@greptile-apps

greptile-apps Bot commented Aug 8, 2026 •

Copy link
Copy Markdown

Greptile Summary

本 PR 为移动端任务对话新增多选消息并分享为 PNG 的能力,同时支持 iOS 截图后按当前可见消息自动进入分享模式。

  • 新增分享选择状态、消息复选框、全选与系统分享流程
  • 新增 iOS 截图监听和原生 WKWebView PNG 渲染器,并提供 React Native WebView 降级路径
  • 将 KaTeX、Mermaid 资源改为随包内联,并为原生与降级渲染路径增加一致的资源限制和像素预算
  • 分享投影仅保留可见消息内容及附件名称,避免携带附件字节和来源 URI

Confidence Score: 5/5

此 PR 当前看起来可以安全合并。

此前报告的外部资源请求、完整尺寸渲染预算和附件图片解码问题在当前代码中均已修复,没有仍然存在的阻塞性故障。

Important Files Changed

Filename Overview
apps/mobile/app/sessions/[sessionId].tsx 串联截图事件、可见消息预选、分享状态、HTML 生成、PNG 导出及系统分享生命周期。
apps/mobile/modules/xdt-screenshot-monitor/ios/XdtScreenshotMonitorModule.swift 新增 iOS 截图通知桥接和带源尺寸、输出像素预算的非持久化 WKWebView PNG 渲染器。
apps/mobile/src/session/conversationShareWebViewHtml.ts 构建离线分享 HTML,使用严格 CSP、内联富文本资源、外部图片清理和降级导出预算。
apps/mobile/src/session/conversationShareProjection.ts 将消息投影为分享安全数据,仅保留可见正文、紧凑引用及不含 URI 的附件名称。
apps/mobile/scripts/sync-rich-content-assets.mjs 从根依赖同步 KaTeX 和 Mermaid 资源,并将 KaTeX 字体转换为内联 data URI。
apps/mobile/src/session/ConversationShareWebView.tsx 在缺少原生渲染器时提供隐藏 WebView PNG 导出和受控导航降级路径。

Sequence Diagram

sequenceDiagram
  participant User as 用户
  participant Screen as 会话页面
  participant Store as 分享选择状态
  participant Builder as 分享 HTML 构建器
  participant Native as iOS 原生渲染器
  participant Fallback as RN WebView 降级渲染器
  participant Share as 系统分享面板
  User->>Screen: 点击分享或完成 iOS 截图
  Screen->>Store: 进入分享模式并预选消息
  User->>Store: 调整消息选择
  Screen->>Builder: 投影选中消息并生成受 CSP 约束的 HTML
  Screen->>Native: 请求生成 PNG
  alt 原生渲染器可用
    Native-->>Screen: PNG Base64
  else 原生渲染器不可用
    Screen->>Fallback: 导出 PNG
    Fallback-->>Screen: PNG Base64
  end
  Screen->>Share: 写入临时文件并打开分享面板
Loading

Reviews (22): Last reviewed commit: "fix(mobile): harden share resource lifec..." | Re-trigger Greptile

Comment thread apps/mobile/src/session/conversationShareWebViewHtml.ts Outdated
Comment thread apps/mobile/src/session/conversationShareWebViewHtml.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

为 Mobile 端会话页补齐「对话分享为图片」能力:进入分享模式后可多选消息生成 PNG 并调用系统分享;iOS 额外支持监听系统截图并按可见区域自动预选消息,同时引入原生 Expo Module(并提供 WebView 降级渲染)。

Changes:

  • 新增分享选择态的状态管理与 UI(消息复选框、底部分享操作条),并在消息操作栏加入分享入口
  • 新增对话导出 HTML/PNG 的渲染链路(JS WebView 导出 + iOS 原生 WKWebView 渲染)
  • iOS 增加截图监听,按实际可见区域计算并自动进入分享模式预选消息;补齐相关 i18n 与测试断言

Reviewed changes

Copilot reviewed 21 out of 23 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pnpm-lock.yaml 增加 xdt-screenshot-monitor 本地模块依赖与锁文件条目
apps/mobile/package.json 引入 xdt-screenshot-monitor 到 mobile 依赖
apps/mobile/modules/xdt-screenshot-monitor/package.json 新增截图监听/原生渲染模块的包定义
apps/mobile/modules/xdt-screenshot-monitor/src/index.ts Expo 可选原生模块封装:截图事件 + HTML→PNG 能力探测与调用
apps/mobile/modules/xdt-screenshot-monitor/ios/XdtScreenshotMonitorModule.swift iOS 侧监听截图通知并提供 WKWebView 生成 PNG 的原生实现
apps/mobile/modules/xdt-screenshot-monitor/ios/XdtScreenshotMonitor.podspec iOS Pod 规格定义,接入 ExpoModulesCore
apps/mobile/modules/xdt-screenshot-monitor/expo-module.config.json Expo module 配置(Apple 平台注册)
apps/mobile/src/session/shareSelectionStore.ts 分享选择态的外部 store + shareable 消息判定与 hooks
apps/mobile/src/session/ShareSelectionBar.tsx 分享模式底部操作条(取消、全选/取消全选、分享)
apps/mobile/src/session/ShareMessageCheckbox.tsx 分享模式下的消息复选框组件
apps/mobile/src/session/selectableMarkdownHtml.ts 抽出可复用的 Markdown fragment HTML 与导出 CSS(供分享渲染复用)
apps/mobile/src/session/conversationShareWebViewHtml.ts 将选中消息渲染为可导出 PNG 的 HTML(含页脚与导出脚本)
apps/mobile/src/session/ConversationShareWebView.tsx 隐藏 WebView 渲染并导出 PNG、写入临时文件供系统分享
apps/mobile/src/session/MessageRenderer.tsx 分享入口/选择态 UI 注入;提供“读取实际可见 shareable 消息”的测量回调
apps/mobile/app/sessions/[sessionId].tsx 会话页接线:截图触发选择、导出/分享链路、隐藏 composer 相关 UI、挂载 WebView 降级
apps/mobile/src/i18n/locales/zh-CN/session.json 增加分享图片相关文案(中文)
apps/mobile/src/i18n/locales/en/session.json 增加分享图片相关文案(英文)
apps/mobile/src/i18n/locales/ja/session.json 增加分享图片相关文案(日文)
apps/mobile/src/i18n/locales/ko/session.json 增加分享图片相关文案(韩文)
apps/mobile/src/tests/sessionHeaderDesktopFirst.test.ts 更新源码字符串断言以匹配新增 ref
apps/mobile/src/tests/sessionComposerDesktopFirst.test.ts 更新 env import 断言以匹配新增导入
apps/mobile/src/tests/messageActionDesktopFirst.test.ts 更新断言以匹配分享态 gating 与结构调整
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread apps/mobile/src/session/conversationShareWebViewHtml.ts Outdated
Comment thread apps/mobile/app/sessions/[sessionId].tsx
Comment thread apps/mobile/src/session/ShareSelectionBar.tsx
Copilot AI review requested due to automatic review settings August 8, 2026 11:12

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e2361ab04a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/mobile/src/session/conversationShareWebViewHtml.ts Outdated
Comment thread apps/mobile/src/session/ShareMessageCheckbox.tsx

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 23 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
Suppressed comments (3)

apps/mobile/app/sessions/[sessionId].tsx:5488

  • 当前实现里导出过程(conversationShareBusy=true)并不会阻止用户继续勾选/取消勾选消息;在 Android(以及 iOS 未启用原生 renderer 的降级路径)会导致 ConversationShareWebView 的 html 频繁变更触发 WebView reload,从而打断 in-flight 的 export(pendingRef 只能等超时),最终出现“生成超时/失败”且导出的内容可能与用户最终选择不一致。

建议在生成期间冻结选择集合(禁用消息复选框/全选按钮之外的选择入口),或在开始导出时把用于渲染/导出的 HTML 快照固定到一次操作结束,避免 WebView 重新加载。

  const shareSelectedConversation = useCallback(async () => {
    if (
      conversationShareBusy
      || !shareSelectionActive
      || selectedShareMessages.length === 0

apps/mobile/modules/xdt-screenshot-monitor/ios/XdtScreenshotMonitorModule.swift:127

  • iOS 原生 WKWebView 渲染 PNG 的路径在 didFinish 后就立即计算尺寸并 takeSnapshot,但这里没有像 JS 降级版一样等待 footer 里的 data: 图片(角色图/Logo)完成解码与布局;在某些机型/压力场景下可能截到“页脚图片为空/只剩文字”的结果。

建议在截图前显式等待 document.images 全部 load/complete(例如使用 iOS 16+ 的 callAsyncJavaScript 等待 Promise,或轮询图片 complete 状态),再进行尺寸测量与 snapshot。

  func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    let script = """
      (function () {
      const stage = document.getElementById('xdt-content');
      if (!stage) throw new Error('stage-not-found');

apps/mobile/package.json:65

  • 这里新增了本地 Expo 原生模块依赖(xdt-screenshot-monitor),会改变 Mobile runtime fingerprint(属于冷更边界输入)。按仓库规则,这类 PR 需要仓库指定把关人对冷更影响做显式确认后才能合并/随原生包发版;请确认合并路径与发版节奏已对齐(不能按普通 OTA 发布)。
    "xdt-ios-app-distribution": "file:./modules/xdt-ios-app-distribution",
    "xdt-mobile-realtime-audio": "file:./modules/xdt-mobile-realtime-audio",
    "xdt-screenshot-monitor": "file:./modules/xdt-screenshot-monitor",
    "xdt-tapdb": "file:./modules/xdt-tapdb",

@MagicLizi MagicLizi added status:threads-open 还有未 resolve 的评审讨论(review-pr 自动维护,仅展示) touches:cold-update 改动影响移动端冷更新(review-pr 自动维护,仅展示) touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示) touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示) touches:security 改动碰到安全边界(review-pr 自动维护,仅展示) labels Aug 8, 2026
Copilot AI review requested due to automatic review settings August 8, 2026 12:23
@dashhuang
dashhuang force-pushed the dash/mobile-share-conversation-image branch from e2361ab to a96af4b Compare August 8, 2026 12:23
Comment thread apps/mobile/src/session/conversationShareWebViewHtml.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a96af4bc9e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/mobile/app/sessions/[sessionId].tsx Outdated
Comment thread apps/mobile/src/session/ConversationShareWebView.tsx Outdated
Comment thread apps/mobile/src/session/conversationShareWebViewHtml.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 24 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
Suppressed comments (2)

apps/mobile/app/sessions/[sessionId].tsx:5425

  • selectedShareMessages 的 useMemo 目前依赖 shareSelectionCount,但 shareSelectionCount 只反映数量;当选中集合变化但数量不变时,这里不会重新计算,导出内容可能与当前勾选不一致。建议改为依赖一个随选中集合变化的 key(例如 useSyncExternalStore 读 getSelectedIds().join('|'))。
    return shareSelectionStore
      .getSelectedIdsInOrder(allShareableIds)
      .map((clientId) => shareMessageById.get(clientId))
      .filter((message): message is ConversationShareMessage => message !== undefined);
  }, [allShareableIds, shareMessageById, shareSelectionActive, shareSelectionCount]);

apps/mobile/app/sessions/[sessionId].tsx:839

  • 当前页面只通过 useShareSelectionCount() 订阅了选中数量;当 shareSelectionStore 发生“选中集合变化但数量不变”(例如一次事件里连续 toggle 两条消息、或未来用 setSelection 替换为等长集合)时,useSyncExternalStore 会因 snapshot 相等而跳过重渲,导致导出 HTML/PNG 仍使用旧的选中集合。建议额外订阅一个会随选中集合变化而变化的 snapshot key,并在后续 memo 依赖中使用。

This issue also appears on line 5421 of the same file.

  const sessionId = readRouteParam(params.sessionId) ?? '';
  const shareSelectionActive = useShareSelectionActive(sessionId);
  const shareSelectionCount = useShareSelectionCount();
  const deviceId = readRouteParam(params.deviceId) ?? remoteSessionStore.getSessionDeviceId(sessionId) ?? '';

@MagicLizi MagicLizi added the touches:large-diff 改动量较大(review-pr 自动维护,仅展示) label Aug 8, 2026
@MagicLizi

Copy link
Copy Markdown
Contributor

@dashhuang 👋 这个 PR 还有 5 条 review conversation 没 resolve(apps/mobile/src/session/conversationShareWebViewHtml.ts / apps/mobile/modules/xdt-screenshot-monitor/ios/XdtScreenshotMonitorModule.swift / apps/mobile/app/sessions/[sessionId].tsx / apps/mobile/src/session/ConversationShareWebView.tsx),auto-review 因此暂时跳过、没法继续审查 / 合并。

如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。

Copilot AI review requested due to automatic review settings August 8, 2026 21:26
@dashhuang
dashhuang force-pushed the dash/mobile-share-conversation-image branch from a96af4b to 52b5e3e Compare August 8, 2026 21:26

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 52b5e3e85e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/mobile/app/sessions/[sessionId].tsx Outdated
Comment thread apps/mobile/app/sessions/[sessionId].tsx Outdated
Comment thread apps/mobile/src/session/ConversationShareWebView.tsx

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 36 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
Suppressed comments (4)

apps/mobile/src/session/ConversationShareWebView.tsx:107

  • 上面把 readyWaitersRef 的 waiter 改为 (error) => void 后,这里需要显式以 null 表示 ready 成功;否则会把 undefined 当成错误分支处理/类型不匹配。
    if (record.type === "conversation-share-ready") {
      readyRef.current = true;
      const waiters = readyWaitersRef.current.splice(0);
      waiters.forEach((waiter) => waiter());
      return;

apps/mobile/src/session/ConversationShareWebView.tsx:140

  • 组件卸载时当前实现会把 readyWaitersRef 里的 waiter 直接执行(等价于“ready 成功”),这会让 exportPng() 的 await 继续往下走并尝试对已卸载的 WebView 注入脚本。更合理的是在卸载时用 Error reject 所有 waiter,并用同一个 Error reject 所有 pending export。
    () => () => {
      readyWaitersRef.current.splice(0).forEach((waiter) => waiter());
      for (const pending of pendingRef.current.values()) {
        clearTimeout(pending.timer);
        pending.reject(new Error("conversation share webview unmounted"));

apps/mobile/src/session/ConversationShareWebView.tsx:60

  • readyWaitersRef 里存的是包装后的回调,但超时分支用 (waiter) => waiter !== resolve 去移除,永远匹配不上,导致 waiter 泄漏并可能在后续 ready 时被错误触发。建议把 waiter 本体保存在局部变量里,并让 waiter 同时承载 resolve/reject(ready 时 resolve,超时/卸载时 reject)。

This issue also appears in the following locations of the same file:

  • line 103
  • line 136
          await new Promise<void>((resolve, reject) => {
            const timer = setTimeout(() => {
              readyWaitersRef.current = readyWaitersRef.current.filter(
                (waiter) => waiter !== resolve,
              );

apps/mobile/app/sessions/[sessionId].tsx:74

  • AUTH_REGION 在本文件内没有被引用(仅出现在 import 行),会增加噪音并可能在开启 noUnusedLocals/noUnusedParameters 或 lint 时变成告警。若确实不需要,建议移除该 import(同时更新对应的 desktop-first 断言测试)。
import { AUTH_REGION, DEVICE_LINK_API_BASE_URL, MOBILE_VISUAL_MOCK_ENABLED } from '@/config/env';

@MagicLizi

Copy link
Copy Markdown
Contributor

@dashhuang 👋 这个 PR 还有 3 条 review conversation 没 resolve(apps/mobile/app/sessions/[sessionId].tsx / apps/mobile/src/session/ConversationShareWebView.tsx),auto-review 因此暂时跳过、没法继续审查 / 合并。

如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。

Signed-off-by: Dash <dashhuang@gmail.com>
@dashhuang
dashhuang force-pushed the dash/mobile-share-conversation-image branch from 83f0ba6 to 87a073d Compare August 9, 2026 17:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 44 out of 47 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
Suppressed comments (2)

apps/mobile/app/sessions/[sessionId].tsx:5583

  • exportConversationSharePng 里原生 renderer renderConversationShareHtmlToPng 一旦 reject/throw,会直接中断并进入上层 catch,从而完全跳过 WebView 降级路径;同时 JSX 里也只在 !conversationShareNativeRendererAvailable 时才挂载 ConversationShareWebView,即便想在原生渲染失败时降级也拿不到 webview ref。建议明确选择:要么把原生渲染异常也纳入降级(try/catch 后回落 WebView,并确保 WebView 在 share 模式下可用/可延迟挂载),要么在注释/策略上说明“原生失败不降级”的原因,避免后续误解为已具备可靠兜底。
        outbox: outboxDisplayItems,
        hiddenClientIds: queueHiddenClientIds,
        sendingClientIds: sendingQueueBadgeClientIds,
        editingClientId: queueEditing?.clientId ?? null,
        steeringClientIds: new Set(inputProjection.steeringQueueClientIds),
        presentationByClientId,
        previewByOssRef: sentPreviewByOssRefRef.current,
      });
    },
    [
      inputProjection,
      outboxDisplayItems,

apps/mobile/src/session/MessageRenderer.tsx:1840

  • 分享选择模式的判据目前是 const shareSelectionActive = actions.shareSelectionActive === true && shareableMessage;,导致非 shareable 的消息在分享模式下仍可能渲染完成态操作条(复制/时间/More 等)。这与同段注释“分享态只保留与导出图片一致的消息内容,不显示操作图标、时间或费用”不一致,也容易在分享模式里出现可交互控件干扰选择/截图预选。建议把“是否处于分享模式”与“该消息是否可分享”拆开,并用全局 share 模式来 gate 操作条。
  const shareableMessage = isShareableMessage(item.message);
  const handleShareableMessageViewChange = useCallback((view: View | null) => {
    actions.onShareableMessageViewChange?.(clientId, view);
  }, [actions.onShareableMessageViewChange, clientId]);
  const shareSelectionActive = actions.shareSelectionActive === true
    && shareableMessage;
  const isFirstUserMessage = item.message.kind === 'user' && clientId === actions.firstUserMessageClientId;
  const copyText = buildMobileMessageCopyText(item.message);
  const canUseCompletedActions = !isStreamingAssistant;
  // 操作行只挂在每轮收尾正文、且该行确实是一条发言(判据见
  // mobileMessageShowsActionBar):中间句不再逐条带复制/分叉/时间,系统边界卡整行
  // 不挂。分享态只保留与导出图片一致的消息内容,不显示操作图标、时间或费用。
  // user 消息、流式「生成中」状态与正文的文本选择(canSelectVisibleText)
  // 不受影响。
  const showCompletedActionBar = !shareSelectionActive && mobileMessageShowsActionBar({
    hasSystemCard: !!item.message.systemCardType,

Copilot AI review requested due to automatic review settings August 9, 2026 17:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 44 out of 47 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
Suppressed comments (1)

apps/mobile/src/session/userMessageCollapse.ts:103

  • truncateTextToVisualLines 目前只把 '\n' 视为换行,但同文件里 LINE_BREAK_RE/estimateTextVisualLineCount 使用的是 /\r\n|\r|\n/。如果消息正文包含 CR 或 CRLF(例如跨平台复制/同步进来的文本),这里的“可见行”截取会与实际折行不一致,可能把本应在收起态隐藏的后续内容带进分享导出。建议统一按 CRLF/CR/LF 处理,并在输出中规范化为 '\n'。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 87a073dccf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/mobile/app/sessions/[sessionId].tsx
Comment thread apps/mobile/src/session/ShareSelectionBar.tsx
Signed-off-by: Dash <dashhuang@gmail.com>
Copilot AI review requested due to automatic review settings August 9, 2026 17:31
Comment thread apps/mobile/src/session/conversationShareWebViewHtml.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6306cce6d5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/mobile/app/sessions/[sessionId].tsx

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 44 out of 47 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
Suppressed comments (2)

apps/mobile/src/session/mathWebViewHtml.ts:45

  • buildKatexLoaderJs 每次调用都会对大体积常量 MOBILE_KATEX_CSS / MOBILE_KATEX_JS 执行 JSON.stringify,并把结果拼进模板字符串。会话里出现多个公式块时会重复做多 MB 字符串的序列化与分配,容易造成 JS 线程卡顿。建议把 stringify 结果提升到模块级常量(只计算一次),loader 内直接复用。
    apps/mobile/src/session/mermaidWebViewHtml.ts:36
  • buildMermaidLoaderJs 里每次生成 HTML 都会对大体积的 MOBILE_MERMAID_JS 做一次 JSON.stringify。mermaid 图表在消息里可能多次出现,重复序列化多 MB 字符串会造成不必要的分配与卡顿。建议把 stringify 结果提升到模块级常量(只计算一次),loader 内直接复用。

@MagicLizi

Copy link
Copy Markdown
Contributor

@dashhuang 👋 这个 PR 还有 3 条 review conversation 没 resolve(apps/mobile/package.json / apps/mobile/src/session/conversationShareWebViewHtml.ts / apps/mobile/app/sessions/[sessionId].tsx),auto-review 因此暂时跳过、没法继续审查 / 合并。

如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。

Signed-off-by: Dash <dashhuang@gmail.com>
Copilot AI review requested due to automatic review settings August 9, 2026 17:46

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 19aa2fb93f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/mobile/app/sessions/[sessionId].tsx Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 44 out of 47 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
Suppressed comments (1)

apps/mobile/src/session/shareSelectionStore.ts:19

  • isShareableMessage 当前只按 kind/systemCardType/orcaCard/hookSource/synthetic trigger 过滤,但不会排除 assistant 流式生成中的消息;在 iOS 截图自动预选路径里,这会把「生成中」的可见气泡也纳入候选集并可能被导出为图片,导致分享内容不完整/与实际 UI 期望不一致。建议把 isStreaming===true 的消息视为不可分享。

Comment thread apps/mobile/src/session/mermaidWebViewHtml.ts Outdated
Comment thread apps/mobile/src/session/mathWebViewHtml.ts Outdated
Signed-off-by: Dash <dashhuang@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 44 out of 47 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
Suppressed comments (2)

apps/mobile/package.json:65

  • 新增 xdt-screenshot-monitor 属于原生模块接入,会改变 Mobile runtime fingerprint,按仓库规则需要由指定把关人对“冷更/出包影响”做显式确认后才能合并(不能按普通 OTA 功能发布)。
    "xdt-ios-app-distribution": "file:./modules/xdt-ios-app-distribution",
    "xdt-mobile-realtime-audio": "file:./modules/xdt-mobile-realtime-audio",
    "xdt-screenshot-monitor": "file:./modules/xdt-screenshot-monitor",
    "xdt-tapdb": "file:./modules/xdt-tapdb",

apps/mobile/src/session/MessageRenderer.tsx:1839

  • 这里用 shareSelectionActive(它被定义为“全局分享态 && 当前消息可分享”)来 gate showCompletedActionBar,会导致在分享模式下,不可分享的消息仍然会显示操作栏/时间等“非导出内容”,与“分享态只保留导出一致的消息内容”的注释语义不一致。建议改为用全局的 actions.shareSelectionActive 来统一隐藏操作栏。
  const showCompletedActionBar = !shareSelectionActive && mobileMessageShowsActionBar({

@MagicLizi MagicLizi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-Review: PR #2188 (heavy tier)

P1 (blocking)

  1. 冷更指纹变更 (apps/mobile/modules/xdt-screenshot-monitor/**): 新增原生 Expo Module 会改变 iOS/Android runtime fingerprint,触发冷更。按 docs/dev-rules/mobile-development.md 冷更边界规则,需仓库指定把关人明确确认后才能合并。PR 已在 Description 标注并说明原因。

  2. Android 路径未实机验证 (apps/mobile/app/sessions/[sessionId].tsx): Android 走 React Native WebView 降级渲染路径(原生 renderConversationShareHtmlToPng 为 iOS-only),包含 SVG→Canvas→PNG 导出管线、WebView postMessage 桥接和 temp 文件写入,仅靠 iOS Simulator 验证存在跨平台风险。需补 Android 实机验证。

  3. 双模式目检未分别执行 (ShareSelectionBar.tsx / ConversationShareWebView.tsx / conversationShareWebViewHtml.ts): 代码层已正确使用语义 token(colors.cta/colors.surfaceChip/colors.textPrimary 等),HTML 生成侧传入 dark flag。此条提示性,不阻塞合并。

P2

  1. Android 临时 PNG 清理跳过逻辑建议加注释说明原因
  2. richContentAssets.generated.ts(~4.36 MB)建议标记为 linguist-generated 或 CI 构建期生成
  3. WebView 导出脚本使用已废弃的 unescape()(WebView 环境限制,无替代方案)
  4. ConversationShareWebView 组件无单元测试(核心数据转换层已有充分覆盖)

核查通过

  • CSP 严格: default-src 'none'; img-src data:; script-src 'unsafe-inline'
  • WKWebView nonPersistent 存储、allowFileAccess=false
  • 用户内容经 redactSensitiveText + escapeHtml 多层转义
  • 附件投影显式剥离文件路径和 data: URI
  • 无凭证泄露、无数据外发风险

@dashhuang

Copy link
Copy Markdown
Member Author

冷更影响明确确认如下:

  • 本 PR 新增 Apple Expo 原生模块 xdt-screenshot-monitor,iOS / Android runtime fingerprint 均会变化。
  • 该能力不能通过普通 OTA 完整下发;存量用户必须安装下一版原生包后才能获得截图监听和原生 PNG renderer。
  • 接受本次冷更影响,并按下一次 iOS / Android 原生版本统一出包;不能把本 PR 当作普通 OTA 功能单独发布。
  • 回滚时可先关闭 JS 入口;若要从二进制移除模块,则需要后续冷更版本。

@MagicLizi 冷更影响已明确确认。麻烦你帮忙完成 Android 实机验证,重点覆盖:手动进入分享态、多选消息、生成 PNG、打开系统分享面板,以及接收 App 能正常读取图片。验证后请把结果补到 PR,并重新评审以更新当前 CHANGES_REQUESTED 状态;若发现实际阻塞问题请直接记录复现机型、系统版本和步骤。

@MagicLizi MagicLizi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto Review 通过

审查完成:0 个 P0/P1 阻断项(冷更新指纹变更是移动端分享截图功能的必要代价,已在 PR 描述中正确记录并由维护者确认门放行)。

2 个 P2 建议:

  • Android 端 PNG 临时文件未清理,建议补充清理逻辑或加注释说明
  • WebView originWhitelist 可收窄为 做纵深防御

合并后迟到 bot 意见(chatgpt-codex-connector 未表态)以 post-merge sweep 兜底提醒。

@MagicLizi MagicLizi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto Review 通过

审查完成:0 个 P0/P1 阻断项(冷更新指纹变更是移动端分享截图功能的必要代价,已在 PR 描述中正确记录并由维护者确认门放行)。

2 个 P2 建议:

  • Android 端 PNG 临时文件未清理,建议补充清理逻辑或加注释说明
  • WebView originWhitelist 可收窄为 about:blank 做纵深防御

合并后迟到 bot 意见(chatgpt-codex-connector 未表态)以 post-merge sweep 兜底提醒。

@MagicLizi

Copy link
Copy Markdown
Contributor

合并啦 🎯 移动端对话分享为图片的 安全实现很扎实——strict CSP + 非持久 WKWebView + 外部图片剥离 + overlay 锁屏激活,每一层都把用户数据守住了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

touches:cold-update 改动影响移动端冷更新(review-pr 自动维护,仅展示) touches:core 改动碰到架构核心路径(review-pr 自动维护,仅展示) touches:large-diff 改动量较大(review-pr 自动维护,仅展示) touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示) touches:rules 改动碰到规则 / 规范文档(review-pr 自动维护,仅展示) touches:security 改动碰到安全边界(review-pr 自动维护,仅展示)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants