@deepseek-ai/dsh-client-ui-open-in-app 0.1.7-rc.2 → 0.2.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -18,11 +18,11 @@
18
18
  en: 056e2bd9e3c78dd8
19
19
  zh: be35d2ba53be0442
20
20
  /deepseek-ai-dsh-client-ui-open-in-app/use-this-package/what-to-expect:
21
- en: c93c175a3583820a
22
- zh: fa812b70176d3d60
21
+ en: 42f7977b585f9c3a
22
+ zh: a13772312d52b97f
23
23
  /deepseek-ai-dsh-client-ui-open-in-app/understand-the-implementation:
24
- en: c8ac8873c45eb568
25
- zh: fd623c411f79d90f
24
+ en: a0304e1bd9b44370
25
+ zh: af0fdfd01c296dce
26
26
  /deepseek-ai-dsh-client-ui-open-in-app/further-exploration:
27
27
  en: 9deb509f65b6edfd
28
28
  zh: d6cd63b2abc3731b
@@ -36,5 +36,5 @@
36
36
  en: 3d77fca62d3c8e86
37
37
  zh: 3125d32a158e690a
38
38
  /deepseek-ai-dsh-client-ui-open-in-app/known-limitations-and-deferred-work/dev-note:
39
- en: 739fd95270d14d0a
40
- zh: d5a6b59133ac6b3f
39
+ en: c79e15f681adeacd
40
+ zh: 6a30399297bde201
package/README.md CHANGED
@@ -29,7 +29,7 @@ Mount this plugin in the Web composition beside [`dsh-host-open-in-app`](../../h
29
29
 
30
30
  ### What to expect
31
31
 
32
- The Session header and document header use the same 24px-high split button with 9px corners. Both headers show only the icon; their tooltip names the default application or the reveal action. Both show the default action’s icon, mark its application with “(default)” in the menu, disable while their own gesture runs, and report failures through a transient toast. The directory adapter uses the existing cross-platform application catalog and remembers the last successful choice in `dsh.open-in-app.choice`; a missing choice falls back to the first available application.
32
+ The Session header, file-tree header, and document header use the same 24px-high split button with 9px corners. These headers show only the icon; their tooltip names the default application or the reveal action. They show the default action’s icon, mark its application with “(default)” in the menu, disable while their own gesture runs, and report failures through a transient toast. The file-tree opener targets the displayed directory supplied as `absolutePath` and shares the application choice and launch state with the Session header. The directory adapter uses the existing cross-platform application catalog and remembers the last successful choice in `dsh.open-in-app.choice`; a missing choice falls back to the first available application.
33
33
 
34
34
  The **Open locally** shortcut captures the main Session's directory and the same remembered application as the header button. The effective binding appears in the button tooltip and `aria-keyshortcuts`; Web follows the [shortcut service’s platform defaults](../shortcuts/README.md). The command runs only while the Conversation is selected and the header button has a directory and installed application to open. An in-flight launch also blocks the command. Pointer and keyboard gestures share the controller's launch state, so repeated gestures cannot launch twice or change the remembered choice mid-launch.
35
35
 
@@ -45,7 +45,7 @@ Mounted controls with the same association reader and file share one query and i
45
45
  <details>
46
46
  <summary>Implementation internals — click to expand</summary>
47
47
 
48
- The plugin registers the split button on `conversation.session.header.utilities` through the standard slot/inject currency and registers the `open-in-app` dictionaries as one effect. A page-lifetime controller ([`src/client/controller.ts`](src/client/controller.ts)) owns the once-per-page availability read, the persisted choice snapshot store, and the launch POST; the component receives the shared sources through the inject `hooks` compartment, so every Session header shares one truth. Document-relative route forms and wire payload types come from the host package's browser-safe `@deepseek-ai/dsh-host-open-in-app/shared` subpath. The controller guards in-flight launches and publishes their captured directory and status; header controls derive delayed busy and transient error visuals from that source.
48
+ Both directory slots use `OpenInAppAction` with an explicit `absolutePath`. The `conversation.session.header.utilities` registration reads the Session cwd, while `sidebar.right.tab.files.actions` receives the displayed directory from its owner. The plugin registers the `open-in-app` dictionaries as one effect. A page-lifetime controller ([`src/client/controller.ts`](src/client/controller.ts)) owns the once-per-page availability read, the persisted choice snapshot store, and the launch POST; the component receives the shared sources through the inject `hooks` compartment, so every Session header shares one truth. Document-relative route forms and wire payload types come from the host package's browser-safe `@deepseek-ai/dsh-host-open-in-app/shared` subpath. The controller guards in-flight launches and publishes their captured directory and status; header controls derive delayed busy and transient error visuals from that source.
49
49
 
50
50
  The directory and file adapters supply application metadata and operations to [`OpenTargetButton`](src/client/OpenTargetButton.tsx), which owns menu ordering, default markers, icons, sizing, and gesture feedback. The file header and empty state share `FileOpenTarget`, while `OpenPathInjected.applications` queries `session.workspacePathApplications` through [`open-path.ts`](src/client/open-path.ts). Opening uses `session.openWorkspacePath`; the Host revalidates an explicitly selected handler before launch. The directory adapter keeps the existing catalog routes. `FileRouteAction` supplies the same control to delivery cards and change review through `deliverables.file.actions` and `deliverables.review.file.actions`; their authenticated routes retain Session file authorization. A failed or unavailable file query therefore needs no platform-specific UI implementation.
51
51
 
@@ -94,4 +94,4 @@ The feature-level decisions, including the split into the host package and this
94
94
 
95
95
  </details>
96
96
 
97
- **Runtime invariant:** No companion is published. The plugin registers one dictionary effect and five slot entries whose disposal the HMR-safety spec proves; application availability, the choice, and the desktop answer live in the controllers' snapshot stores with no second copy to diverge.
97
+ **Runtime invariant:** No companion is published. The plugin registers one dictionary effect and six slot entries whose disposal the HMR-safety spec proves; application availability, the choice, and the desktop answer live in the controllers' snapshot stores with no second copy to diverge.
package/README.zh.md CHANGED
@@ -29,7 +29,7 @@ kind: "package-reference"
29
29
 
30
30
  ### 预期行为
31
31
 
32
- 会话标题栏和文档标题栏共用同一个高 24px、圆角 9px 的分体按钮。两个标题栏都只显示图标,悬停提示显示默认应用名称或文件定位动作。两者都显示默认动作的图标,在菜单的默认应用后标注“(默认)”,仅在自己的操作执行期间禁用,并通过短暂提示报告失败。目录适配器使用已有的跨平台应用列表,将最后一次成功选择保存在 `dsh.open-in-app.choice` 中;原选择不可用时回退到第一个可用应用。
32
+ 会话标题栏、文件树标题栏和文档标题栏共用同一个高 24px、圆角 9px 的分体按钮。这些标题栏都只显示图标,悬停提示显示默认应用名称或文件定位动作。它们都显示默认动作的图标,在菜单的默认应用后标注“(默认)”,仅在自己的操作执行期间禁用,并通过短暂提示报告失败。文件树打开按钮以 `absolutePath` 传入的显示目录为目标,并与会话头部共享应用选择和启动状态。目录适配器使用已有的跨平台应用列表,将最后一次成功选择保存在 `dsh.open-in-app.choice` 中;原选择不可用时回退到第一个可用应用。
33
33
 
34
34
  **在本地打开**快捷键捕获主会话的目录,并使用与头部按钮相同的已记住应用。按钮 tooltip 和 `aria-keyshortcuts` 显示当前有效绑定;Web 遵循[快捷键服务的平台默认值](../shortcuts/README.zh.md)。只有选中会话界面、且头部按钮有可打开的目录和已安装应用时,命令才执行。启动尚未结束时也会阻止命令。鼠标与键盘操作共享 controller 的启动状态,重复操作不会启动两次,也不会在启动期间更改已记住的选择。
35
35
 
@@ -45,7 +45,7 @@ kind: "package-reference"
45
45
  <details>
46
46
  <summary>实现内幕——点击展开</summary>
47
47
 
48
- 插件通过标准 slot/inject 机制把分体按钮注册到 `conversation.session.header.utilities`,并以一个 effect 注册 `open-in-app` 词典。一个页面生命周期的 controller([`src/client/controller.ts`](src/client/controller.ts))拥有每页一次的可用性读取、持久化选择的 snapshot store 与启动 POST;组件经 inject 的 `hooks` 隔间接收共享源,因此所有会话头部共享同一份事实。文档相对的路由形式与 wire 载荷类型来自主机包的浏览器安全子路径 `@deepseek-ai/dsh-host-open-in-app/shared`。controller 守卫执行中的启动,并发布所捕获的目录与状态;头部控件从该源派生延迟出现的等待态和短暂错误态。
48
+ 两个目录插槽均通过显式的 `absolutePath` 使用 `OpenInAppAction`。`conversation.session.header.utilities` 的注册函数读取会话 cwd,`sidebar.right.tab.files.actions` 则接收所属组件传入的显示目录。插件以一个 effect 注册 `open-in-app` 词典。一个页面生命周期的 controller([`src/client/controller.ts`](src/client/controller.ts))拥有每页一次的可用性读取、持久化选择的 snapshot store 与启动 POST;组件经 inject 的 `hooks` 隔间接收共享源,因此所有会话头部共享同一份事实。文档相对的路由形式与 wire 载荷类型来自主机包的浏览器安全子路径 `@deepseek-ai/dsh-host-open-in-app/shared`。controller 守卫执行中的启动,并发布所捕获的目录与状态;头部控件从该源派生延迟出现的等待态和短暂错误态。
49
49
 
50
50
  目录和文件适配器把应用信息与操作交给 [`OpenTargetButton`](src/client/OpenTargetButton.tsx),由它统一管理菜单顺序、默认标记、图标、尺寸和操作反馈。文件标题栏和空态共用 `FileOpenTarget`,`OpenPathInjected.applications` 通过 [`open-path.ts`](src/client/open-path.ts) 查询 `session.workspacePathApplications`。打开操作使用 `session.openWorkspacePath`,Host 在启动前重新验证指定的关联应用。`FileRouteAction` 通过 `deliverables.file.actions` 和 `deliverables.review.file.actions` 为交付卡片和变更对比页提供同一控件,其认证路由保留会话文件校验。目录适配器继续使用已有的应用列表路由,文件查询失败或不可用时无需增加平台专用的界面实现。
51
51
 
@@ -94,4 +94,4 @@ Host 在打开或定位前通过当前文件系统验证路径。没有对应 Ho
94
94
 
95
95
  </details>
96
96
 
97
- **运行时不变式:** 不发布伴生入口。插件注册一个词典 effect 和五个 slot 条目,HMR 安全性 spec 证明它们都会在资源释放时撤销;应用可用性、选择与桌面回答存储在控制器的快照存储中,不存在可能与之分歧的第二份副本。
97
+ **运行时不变式:** 不发布伴生入口。插件注册一个词典 effect 和六个 slot 条目,HMR 安全性 spec 证明它们都会在资源释放时撤销;应用可用性、选择与桌面回答存储在控制器的快照存储中,不存在可能与之分歧的第二份副本。
package/lib/client.js CHANGED
@@ -4,9 +4,9 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
7
8
  let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
8
9
  let react_jsx_runtime = require("react/jsx-runtime");
9
- let react = require("react");
10
10
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
11
11
  /** Browser-relative form of {@link OPEN_IN_APP_APPS_PATH}. */
12
12
  const OPEN_IN_APP_APPS_ROUTE = "/open-in-app/apps".slice(1);
@@ -364,12 +364,11 @@ window.__ModuleLoader__.load({
364
364
  //#region lib/types/client/OpenInAppAction.js
365
365
  /**
366
366
  * Adapt the installed directory catalog to the shared opening control.
367
- * @param props - workspace state, installed catalog, and launch operations.
368
- * @returns the shared control, or null without an eligible application and directory.
367
+ * @param props - displayed directory, installed catalog, and launch operations.
368
+ * @returns the shared control, or null without an eligible application.
369
369
  */
370
370
  function OpenInAppAction(props) {
371
- const { sessionId, useSessions, useOpenInAppApps, useOpenInAppChoice, t } = props;
372
- const cwd = useSessions((state) => state.byId[sessionId]?.cwd);
371
+ const { absolutePath, useOpenInAppApps, useOpenInAppChoice, t } = props;
373
372
  const available = useOpenInAppApps((apps) => apps);
374
373
  const choice = useOpenInAppChoice((id) => id);
375
374
  const operation = props.useOpenInAppLaunch((value) => value);
@@ -383,7 +382,7 @@ window.__ModuleLoader__.load({
383
382
  }];
384
383
  });
385
384
  const preferred = apps.find((app) => app.id === choice) ?? apps[0];
386
- if (preferred === void 0 || cwd === void 0 || cwd === "") return null;
385
+ if (preferred === void 0) return null;
387
386
  return (0, react_jsx_runtime.jsx)(OpenTargetButton, {
388
387
  kind: "directory",
389
388
  applications: apps,
@@ -395,14 +394,14 @@ window.__ModuleLoader__.load({
395
394
  execute: async (operation) => {
396
395
  const id = operation.kind === "application" ? operation.id : preferred.id;
397
396
  try {
398
- await props.launch(id, cwd);
397
+ await props.launch(id, absolutePath);
399
398
  } catch (_error) {
400
399
  return "openError";
401
400
  }
402
401
  if (operation.kind === "application") props.choose(id);
403
402
  return null;
404
403
  }
405
- }, cwd);
404
+ }, absolutePath);
406
405
  }
407
406
  //#endregion
408
407
  //#region lib/types/client/open-path.js
@@ -753,8 +752,8 @@ window.__ModuleLoader__.load({
753
752
  "layout"
754
753
  ];
755
754
  /**
756
- * Client plugin body: register the dictionaries, the header split button, and
757
- * the document preview's path controls.
755
+ * Client plugin body: register dictionaries, workspace directory controls, and
756
+ * document preview path controls.
758
757
  * @param ctx - client root context.
759
758
  */
760
759
  function apply(ctx) {
@@ -823,24 +822,39 @@ window.__ModuleLoader__.load({
823
822
  };
824
823
  }
825
824
  }), "open-in-app: workspace command");
