diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.i18n.yaml index a5158a3994..9c2cdd061a 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.md -2026-09-16-desktop-window-menus.md: 85ac42aa287b1cd9e595ade7cb44b965dea787dd -2026-09-16-desktop-window-menus.zh.md: 1376b90058ceafa7e378b4fd543d261064f5d8aa +2026-09-16-desktop-window-menus.md: 8a5343404e0cb3d2e7c4e20248a8b29f3bfbec47 +2026-09-16-desktop-window-menus.zh.md: d6ee6412462aecf49557a7994a683d37bf8e7253 diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.md b/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.md index 85ac42aa28..8a5343404e 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.md +++ b/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.md @@ -12,7 +12,7 @@ The Desktop shell replaces Electron's default application menu with a custom tem On macOS the template declares `{ role: 'fileMenu' }` before the Edit menu and `{ role: 'windowMenu' }` after it, and a separator-delimited run of `hide`, `hideOthers`, and `unhide` before Quit in the application submenu. Those roles contribute only the standard items; no Services submenu, window list, or other macOS default is declared. Windows and Linux keep the application and Edit menus. -Electron's role labels are English string literals inside Electron (`lib/browser/api/menu-item-roles.ts`, `filemenu`, `windowmenu`, `close`, `minimize`, `hide`) with no locale lookup, and Electron re-applies them to the native menu items before the menu is shown, so a non-English Desktop shows them in English; the existing Edit menu behaves the same way. No custom close, minimize, or hide code is added: ⌘W destroys the window through Electron's own role. +Electron supplies English defaults for the File, Window, and Edit roles. Explicit labels override role defaults while retaining native actions and shortcuts; the localized application commands are documented in the [Desktop README](../../../../apps/desktop/README.md). No custom close, minimize, or hide code is added: ⌘W destroys the window through Electron's own role. ## Alternatives considered @@ -28,8 +28,8 @@ Electron's role labels are English string literals inside Electron (`lib/browser ## Consequences -macOS regains the window and application commands the custom menu suppressed, at the cost of four menu roles. The labels stay English on a non-English Desktop. +macOS regains the window and application commands the custom menu suppressed. File, Window, and Edit retain Electron’s default English labels; application commands use the Desktop locale. ## Testing -A `apps/desktop/tests/main-startup.spec.ts` case pins the declared menu roles per platform, including the macOS hide commands. Role-based menu items execute natively, so a programmatic `click()` and the vitest Electron mock cannot exercise the shortcuts; a real Electron 44 run of the same template showed the standard File, Window, and application items present only after this change, with the same English labels while `app.getLocale()`, `getSystemLocale()`, and `getPreferredSystemLanguages()` all reported `zh-CN`. +`apps/desktop/tests/main-startup.spec.ts` verifies the declared menu roles per platform and records English and Chinese application-label snapshots. Role-based menu items execute natively, so a programmatic `click()` and the Vitest Electron mock cannot exercise the shortcuts. The isolated Electron 44 demo confirms that explicit application labels appear in native menus; the template snapshots do not qualify native shortcut execution. diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.zh.md b/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.zh.md index 1376b90058..d6ee641246 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.zh.md @@ -12,7 +12,7 @@ Desktop shell 用自定义模板替换了 Electron 的默认应用菜单,该 macOS 上的模板在 Edit 菜单之前声明 `{ role: 'fileMenu' }`,在其之后声明 `{ role: 'windowMenu' }`,并在应用子菜单的 Quit 之前声明由分隔符隔开的 `hide`、`hideOthers` 和 `unhide`。这些 role 只提供标准菜单项;不声明 Services 子菜单、窗口列表或其他 macOS 默认项。Windows 和 Linux 保留应用菜单和 Edit 菜单。 -这些 role 的标签是 Electron 内部的英文常量(`lib/browser/api/menu-item-roles.ts` 的 `filemenu`、`windowmenu`、`close`、`minimize`、`hide`),没有语言查询,而且 Electron 在菜单显示前会把这些标签重新写到本机菜单项上,因此在非英文的 Desktop 上它们仍是英文;现有的 Edit 菜单同样如此。这里不新增任何自定义的关闭、最小化或隐藏代码:⌘W 通过 Electron 自身的 role 销毁窗口。 +Electron 为 File、Window 和 Edit role 提供英文默认标签。显式标签会覆盖 role 的默认标签,同时保留原生命令和快捷键;本地化的应用命令见 [Desktop README](../../../../apps/desktop/README.zh.md)。这里不新增任何自定义的关闭、最小化或隐藏代码:⌘W 通过 Electron 自身的 role 销毁窗口。 ## 考虑过的替代方案 @@ -28,8 +28,8 @@ macOS 上的模板在 Edit 菜单之前声明 `{ role: 'fileMenu' }`,在其之 ## 影响 -macOS 恢复了自定义菜单压掉的窗口和应用命令,代价是四个菜单 role。在非英文的 Desktop 上这些标签仍是英文。 +macOS 恢复了自定义菜单压掉的窗口和应用命令。File、Window 和 Edit 保留 Electron 的默认英文标签;应用命令使用 Desktop 的语言。 ## 测试 -`apps/desktop/tests/main-startup.spec.ts` 中的一个用例固定了各平台声明的菜单 role,包括 macOS 的隐藏命令。基于 role 的菜单项由本机执行,因此程序化的 `click()` 和 vitest 的 Electron mock 都无法触达这些快捷键;用真实 Electron 44 运行同一模板显示,只有在本改动之后才出现标准的 File、Window 和应用菜单项,并且在 `app.getLocale()`、`getSystemLocale()` 和 `getPreferredSystemLanguages()` 都报告 `zh-CN` 时标签保持不变。 +`apps/desktop/tests/main-startup.spec.ts` 验证各平台声明的菜单 role,并记录中英文应用标签快照。基于 role 的菜单项由本机执行,因此程序化的 `click()` 和 Vitest 的 Electron mock 都无法触达这些快捷键。独立 Electron 44 demo 确认显式应用标签会显示在原生菜单中;模板快照不能验证原生快捷键的执行。 diff --git a/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.i18n.yaml b/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.i18n.yaml index 69db5487b8..af257a325c 100644 --- a/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-17-developer-tools-settings.md -2026-09-17-developer-tools-settings.md: e76cb7f367faff71ef916d8a263dc523583f1cd2 -2026-09-17-developer-tools-settings.zh.md: 5a7d6d59db686f57ef4945b572ea65ffe54b7f9c +2026-09-17-developer-tools-settings.md: 7819b2a2df3c9eaf2db1e1271d7b419768affaff +2026-09-17-developer-tools-settings.zh.md: 5ff9f2c1e2380f086969cc392bf649edd13e2893 diff --git a/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.md b/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.md index e76cb7f367..7819b2a2df 100644 --- a/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.md +++ b/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.md @@ -10,9 +10,9 @@ Diagnostic views, preset selection and changed-file summaries add complexity to ## Decision -The settings-owned `ctx.settingsScope.developerTools` preference binds a namespace scope and exposes one accepted `ui-developer-tools.enabled` preference, defaulting off. General Settings offers the same switch on Web and desktop. The existing settings transport owns validation, ordered writes, persistence and recovery; loopback Web and desktop follow Host acceptance. Remote Web owns one browser-local observable shared by all consumers until reload, with no Host writes. +The settings-owned `ctx.settingsScope.developerTools` preference binds a namespace scope and exposes one accepted `ui-developer-tools.enabled` preference. The initial default-off choice reduces routine-task complexity; [Developer tools default on after preference resolution](2026-09-20-developer-tools-default-on.md) supersedes that default and owns startup enablement. General Settings offers the same switch on Web and desktop. The existing settings transport owns validation, ordered writes, persistence and recovery; loopback Web and desktop follow Host acceptance. Remote Web owns one browser-local observable shared by all consumers until reload, with no Host writes. -Off exposes only Chat, hides tool Inspect actions when no visible View declares tool-call inspection, removes the conversation View tab bar, hides the new-session preset chip, and omits the changed-files card and its summary read. Turning it off from another View activates Chat. The preset composition, saved default, Session events and explicit delivery cards remain unchanged. This preference is presentation policy, not Host authorization. +Off hides the Trajectory View, hides tool Inspect actions when no visible View declares tool-call inspection, hides the conversation View tab bar while fewer than two Views are available, hides the new-session preset chip and the settings row that allows switching Agent modes, and omits the changed-files card and its summary read. Views registered by other plugins stay available. Turning it off while Trajectory is active activates Chat. The preset composition, saved default, Session events and explicit delivery cards remain unchanged. This preference is presentation policy, not Host authorization. Plugin assembly translates the shared preference into component-specific capabilities (`showCodeDiff`, `showPresetPicker`, and `interactivePreview`). Conversation supplies the inspection callback using the visible target's `toolCallFocus`; Chat does not name the target. The builtin HTML renderer uses DOMPurify for complete-document sanitization and parser-mutation defenses. Off preserves root styling, removes active documents, resource hints, SVG animation and all `href` attributes, and mounts an iframe with no sandbox permissions and a CSP denying scripts and external resources. It reads no related files. On retains the opaque scripted Blob renderer and its bounded related-file reads. Switching modes replaces the browsing context and disposes pending reads. The [document-preview decision](../architecture/2026-09-08-document-preview-operations.md) and [filesystem-read authority](../architecture/2026-09-09-workspace-file-read-authority.md) remain authoritative for advanced rendering and Host read access. @@ -26,4 +26,4 @@ Plugin assembly translates the shared preference into component-specific capabil Basic HTML sacrifices JavaScript, external and related resources, and link navigation; inline styles and data images remain available. Advanced mode retains normal browser networking and does not gain parent-origin, popup, form or top-navigation privileges. The setting does not constrain third-party preview implementations or revoke Host filesystem authority. -Component and settings tests cover defaulting, schema rejection, accepted-state updates, chip disclosure, changed-file visibility and iframe disposal. The recorded-session [document preview](../../../../apps/web/tests/document-preview.e2e.ts) and [changed-files](../../../../apps/web/tests/changed-files-turn.e2e.ts) scenarios exercise the real settings control and the shared renderer. The browser scaffold enables developer tools by default. Only settings chrome, document preview, changed-files and developer-tools scenarios explicitly retain the shipped default; together they cover hidden diagnostic controls, static previews, and switching back to Chat. Refused Host writes recover accepted state and show the localized retry message. Native Electron execution remains unverified. +Component and settings tests cover defaulting, schema rejection, accepted-state updates, chip disclosure, changed-file visibility and iframe disposal. The recorded-session [document preview](../../../../apps/web/tests/document-preview.e2e.ts) and [changed-files](../../../../apps/web/tests/changed-files-turn.e2e.ts) scenarios exercise the real settings control and the shared renderer. The browser scaffold uses the shipped default unless a scenario specifies a preference. Settings chrome, document preview and changed-files scenarios explicitly disable developer tools; together they cover hidden diagnostic controls, static previews, and switching back to Chat. Refused Host writes recover accepted state and show the localized retry message. Native Electron execution remains unverified. diff --git a/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.zh.md b/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.zh.md index 5a7d6d59db..5ff9f2c1e2 100644 --- a/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.zh.md +++ b/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.zh.md @@ -10,9 +10,9 @@ Status: implemented ## Decision -设置域内的 `ctx.settingsScope.developerTools` 偏好对象绑定命名空间 scope,提供一个已接受的 `ui-developer-tools.enabled` 偏好,默认关闭。Web 和桌面端的通用设置提供同一个开关。现有设置传输负责验证、有序写入、持久化和恢复;回环 Web 和桌面端跟随 Host 接受的状态。远程 Web 持有一个所有消费者共享的浏览器本地可观察值,刷新后重置,不发送 Host 写入。 +设置域内的 `ctx.settingsScope.developerTools` 偏好对象绑定命名空间 scope,提供一个已接受的 `ui-developer-tools.enabled` 偏好。最初默认关闭的选择用于降低日常任务的复杂度;[偏好解析后默认开启开发者工具](2026-09-20-developer-tools-default-on.zh.md)取代该默认值并负责启动时的启用策略。Web 和桌面端的通用设置提供同一个开关。现有设置传输负责验证、有序写入、持久化和恢复;回环 Web 和桌面端跟随 Host 接受的状态。远程 Web 持有一个所有消费者共享的浏览器本地可观察值,刷新后重置,不发送 Host 写入。 -关闭时仅展示 Chat,在没有可见 View 声明工具调用检查能力时隐藏 Inspect 操作,移除会话 View 标签栏,隐藏新会话预设入口,并省略改动文件卡片及其摘要读取。在其他 View 中关闭开关会激活 Chat。预设组合、保存的默认值、Session 事件和显式交付卡片保持不变。此偏好是展示策略,不是 Host 授权。 +关闭时隐藏 Trajectory View,在没有可见 View 声明工具调用检查能力时隐藏 Inspect 操作,可见 View 少于两个时隐藏会话 View 标签栏,隐藏新会话预设入口和「允许切换agent模式」设置行,并省略改动文件卡片及其摘要读取。其他插件注册的 View 保持可用。在 Trajectory 中关闭开关会激活 Chat。预设组合、保存的默认值、Session 事件和显式交付卡片保持不变。此偏好是展示策略,不是 Host 授权。 插件组装层将共享偏好转换为组件自身的能力(`showCodeDiff`、`showPresetPicker` 和 `interactivePreview`)。Conversation 根据可见目标的 `toolCallFocus` 提供检查回调;Chat 不再指定目标名称。内置 HTML 渲染器使用 DOMPurify 清理完整文档并防护解析变异。关闭时保留根节点样式,移除主动文档、资源提示、SVG 动画及所有 `href` 属性,再放入不授予沙箱权限的 iframe,并通过 CSP 禁止脚本和外部资源。不读取关联文件。开启时保留不透明源的脚本 Blob 渲染器及其有界关联文件读取。切换模式会替换浏览上下文并清理待处理读取。[文档预览决策](../architecture/2026-09-08-document-preview-operations.zh.md)和[文件系统读取权限](../architecture/2026-09-09-workspace-file-read-authority.zh.md)继续定义高级渲染与 Host 读取访问。 @@ -26,4 +26,4 @@ Status: implemented 基础 HTML 放弃 JavaScript、外部与关联资源和链接导航;行内样式与 data 图片仍可用。高级模式保留正常浏览器网络能力,不增加父源、弹窗、表单或顶层导航权限。此设置不约束第三方预览实现,也不撤销 Host 文件系统读取权限。 -组件和设置测试覆盖默认值、schema 拒绝、已接受状态更新、入口折叠、改动文件展示和 iframe 清理。基于已记录 Session 的[文档预览](../../../../apps/web/tests/document-preview.e2e.ts)与[改动文件](../../../../apps/web/tests/changed-files-turn.e2e.ts)场景验证真实设置控件及共用渲染器。浏览器脚手架默认开启开发者工具,仅设置外壳、文档预览、改动文件和开发者工具场景显式保留产品默认值;这些场景共同覆盖诊断控件隐藏、静态预览和切回 Chat。Host 拒绝写入后会恢复已接受状态,并显示本地化重试提示。原生 Electron 执行尚未验证。 +组件和设置测试覆盖默认值、schema 拒绝、已接受状态更新、入口折叠、改动文件展示和 iframe 清理。基于已记录 Session 的[文档预览](../../../../apps/web/tests/document-preview.e2e.ts)与[改动文件](../../../../apps/web/tests/changed-files-turn.e2e.ts)场景验证真实设置控件及共用渲染器。浏览器脚手架使用产品默认值,除非场景指定偏好。设置外壳、文档预览和改动文件场景显式关闭开发者工具;这些场景共同覆盖诊断控件隐藏、静态预览和切回 Chat。Host 拒绝写入后会恢复已接受状态,并显示本地化重试提示。原生 Electron 执行尚未验证。 diff --git a/.agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.i18n.yaml b/.agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.i18n.yaml new file mode 100644 index 0000000000..aa7199eff4 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.md +2026-09-20-developer-tools-default-on.md: 42c89faed11b966ac8ea0882236af9bf13557702 +2026-09-20-developer-tools-default-on.zh.md: a684844f5ba53a0bebdd1c3a3a3ade1ed150cc31 diff --git a/.agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.md b/.agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.md new file mode 100644 index 0000000000..42c89faed1 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.md @@ -0,0 +1,31 @@ +# Agent Note: Developer tools default on after preference resolution + +Status: implemented + +English | [中文](2026-09-20-developer-tools-default-on.zh.md) + +## Problem + +New installations need access to developer views, preset selection, change summaries and interactive HTML previews without first finding a Settings switch. A saved disabled preference must still prevent those features from activating during startup, including script execution that cannot be undone after it occurs. + +## Decision + +The Host schema defaults `ui-developer-tools.enabled` to `true`. Remote browsers with process-local preferences also start enabled. Saved Host values take precedence, including `false`. This supersedes only the initial default-off choice in [Shared developer-tool settings](2026-09-17-developer-tools-settings.md); that note retains ownership of shared persistence and rendering policy. + +Host-backed clients publish disabled until an accepted schema-resolved value arrives. Loading, failed initial reads and an unavailable namespace do not grant interactive-preview capabilities. The Host supplies the default for an absent stored field; the client does not interpret an absent response as consent to enable scripts. An accepted value remains authoritative during later refreshes. + +Only the Trajectory tab follows the developer-tools preference; third-party Views remain available. The conversation shell retains the explicit Trajectory id because the requested rule names that View. No current requirement makes arbitrary diagnostic Views share the switch. + +## Alternatives considered + +**Keep the default off.** This minimizes the initial interface but requires a separate opt-in for the features new installations now expose. Users can still save the disabled preference. + +**Default the Host client to enabled before settings arrive.** This makes rendering immediate, but briefly activates features for users who disabled them. Unmounting a scripted HTML iframe cannot undo scripts or requests already executed. + +**Add a generic developer-tool flag to View registration.** This expands the plugin API and allows other Views to join a switch whose current requirement is specific to Trajectory. A future requirement can justify that API separately. + +## Consequences + +Fresh profiles expose developer features after settings resolve; users with a saved disabled choice retain the simpler presentation. New users also receive interactive HTML previews with the capabilities documented by the [filesystem-read authority](../architecture/2026-09-09-workspace-file-read-authority.md). Host initialization can briefly withhold developer controls even when the eventual preference is enabled. + +Settings tests cover initial withholding, unavailable namespaces, explicit disabled values and process-local defaults. HTML renderer coverage checks that pending and disabled preferences neither read related files nor create script-enabled frames. Browser settings coverage verifies the default and a disabled choice across reload; document-preview replay pins the explicitly disabled scenario. diff --git a/.agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.zh.md b/.agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.zh.md new file mode 100644 index 0000000000..a684844f5b --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.zh.md @@ -0,0 +1,31 @@ +# Agent Note: 偏好解析后默认开启开发者工具 + +Status: implemented + +[English](2026-09-20-developer-tools-default-on.md) | 中文 + +## Problem + +新安装需要直接使用开发者视图、预设选择、改动摘要和交互式 HTML 预览,无需先寻找设置开关。已保存的关闭偏好仍必须阻止这些功能在启动期间激活,包括发生后无法撤销的脚本执行。 + +## Decision + +Host schema 将 `ui-developer-tools.enabled` 默认为 `true`。使用进程内偏好的远程浏览器也从开启状态启动。已保存的 Host 值优先,包括 `false`。这仅取代[共享开发者工具设置](2026-09-17-developer-tools-settings.zh.md)中最初默认关闭的选择;该记录继续负责共享持久化和渲染策略。 + +使用 Host 偏好的客户端在收到经过 schema 解析并接受的值之前发布关闭状态。加载中、首次读取失败和命名空间不可用均不授予交互式预览能力。缺少存储字段时由 Host 提供默认值;客户端不会把缺少响应解释为允许开启脚本。后续刷新期间,已接受的值继续有效。 + +只有 Trajectory 标签页受开发者工具偏好控制;第三方 View 保持可用。对话外壳保留显式 Trajectory id,因为当前需求明确指定此 View。没有现行需求要求任意诊断 View 共用该开关。 + +## Alternatives considered + +**保持默认关闭。** 这可简化初始界面,但新安装现在直接提供的功能将需要额外启用。用户仍可保存关闭偏好。 + +**在设置到达前让 Host 客户端默认开启。** 这可以立即渲染,却会短暂激活用户已关闭的功能。卸载可执行脚本的 HTML iframe 无法撤销已经执行的脚本或请求。 + +**在 View 注册中增加通用开发者工具标记。** 这会扩展插件 API,并允许其他 View 加入当前仅针对 Trajectory 的开关。未来需求可以单独论证该 API。 + +## Consequences + +新 profile 在设置解析后展示开发者功能;已保存关闭选择的用户保留简化展示。新用户也会获得带有[文件系统读取权限](../architecture/2026-09-09-workspace-file-read-authority.zh.md)所述能力的交互式 HTML 预览。即使最终偏好为开启,Host 初始化期间也可能短暂不展示开发者控件。 + +设置测试覆盖首次等待、命名空间不可用、显式关闭值和进程内默认值。HTML 渲染器覆盖验证等待中及关闭偏好均不读取关联文件,也不创建允许脚本的 frame。浏览器设置覆盖验证默认值和关闭选择在刷新后保持;文档预览回放固定显式关闭的场景。 diff --git a/apps/desktop/README.i18n.yaml b/apps/desktop/README.i18n.yaml index 1187d5f1a7..bea2649956 100644 --- a/apps/desktop/README.i18n.yaml +++ b/apps/desktop/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write apps/desktop/README.md -README.md: 8885703f33223d381633c29a11516619d52fd711 -README.zh.md: b5a3f0433d874b3fc51ef16805d7ab6df20d9059 +README.md: 2b41f6deeda70db186fb38ee6b5590bf8c459835 +README.zh.md: 24128293d5a246e33d36ba1ecbc10ae3cf594ea5 diff --git a/apps/desktop/README.md b/apps/desktop/README.md index 8885703f33..2b41f6deed 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -4,7 +4,7 @@ English | [中文](README.zh.md) The desktop application is an Electron shell around the complete dsh Web application. An Electron RunAsNode child starts the shared profile runner, and Electron immediately loads the packaged Web entry at `dsh-app://app/`. Its shared loading page waits for Host boot injections, then starts the client without navigating to another document. Electron forwards application HTTP requests to the authenticated Web Host; WebSocket streams connect to that Host with credentials attached only for the owned application window. Node IPC carries boot injections, readiness, and shutdown. Desktop defaults to port `19387`, separate from Web’s `3080`; a `webserver.config.port` patch can override it. -The first application-menu command, **About DeepSeek Harness**, opens Electron's native About panel with the application icon, product name, and installed release version. The menu follows the Desktop shell locale. macOS reads the icon from its application bundle, so an unpackaged development launch displays Electron's icon; Windows receives the packaged PNG. +The first application-menu command, **About DeepSeek Harness**, opens Electron's native About panel with the application icon, product name, and installed release version. The menu follows the Desktop shell locale. On macOS, Hide, Hide Others, Show All, and Quit use localized labels; Hide and Quit include the DeepSeek Harness product name. These commands retain their native actions and shortcuts. macOS reads the icon from its application bundle, so an unpackaged development launch displays Electron's icon; Windows receives the packaged PNG. Desktop’s local native directory flow opens an Electron folder dialog attached to the application window, restoring, showing, and focusing that window first. Concurrent requests share one dialog; cancellation returns no path and failures remain retryable. Ordinary Web uses the Host chooser. Browse mode lists Host directories. On Linux without zenity or kdialog, automatic selection uses browse instead of the Electron dialog. diff --git a/apps/desktop/README.zh.md b/apps/desktop/README.zh.md index b5a3f0433d..24128293d5 100644 --- a/apps/desktop/README.zh.md +++ b/apps/desktop/README.zh.md @@ -4,7 +4,7 @@ 桌面应用是完整 dsh Web 应用外的一层 Electron 壳。Electron RunAsNode 子进程启动共享 profile runner,Electron 立即从 `dsh-app://app/` 加载打包内的 Web 入口。共享加载页等待 Host 启动注入,然后在同一文档中启动客户端。Electron 将应用 HTTP 请求转发给已认证的 Web Host;WebSocket 流连接到该 Host,仅为归属的应用窗口附加凭据。Node IPC 承载启动注入、就绪与关闭。Desktop 默认使用端口 `19387`,与 Web 的 `3080` 分开;可通过 `webserver.config.port` patch 覆盖。 -应用菜单第一项“**关于 DeepSeek Harness**”打开 Electron 原生关于面板,展示应用图标、产品名称和当前安装的发布版本。菜单文案跟随桌面壳的语言。macOS 从应用包读取图标,因此未打包的开发启动会显示 Electron 图标;Windows 使用随包分发的 PNG。 +应用菜单第一项“**关于 DeepSeek Harness**”打开 Electron 原生关于面板,展示应用图标、产品名称和当前安装的发布版本。菜单文案跟随桌面壳的语言。macOS 的隐藏、隐藏其他、显示全部和退出条目使用本地化文案,隐藏和退出条目包含 DeepSeek Harness 产品名称。这些条目保留原生动作和快捷键。macOS 从应用包读取图标,因此未打包的开发启动会显示 Electron 图标;Windows 使用随包分发的 PNG。 Desktop 的本地原生目录流程打开绑定应用窗口的 Electron 文件夹对话框,并先恢复、显示和聚焦该窗口。并发请求共用一个对话框;取消不返回路径,失败后可以重试。普通 Web 使用 Host 选择器。浏览模式列出 Host 目录。Linux 缺少 zenity 或 kdialog 时,自动选择使用浏览模式,不使用 Electron 对话框。 diff --git a/apps/desktop/src/locale.ts b/apps/desktop/src/locale.ts index 03e6f29a57..09dedf44e5 100644 --- a/apps/desktop/src/locale.ts +++ b/apps/desktop/src/locale.ts @@ -3,6 +3,10 @@ export const en = { application: 'Application', aboutMenu: 'About DeepSeek Harness', + hideApplication: 'Hide DeepSeek Harness', + hideOtherApplications: 'Hide Others', + showAllApplications: 'Show All', + quitApplication: 'Quit DeepSeek Harness', edit: 'Edit', menuBar: 'Application menu', delete: 'Delete', @@ -94,6 +98,10 @@ export type DesktopMessages = { readonly [Key in keyof typeof en]: string } export const zh = { application: '应用', aboutMenu: '关于 DeepSeek Harness', + hideApplication: '隐藏 DeepSeek Harness', + hideOtherApplications: '隐藏其他', + showAllApplications: '显示全部', + quitApplication: '退出 DeepSeek Harness', edit: '编辑', menuBar: '应用菜单', delete: '删除', diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index e5aa563c6e..63680a5c0b 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -608,12 +608,15 @@ async function main(): Promise { }) // A custom application menu replaces Electron's default menu, so macOS needs // its standard menus and application hide commands declared explicitly. + // Keep app.name stable: Electron derives its default userData directory from it. const darwin = process.platform === 'darwin' const platformMenus: MenuItemConstructorOptions[] = darwin ? [{ role: 'fileMenu' }, { role: 'editMenu' }, { role: 'windowMenu' }] : [{ role: 'editMenu' }] const hideCommands: MenuItemConstructorOptions[] = darwin - ? [{ role: 'hide' }, { role: 'hideOthers' }, { role: 'unhide' }, { type: 'separator' }] + ? [{ role: 'hide', label: currentDesktopLocale().messages.hideApplication }, + { role: 'hideOthers', label: currentDesktopLocale().messages.hideOtherApplications }, + { role: 'unhide', label: currentDesktopLocale().messages.showAllApplications }, { type: 'separator' }] : [] const applicationItems = (): MenuItemConstructorOptions[] => [ { label: currentDesktopLocale().messages.aboutMenu, role: 'about' }, @@ -621,7 +624,8 @@ async function main(): Promise { { label: currentDesktopLocale().messages.checkUpdatesMenu, click: () => { void openUpdatePrompt(true) } }, { type: 'separator' }, ...hideCommands, - { role: 'quit', ...(process.platform === 'win32' ? { label: currentDesktopLocale().messages.exitApplication } : {}) }, + { role: 'quit', ...(darwin ? { label: currentDesktopLocale().messages.quitApplication } + : process.platform === 'win32' ? { label: currentDesktopLocale().messages.exitApplication } : {}) }, ] Menu.setApplicationMenu(process.platform === 'win32' ? null : Menu.buildFromTemplate([{ label: darwin ? app.name : currentDesktopLocale().messages.application, diff --git a/apps/desktop/tests/expected/application-menu-en-US.json b/apps/desktop/tests/expected/application-menu-en-US.json new file mode 100644 index 0000000000..af72c9b264 --- /dev/null +++ b/apps/desktop/tests/expected/application-menu-en-US.json @@ -0,0 +1,18 @@ +[ + { + "role": "hide", + "label": "Hide DeepSeek Harness" + }, + { + "role": "hideOthers", + "label": "Hide Others" + }, + { + "role": "unhide", + "label": "Show All" + }, + { + "role": "quit", + "label": "Quit DeepSeek Harness" + } +] diff --git a/apps/desktop/tests/expected/application-menu-zh-CN.json b/apps/desktop/tests/expected/application-menu-zh-CN.json new file mode 100644 index 0000000000..991b9d7639 --- /dev/null +++ b/apps/desktop/tests/expected/application-menu-zh-CN.json @@ -0,0 +1,18 @@ +[ + { + "role": "hide", + "label": "隐藏 DeepSeek Harness" + }, + { + "role": "hideOthers", + "label": "隐藏其他" + }, + { + "role": "unhide", + "label": "显示全部" + }, + { + "role": "quit", + "label": "退出 DeepSeek Harness" + } +] diff --git a/apps/desktop/tests/main-startup.spec.ts b/apps/desktop/tests/main-startup.spec.ts index 171e3855f1..3c792be2eb 100644 --- a/apps/desktop/tests/main-startup.spec.ts +++ b/apps/desktop/tests/main-startup.spec.ts @@ -672,6 +672,22 @@ describe('desktop main startup', () => { expect(harness.menu.setApplicationMenu).toHaveBeenCalledOnce() }) + it.each(['en-US', 'zh-CN'])('localizes macOS visibility and quit commands without changing the application name (%s)', async (locale) => { + vi.spyOn(process, 'platform', 'get').mockReturnValue('darwin') + vi.spyOn(harness.app, 'getLocale').mockReturnValue(locale) + const originalName = harness.app.name + harness.app.name = '@deepseek-ai/dsh-desktop' + try { + await import('../src/main.ts') + await harness.preparing.promise + const commands = applicationMenuItems().filter(item => + item.role === 'hide' || item.role === 'hideOthers' || item.role === 'unhide' || item.role === 'quit') + await expect(JSON.stringify(commands, null, 2) + '\n') + .toMatchFileSnapshot(`./expected/application-menu-${locale}.json`) + expect(harness.app.name).toBe('@deepseek-ai/dsh-desktop') + } finally { harness.app.name = originalName } + }) + it('attaches Host socket credentials only to the owned application origin and window', async () => { await import('../src/main.ts') await harness.preparing.promise diff --git a/apps/web/tests/assembled-remote.ts b/apps/web/tests/assembled-remote.ts index 72a3db40db..5d9b7f8a14 100644 --- a/apps/web/tests/assembled-remote.ts +++ b/apps/web/tests/assembled-remote.ts @@ -99,7 +99,7 @@ interface CapturedFixture { } export interface AssembledRemoteOptions { - /** Supply an enabled Host preference for diagnostic View scenarios. */ + /** Override the schema-resolved Host preference for developer-tool scenarios. */ readonly developerTools?: boolean /** Return the fixture's image-dimension admission error from Session prompt. */ readonly rejectPrompt?: boolean @@ -142,16 +142,14 @@ export function createAssembledRemote(options: AssembledRemoteOptions = {}): Ass const mock = RemoteMock.create().load(remoteDefaultResponses) mock.load({ unary: { - 'settings/describe': options.developerTools === true - ? ok({ - ...fixture.settingsDescribe.value, - namespaces: [...fixture.settingsDescribe.value.namespaces, { - ns: 'ui-developer-tools', - schema: { type: 'object', dict: { enabled: { type: 'boolean' } } }, - value: { enabled: true }, applies: 'live', secrets: [], revision: 0, - }], - }) - : structuredClone(fixture.settingsDescribe), + 'settings/describe': ok({ + ...fixture.settingsDescribe.value, + namespaces: [...fixture.settingsDescribe.value.namespaces, { + ns: 'ui-developer-tools', + schema: { type: 'object', dict: { enabled: { type: 'boolean' } } }, + value: { enabled: options.developerTools ?? true }, applies: 'live', secrets: [], revision: 0, + }], + }), 'credentials/describe': structuredClone(fixture.credentialsDescribe), 'session/modelCatalog': structuredClone(fixture.modelCatalog), 'agentPresets/list': structuredClone(fixture.agentPresets), diff --git a/apps/web/tests/developer-tools-settings.e2e.ts b/apps/web/tests/developer-tools-settings.e2e.ts index 16c0e6500a..28dddd3c02 100644 --- a/apps/web/tests/developer-tools-settings.e2e.ts +++ b/apps/web/tests/developer-tools-settings.e2e.ts @@ -1,4 +1,4 @@ -/** Shared settings are off on a fresh Host and survive browser reload through the ordinary settings UI. */ +/** Developer tools default on for a fresh Host and survive browser reload through the ordinary settings UI. */ import { readFile } from 'node:fs/promises' import { join } from 'node:path' import { chromium } from 'playwright' @@ -7,7 +7,7 @@ import { launchWebScaffold } from './scaffold.ts' import { newEnglishPage } from './support.ts' it('persists developer tools in the Host settings document and restores the accepted choice', async () => { - const scaffold = await launchWebScaffold({ developerTools: false }) + const scaffold = await launchWebScaffold() onTestFinished(() => scaffold.close()) const browser = await chromium.launch() onTestFinished(() => browser.close()) @@ -15,15 +15,26 @@ it('persists developer tools in the Host settings document and restores the acce await page.goto(scaffold.authenticatedUrl) await page.getByRole('button', { name: 'Settings', exact: true }).click() const toggle = page.getByRole('switch', { name: 'Developer tools' }) - expect(await toggle.getAttribute('aria-checked')).toBe('false') + expect(await toggle.getAttribute('aria-checked')).toBe('true') expect(await readFile(join(scaffold.harnessHome, 'settings.yaml'), 'utf8')).not.toContain('ui-developer-tools:') await toggle.click() - await expect.poll(() => toggle.getAttribute('aria-checked')).toBe('true') - expect(await readFile(join(scaffold.harnessHome, 'settings.yaml'), 'utf8')).toContain('ui-developer-tools:\n enabled: true') - await page.reload() - await page.getByRole('button', { name: 'Settings', exact: true }).click() - await expect.poll(() => toggle.getAttribute('aria-checked')).toBe('true') - await toggle.click() await expect.poll(() => toggle.getAttribute('aria-checked')).toBe('false') expect(await readFile(join(scaffold.harnessHome, 'settings.yaml'), 'utf8')).toContain('ui-developer-tools:\n enabled: false') + await page.reload() + await page.getByRole('button', { name: 'Settings', exact: true }).click() + await expect.poll(() => toggle.getAttribute('aria-checked')).toBe('false') + + await page.getByRole('button', { name: 'Agent presets', exact: true }).click() + const pickerToggle = page.getByRole('switch', { name: 'Allow switching Agent modes' }) + await expect.poll(() => page.getByRole('heading', { name: 'Agent presets', exact: true }).count()).toBe(1) + expect(await pickerToggle.count()).toBe(0) + await scaffold.ctx.settings.update('ui-developer-tools', { enabled: true }) + await expect.poll(() => pickerToggle.count()).toBe(1) + await expect.poll(() => pickerToggle.isEnabled()).toBe(true) + const pickerEnabled = await pickerToggle.getAttribute('aria-checked') + await scaffold.ctx.settings.update('ui-developer-tools', { enabled: false }) + await expect.poll(() => pickerToggle.count()).toBe(0) + await scaffold.ctx.settings.update('ui-developer-tools', { enabled: true }) + await expect.poll(() => pickerToggle.isEnabled()).toBe(true) + await expect.poll(() => pickerToggle.getAttribute('aria-checked')).toBe(pickerEnabled) }) diff --git a/apps/web/tests/document-preview.e2e.ts b/apps/web/tests/document-preview.e2e.ts index 31b2fd06b6..8c41184009 100644 --- a/apps/web/tests/document-preview.e2e.ts +++ b/apps/web/tests/document-preview.e2e.ts @@ -365,7 +365,7 @@ fs.appendFileSync(${JSON.stringify(openLog)}, JSON.stringify({ path, action }) + await successShot(page, 'html-basic') sections.push([ '## Basic HTML', '', - '- Developer tools: off by default on Web and desktop', + '- Developer tools: disabled for this scenario', '- Sandbox: no permissions', `- Inline script: ${await basicHtml.locator('#result').innerText()}`, `- Local script: ${await basicHtml.locator('#local-result').innerText()}`, diff --git a/apps/web/tests/scaffold.ts b/apps/web/tests/scaffold.ts index e7249a0b67..fa3e98dd35 100644 --- a/apps/web/tests/scaffold.ts +++ b/apps/web/tests/scaffold.ts @@ -302,7 +302,7 @@ export interface WebScaffold { /** Options for {@link launchWebScaffold}. */ export interface LaunchOptions { - /** The scaffold enables developer tools unless false preserves the shipped default. */ + /** Override the developer-tools preference; omitted uses the shipped default. */ developerTools?: boolean /** Enable the real Open In rows with deterministic launch-environment facts. */ openInAppEnvironment?: LaunchEnvironmentSnapshot @@ -818,8 +818,8 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise