From d780d354ba9ccf31936b1972fdd7745c8044584a Mon Sep 17 00:00:00 2001 From: kdletters Date: Sat, 20 Jun 2026 00:24:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E5=8F=A3=E7=A7=BB=E5=8A=A8=E5=89=AA?= =?UTF-8?q?=E8=B4=B4=E6=9D=BF=E5=93=8D=E5=BA=94=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 Expo 剪贴板读写成功响应收口到 clipboard 模块 让移动 dispatch 只委托剪贴板请求 同步移动壳配置门禁和共享记忆 --- apps/mobile-shell/scripts/check-config.mjs | 10 +++++++--- apps/mobile-shell/src/host-bridge/clipboard.ts | 17 +++++++++++------ apps/mobile-shell/src/host-bridge/dispatch.ts | 4 ++-- .../shared-memory/decision-log.md | 2 +- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/apps/mobile-shell/scripts/check-config.mjs b/apps/mobile-shell/scripts/check-config.mjs index a4a8b2d70..21ffc7032 100644 --- a/apps/mobile-shell/scripts/check-config.mjs +++ b/apps/mobile-shell/scripts/check-config.mjs @@ -1692,8 +1692,10 @@ if ( dispatchSource.includes('Clipboard.setStringAsync') || dispatchSource.includes('Clipboard.getStringAsync') || dispatchSource.includes('ClipboardWriteTextPayload') || - !dispatchSource.includes('writeMobileHostBridgeClipboardText(request.payload)') || - !dispatchSource.includes('readMobileHostBridgeClipboardText()') + !dispatchSource.includes('writeMobileHostBridgeClipboardText(request)') || + !dispatchSource.includes('readMobileHostBridgeClipboardText(request)') || + dispatchSource.includes('ok(request, await writeMobileHostBridgeClipboardText') || + dispatchSource.includes('ok(request, await readMobileHostBridgeClipboardText') ) { throw new Error( 'mobile shell dispatch must delegate clipboard IO to clipboard.ts', @@ -1702,9 +1704,11 @@ if ( for (const snippet of [ 'writeMobileHostBridgeClipboardText', 'type ClipboardWriteTextPayload', + 'request: HostBridgeRequest', + 'ok(request, true)', 'writeMobileClipboardText(text)', 'readMobileHostBridgeClipboardText', - 'readMobileClipboardText()', + 'ok(request, await readMobileClipboardText())', ]) { if (!clipboardSource.includes(snippet)) { throw new Error(`mobile shell clipboard module is missing ${snippet}`); diff --git a/apps/mobile-shell/src/host-bridge/clipboard.ts b/apps/mobile-shell/src/host-bridge/clipboard.ts index 0599f9186..8b74e47ef 100644 --- a/apps/mobile-shell/src/host-bridge/clipboard.ts +++ b/apps/mobile-shell/src/host-bridge/clipboard.ts @@ -4,9 +4,10 @@ import { type ClipboardReadTextResult, type ClipboardWriteTextPayload, type HostBridgeError, + type HostBridgeRequest, normalizeHostBridgeClipboardText, } from '../../../../packages/shared/src/contracts/hostBridge'; -import { invalidRequest } from './protocol'; +import { invalidRequest, ok } from './protocol'; export async function writeMobileClipboardText(rawText: unknown) { const clipboardText = normalizeHostBridgeClipboardText(rawText); @@ -18,17 +19,21 @@ export async function writeMobileClipboardText(rawText: unknown) { return true; } -export async function writeMobileHostBridgeClipboardText(payload: unknown) { - const text = (payload as ClipboardWriteTextPayload | undefined)?.text; +export async function writeMobileHostBridgeClipboardText( + request: HostBridgeRequest, +) { + const text = (request.payload as ClipboardWriteTextPayload | undefined)?.text; if (!(await writeMobileClipboardText(text))) { throw invalidRequest('text is required'); } - return true; + return ok(request, true); } -export function readMobileHostBridgeClipboardText() { - return readMobileClipboardText(); +export async function readMobileHostBridgeClipboardText( + request: HostBridgeRequest, +) { + return ok(request, await readMobileClipboardText()); } export async function readMobileClipboardText(): Promise { diff --git a/apps/mobile-shell/src/host-bridge/dispatch.ts b/apps/mobile-shell/src/host-bridge/dispatch.ts index 09a7b140a..abaf62d38 100644 --- a/apps/mobile-shell/src/host-bridge/dispatch.ts +++ b/apps/mobile-shell/src/host-bridge/dispatch.ts @@ -60,9 +60,9 @@ export async function dispatchMobileHostBridgeRequest( case 'network.status': return getMobileHostBridgeNetworkStatus(request); case 'clipboard.writeText': - return ok(request, await writeMobileHostBridgeClipboardText(request.payload)); + return writeMobileHostBridgeClipboardText(request); case 'clipboard.readText': - return ok(request, await readMobileHostBridgeClipboardText()); + return readMobileHostBridgeClipboardText(request); case 'file.exportText': return ok(request, await exportTextFile(request.payload)); case 'file.importText': diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 0e969406e..f8387ce1b 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -88,7 +88,7 @@ - 2026-06-18 草稿生成完成 / 失败通知:平台壳层的 `markDraftReady` / `markDraftFailed` 统一收口会在原生壳声明 `notification.showLocal` 时请求即时本地通知;通知 payload 只包含生成完成 / 失败标题和草稿来源正文,按草稿来源去重,同一草稿重新进入生成中后才允许再次通知。该能力不替代现有完成 / 错误弹窗、作品架红点、队列概览或后端状态回读,通知失败不阻断主流程。根级原生壳门禁必须覆盖平台壳同步层通过真实 HostBridge transport 发出 `notification.showLocal`,避免只测模型文案。 - 2026-06-19 草稿生成 HostBridge 消费门禁:`PlatformEntryFlowShellImpl` 只负责派生草稿通知和未读数量,实际宿主同步经 `platformHostBridgeSync.ts` 调用 `showHostLocalNotification` / `setHostAppBadgeCount`;`npm run check:native-shells` 必须运行该同步层的真实 Tauri transport 测试,并继续覆盖通知模型、未读计数模型、音频导入和文档导入等 H5 HostBridge 消费测试。 - 2026-06-18 剪贴板读取能力:新增 `clipboard.readText` HostBridge capability,H5 只能读取纯文本结果,契约限制返回文本最多 100000 字符;Expo 壳通过 `expo-clipboard` 读取系统剪贴板文本,Tauri 壳通过 Rust 侧 `tauri-plugin-clipboard-manager` 读取文本且不开放插件 JS guest API。该能力不读取图片、HTML、文件列表或剪贴板监听事件,宿主未声明或读取失败时由 H5 视作失败并保留原流程。 -- 2026-06-19 移动壳剪贴板边界:Expo `clipboard.writeText` / `clipboard.readText` 的系统剪贴板读写、共享 100000 字符归一、payload 校验和响应边界统一收口在 `apps/mobile-shell/src/host-bridge/clipboard.ts`;`dispatch.ts` 只负责把 HostBridge request 委托给 `writeMobileHostBridgeClipboardText(...)` / `readMobileHostBridgeClipboardText()`,不得直接导入 `expo-clipboard` 或调用 `Clipboard.setStringAsync` / `Clipboard.getStringAsync`。移动壳配置检查会覆盖该模块结构、共享文本边界和 dispatch 委托关系,避免剪贴板能力散落到分发层。 +- 2026-06-19 移动壳剪贴板边界:Expo `clipboard.writeText` / `clipboard.readText` 的系统剪贴板读写、共享 100000 字符归一、payload 校验和 HostBridge 成功 / 失败响应边界统一收口在 `apps/mobile-shell/src/host-bridge/clipboard.ts`;`dispatch.ts` 只负责把 HostBridge request 委托给 `writeMobileHostBridgeClipboardText(request)` / `readMobileHostBridgeClipboardText(request)`,不得直接导入 `expo-clipboard`、调用 `Clipboard.setStringAsync` / `Clipboard.getStringAsync` 或包装剪贴板成功响应。移动壳配置检查会覆盖该模块结构、共享文本边界和 dispatch 委托关系,避免剪贴板能力散落到分发层。 - 2026-06-19 桌面壳剪贴板边界:Tauri `clipboard.writeText` / `clipboard.readText` 的系统剪贴板读写、共享 100000 字符归一、payload 校验和响应边界统一收口在 `apps/desktop-shell/src-tauri/src/host_bridge/clipboard.rs`;`dispatch.rs` 只负责把 HostBridge request 委托给 `write_desktop_host_bridge_clipboard_text(...)` / `read_desktop_host_bridge_clipboard_text(...)`,不得直接承接剪贴板文本截断、payload 解析或插件读写细节;桌面 share fallback 仍可调用底层写剪贴板函数复制分享文本。桌面壳配置检查会覆盖该模块结构、共享文本边界和 dispatch 委托关系,避免剪贴板能力散落到分发层。 - 2026-06-19 桌面壳本地通知边界:Tauri `notification.showLocal` 的 payload 清洗、权限状态检查、prompt 权限请求和系统通知发送统一收口在 `apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs`;`dispatch.rs` 只负责把 HostBridge request 委托给 `show_desktop_local_notification(...)` 并映射响应,不得直接调用 `app.notification()`、`NotificationExt` 或 `PermissionState`。桌面壳配置检查会覆盖该模块结构、权限语义和 dispatch 委托关系,避免本地通知能力散落到分发层。 - 2026-06-18 文本文件导入能力:新增 `file.importText` HostBridge capability,H5 统一通过 `importHostTextFile()` 读取宿主返回的纯文本内容;Expo 壳通过 `expo-document-picker` 打开系统文档选择器,Tauri 壳通过系统文件选择框读取真实文本文件。两端只接受 `text/plain`、`text/markdown`、`text/csv`、`application/json` 或对应扩展名,单次不超过 5 MiB,成功只返回清洗后的文件名、MIME、UTF-8 文本内容和字节数,不暴露设备 URI / 本机绝对路径,也不开放通用文件系统。