825
+ const directoryInjected = () => ({
826
+ hooks: {
827
+ openInAppApps: controller.apps,
828
+ openInAppChoice: controller.choice,
829
+ openInAppLaunch: controller.operation,
830
+ shortcuts: ctx.shortcuts.catalog
831
+ },
832
+ launch: (appId, path) => controller.launch(appId, path),
833
+ choose: (appId) => {
834
+ controller.choose(appId);
835
+ },
836
+ iconUrl: (appId) => `${OPEN_IN_APP_ICON_PREFIX_ROUTE}/${appId}`
837
+ });
838
+ function SessionOpenInAppAction(props) {
839
+ const { sessionId, useSessions } = props;
840
+ const cwd = useSessions((state) => state.byId[sessionId]?.cwd);
841
+ return cwd ? (0, react.createElement)(OpenInAppAction, {
842
+ ...props,
843
+ absolutePath: cwd
844
+ }) : null;
845
+ }
826
846
  ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
827
847
  name: "conversation.session.header.utilities",
828
848
  id: "open-in-app",
829
849
  order: -10,
830
850
  locale: NS,
831
- inject: () => ({
832
- hooks: {
833
- openInAppApps: controller.apps,
834
- openInAppChoice: controller.choice,
835
- openInAppLaunch: controller.operation,
836
- shortcuts: ctx.shortcuts.catalog
837
- },
838
- launch: (appId, path) => controller.launch(appId, path),
839
- choose: (appId) => {
840
- controller.choose(appId);
841
- },
842
- iconUrl: (appId) => `${OPEN_IN_APP_ICON_PREFIX_ROUTE}/${appId}`
843
- })
851
+ inject: directoryInjected
852
+ }, SessionOpenInAppAction));
853
+ ctx.slots.inject("sidebar.right.tab.files.actions", () => ctx.slots.register({
854
+ name: "sidebar.right.tab.files.actions",
855
+ id: "open-in-app",
856
+ locale: NS,
857
+ inject: directoryInjected
844
858
  }, OpenInAppAction));
845
859
  const applications = (path, signal) => paths.applications(path, signal);
846
860
  const pathInjected = () => ({
@@ -1,9 +1,9 @@
1
1
  import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store';
2
- import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import type { InjectFace, PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
3
3
  import type { OpenInAppLaunchState } from './controller.ts';
4
4
  import type { ShortcutCatalogEntry } from '@deepseek-ai/dsh-client-shortcuts/client';
5
5
  import { NS } from './locales.ts';
6
- /** Browser operations and state injected into the Session Header contribution. */
6
+ /** Browser operations and state shared by the Session header and file tree contributions. */
7
7
  export interface OpenInAppActionInjected {
8
8
  hooks: {
9
9
  openInAppApps: ObservableSnapshot<readonly string[] | null>;
@@ -15,12 +15,14 @@ export interface OpenInAppActionInjected {
15
15
  choose: (appId: string) => void;
16
16
  iconUrl: (appId: string) => string;
17
17
  }
18
- /** Full props for the Session-header open-in-app split button. */
19
- export type OpenInAppActionProps = PropsRuntime<'conversation.session.header.utilities'> & PropsLocale<typeof NS> & InjectFace<OpenInAppActionInjected>;
18
+ /** Directory path, installed applications, and launch operations for the split button. */
19
+ export type OpenInAppActionProps = PropsLocale<typeof NS> & InjectFace<OpenInAppActionInjected> & {
20
+ absolutePath: string;
21
+ };
20
22
  /**
21
23
  * Adapt the installed directory catalog to the shared opening control.
22
- * @param props - workspace state, installed catalog, and launch operations.
23
- * @returns the shared control, or null without an eligible application and directory.
24
+ * @param props - displayed directory, installed catalog, and launch operations.
25
+ * @returns the shared control, or null without an eligible application.
24
26
  */
25
27
  export declare function OpenInAppAction(props: OpenInAppActionProps): React.JSX.Element | null;
26
28
  //# sourceMappingURL=OpenInAppAction.d.ts.map
@@ -18,8 +18,8 @@ export type { OpenPathEmptyActionProps } from './OpenPathEmptyAction.tsx';
18
18
  /** Required services: sessions, layout selection, the slot registry, copy, Remote calls, and shortcuts. */
19
19
  export declare const inject: string[];
20
20
  /**
21
- * Client plugin body: register the dictionaries, the header split button, and
22
- * the document preview's path controls.
21
+ * Client plugin body: register dictionaries, workspace directory controls, and
22
+ * document preview path controls.
23
23
  * @param ctx - client root context.
24
24
  */
25
25
  export declare function apply(ctx: ClientContext): void;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-open-in-app",
3
3
  "description": "Web \"Open In...\" controls: the Session-header split button opening the workspace directory in an installed application, and the document preview's default-application controls for one file",
4
- "version": "0.1.7-rc.2",
4
+ "version": "0.2.0-rc.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -54,25 +54,26 @@
54
54
  "devDependencies": {
55
55
  "@types/react": "~18.3.1",
56
56
  "react": "^18.2.0",
57
- "@deepseek-ai/dsh-api-gateway": "0.1.7-rc.2",
58
57
  "@deepseek-ai/cordis": "~4.0.4",
59
- "@deepseek-ai/dsh-api-remotes": "0.1.7-rc.2",
60
- "@deepseek-ai/dsh-api-session-controller": "0.1.7-rc.2",
61
- "@deepseek-ai/dsh-client-locale": "0.1.7-rc.2",
62
- "@deepseek-ai/dsh-client-store": "0.1.7-rc.2",
63
- "@deepseek-ai/dsh-client-test-runtime": "0.1.7-rc.2",
64
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.7-rc.2",
65
- "@deepseek-ai/dsh-client-ui-layout": "0.1.7-rc.2",
66
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2",
67
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.2",
68
- "@deepseek-ai/dsh-client-ui-session": "0.1.7-rc.2",
69
- "@deepseek-ai/dsh-client-ui-sidebar-documentpreview": "0.1.7-rc.2",
70
- "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.2",
71
- "@deepseek-ai/dsh-session": "0.1.7-rc.2",
72
- "@deepseek-ai/dsh-native-command": "0.1.7-rc.2",
73
- "@deepseek-ai/dsh-host-open-in-app": "0.1.7-rc.2",
74
- "@deepseek-ai/dsh-client-shortcuts": "0.1.7-rc.2",
75
- "@deepseek-ai/dsh-client-ui-deliverables": "0.1.7-rc.2"
58
+ "@deepseek-ai/dsh-api-gateway": "0.2.0-rc.2",
59
+ "@deepseek-ai/dsh-api-remotes": "0.2.0-rc.2",
60
+ "@deepseek-ai/dsh-api-session-controller": "0.2.0-rc.2",
61
+ "@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
62
+ "@deepseek-ai/dsh-client-store": "0.2.0-rc.2",
63
+ "@deepseek-ai/dsh-client-test-runtime": "0.2.0-rc.2",
64
+ "@deepseek-ai/dsh-client-ui-conversation": "0.2.0-rc.2",
65
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.2",
66
+ "@deepseek-ai/dsh-client-ui-session": "0.2.0-rc.2",
67
+ "@deepseek-ai/dsh-client-ui-sidebar-documentpreview": "0.2.0-rc.2",
68
+ "@deepseek-ai/dsh-client-ui-sidebar-files": "0.2.0-rc.2",
69
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
70
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2",
71
+ "@deepseek-ai/dsh-session": "0.2.0-rc.2",
72
+ "@deepseek-ai/dsh-client-ui-layout": "0.2.0-rc.2",
73
+ "@deepseek-ai/dsh-native-command": "0.2.0-rc.2",
74
+ "@deepseek-ai/dsh-client-shortcuts": "0.2.0-rc.2",
75
+ "@deepseek-ai/dsh-host-open-in-app": "0.2.0-rc.2",
76
+ "@deepseek-ai/dsh-client-ui-deliverables": "0.2.0-rc.2"
76
77
  },
77
78
  "scripts": {
78
79
  "bundle": "tsdown",