@deepseek-ai/dsh-client-ui-workspace 0.1.0-rc.6 → 0.1.0-rc.8

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
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/client/ui-workspace/README.md
5
- README.md: 8878aa49dcccd60ddcde5f0a9563bbfbd969c9c0
6
- README.zh.md: c83f1c53d5471dd8d52b933163a573eb98a70c2e
5
+ README.md: 041c886ae3f38415ac01fbfc3546a1f32581257c
6
+ README.zh.md: 96d9bc33b8ed47372a0e01c4992dfcef59f2432f
package/README.md CHANGED
@@ -6,9 +6,9 @@ Shared Workspace browser and picker plugin. `WorkspaceBrowser` fills the sidebar
6
6
 
7
7
  The browser renders grouped or flat Session rows from the global runtime hooks and owns Workspace add/rename/reorder plus Session reorder. A Workspace remembers whether it is closed or showing Sessions; an open Workspace shows five Sessions by default, offers a transient **Show more** control for the remainder, and returns to five after the whole Workspace is closed and reopened. Creating a Session from a Workspace row first opens that group so the new row remains visible when the Session state arrives. Once the Workspace list baseline is ready, browser-persisted expansion and Session-order records retain only current Workspace ids plus Ungrouped and the flat-list account. View options combine grouping with one browser-persisted Session order per account: real Workspaces initialize from `WorkspaceView.sessionIds`, while Ungrouped and the cross-Workspace flat list initialize from recency. **Manual** and **Last updated** apply in either presentation. Entering Last updated performs a complete recency sort and later user prompts or steers promote their Session once, while entering Manual preserves every current position and disables later promotion. Dragging edits the current order in either mode; Manual-mode drags for real Workspaces also update the Host Session account, while Ungrouped and flat-list orders remain browser-local because neither has one Workspace account. Flat rows omit the empty leading status slot because they have no parent hierarchy, but retain it when a Session status is visible. Workspace drag order is Host-durable in either Session order mode.
8
8
 
9
- Collapsed search is one header action beside the view and add actions. In the rail, add and search render as 36px controls on the shell's shared horizontal entry path. Activating search expands the field across the header; an outside click collapses only a query that is empty after trimming, while the clear control always resets and collapses it. A non-blank search query replaces either browsing mode with one flat result list: case-insensitive title and Workspace substring matches appear immediately, while a 250 ms debounced Host request adds ranked current-conversation content matches and snippets. The English search input and its defensive request path remove NUL, cap the query at the wire schema's 500 UTF-16 code units without splitting a surrogate pair, and preserve the existing debounce and cancellation behavior. Each new query aborts the preceding request; a failed content search leaves metadata matches visible with a warning. The list is capped at 20, asks the user to narrow broader queries, and opens the selected Session without clearing the query or jumping to a specific event.
9
+ Collapsed search is one header action beside the view and add actions. In the rail, add and search render as 36px controls on the shell's shared horizontal entry path. Activating search expands the field across the header; an outside click collapses only a query that is empty after trimming — except while the rail search gesture is still in flight (until focus lands in the input after the column slide), so the expanding click cannot dismiss the search it opened — while the clear control always resets and collapses it. A non-blank search query replaces either browsing mode with one flat result list: case-insensitive title and Workspace substring matches appear immediately, while a 250 ms debounced Host request adds ranked current-conversation content matches and snippets. The English search input and its defensive request path remove NUL, cap the query at the wire schema's 500 UTF-16 code units without splitting a surrogate pair, and preserve the existing debounce and cancellation behavior. Each new query aborts the preceding request; a failed content search leaves metadata matches visible with a warning. The list is capped at 20, asks the user to narrow broader queries, and opens the selected Session without clearing the query or jumping to a specific event.
10
10
 
11
- The picker lists real Host Workspace entities through the global `useWorkspaces` hook. Selecting a Workspace invokes the slot owner's `onPick` callback to retarget the frontend Session object. Distinct canonical paths remain separate id-keyed Workspaces when their basenames and display titles match; the sidebar hover detail exposes the full path. Each registration declares a **directory-flow child hole** (`single` kind: `conversation.hero.workspace.directoryFlow` / `sidebar.workspaces.directoryFlow`) that the composed picker package's client half fills with its picking interaction — the [`-native`](../../host/directory-picker-native/README.md) backend's renderless OS-chooser driver today, an in-app browsing dialog under a `-browse` composition. The flat **Add workspace...** action renders only while the surface's hole is occupied (occupancy read per menu render; an empty hole means the composition has no picking affordance — the seam's documented no-flow default, under which the sidebar header drops its add button rather than offering a dead one). This package owns the trigger and the adoption: the occupant reports one picked path per open through the hole's owner conversation (`open`/`busy`/`onPicked`/`onCancel`/`onError`), and the owner adopts it through the object layer, selecting the committed Workspace only after its list projection has refreshed; cancellation is silent, and errors land in the retryable folder dialog whose **Choose again** reopens the flow. Adding has exactly one route: the occupant's own create-folder affordance already covers a brand-new directory, so no separate create-by-name dialog exists. A menu only appears where there is something to choose between — with no Workspace listed, the anchor gesture raises the flow directly instead of a one-row popover, and it waits for the list baseline before treating an empty list as final. The runtime Session and Workspace services own materialization. The Workspace row's Delete action opens a confirmation that states the retention boundary, blocks duplicate submission, and keeps failures open; success removes the group while its Sessions remain under Ungrouped. The Session row's Rename action opens the same browser-owned dialog pattern prefilled with the row's display title: no client-side conflict rule exists (the host normalizes and may reject with `title-invalid`, rendered in the dialog alert), and confirming an unchanged title is deliberately allowed — it pins the current automatic title against regeneration. The Session row's Archive action commits without a confirmation dialog (non-destructive: the log and the workspace accounting slot remain) through `ctx.workspaces.archiveSession`; the row disappears from every grouping surface — workspace groups, Ungrouped, content search, and the flat list — when the archive-set echo lands, and failures are console diagnostics that leave the tree unchanged. A blank New Session row is a pure placeholder: it renders no row menu and no time label (nothing has happened in it yet), so rename, fork, and archive first apply once the first prompt lands.
11
+ The picker lists real Host Workspace entities through the global `useWorkspaces` hook. Selecting a Workspace invokes the slot owner's `onPick` callback to retarget the frontend Session object. Distinct canonical paths remain separate id-keyed Workspaces when their basenames and display titles match; the sidebar hover detail shows a POSIX home or descendant as `~` / `~/…` and leaves a Windows path verbatim. Each registration declares a **directory-flow child hole** (`single` kind: `conversation.hero.workspace.directoryFlow` / `sidebar.workspaces.directoryFlow`) that the composed picker package's client half fills with its picking interaction — the [`-native`](../../host/directory-picker-native/README.md) backend's renderless OS-chooser driver today, an in-app browsing dialog under a `-browse` composition. The flat **Add workspace...** action renders only while the surface's hole is occupied (occupancy read per menu render; an empty hole means the composition has no picking affordance — the seam's documented no-flow default, under which the sidebar header drops its add button rather than offering a dead one). This package owns the trigger and the adoption: the occupant reports one picked path per open through the hole's owner conversation (`open`/`busy`/`onPicked`/`onCancel`/`onError`), and the owner adopts it through the object layer, selecting the committed Workspace only after its list projection has refreshed; cancellation is silent, and errors land in the retryable folder dialog whose **Choose again** reopens the flow. Adding has exactly one route: the occupant's own create-folder affordance already covers a brand-new directory, so no separate create-by-name dialog exists. A menu only appears where there is something to choose between — with no Workspace listed, the anchor gesture raises the flow directly instead of a one-row popover, and it waits for the list baseline before treating an empty list as final. The runtime Session and Workspace services own materialization. The Workspace row's Delete action opens a confirmation that states the retention boundary, blocks duplicate submission, and keeps failures open; success removes the group while its Sessions remain under Ungrouped. The Session row's Rename action opens the same browser-owned dialog pattern prefilled with the row's display title: no client-side conflict rule exists (the host normalizes and may reject with `title-invalid`, rendered in the dialog alert), and confirming an unchanged title is deliberately allowed — it pins the current automatic title against regeneration. The Session row's Archive action commits without a confirmation dialog (non-destructive: the log and the workspace accounting slot remain) through `ctx.workspaces.archiveSession`; the row disappears from every grouping surface — workspace groups, Ungrouped, content search, and the flat list — when the archive-set echo lands, and failures are console diagnostics that leave the tree unchanged. A blank New Session row is a pure placeholder: it renders no row menu and no time label (nothing has happened in it yet), so rename, fork, and archive first apply once the first prompt lands.
12
12
 
13
13
  Workspace and Session hover cards copy the value their row clips: activating a Workspace card writes its full directory path, while activating a non-blank Session card writes its full display title. A provisional blank New Session card remains read-only because its localized label is a placeholder rather than session content. The card reports the dictionary-driven copied state only after the browser accepts the clipboard write.
14
14
 
package/README.zh.md CHANGED
@@ -6,9 +6,9 @@
6
6
 
7
7
  该浏览器通过全局运行时钩子将 Session 行渲染为分组或扁平形式,并负责 Workspace 添加/重命名/重排序以及 Session 重排序。每个 Workspace 会记住自身是关闭还是显示 Session;打开后默认显示五条 Session,其余条目通过临时的**展开其余**控件显示,而关闭并重新打开整个 Workspace 后会恢复为五条。从 Workspace 行创建 Session 时会先打开该分组,使 Session 状态到达后新行保持可见。Workspace 列表基线就绪后,浏览器持久化的展开状态与 Session 顺序记录只保留当前 Workspace id、Ungrouped 和单列表记账。视图选项把分组方式和每个记账各自的一份浏览器持久化 Session 顺序放在一起:真实 Workspace 从 `WorkspaceView.sessionIds` 初始化,Ungrouped 和跨 Workspace 的单列表则从最近更新时间顺序初始化。**手动排序**和**最近更新**在两种呈现方式下都可用。进入最近更新时会执行一次完整的时间排序,后续 user prompt 或 steer 会将对应 Session 置顶一次;进入手动排序则保留所有当前位置并停用后续置顶。两种模式下的拖拽都会编辑当前顺序;真实 Workspace 在手动模式下的拖拽还会更新 Host Session 记账,而 Ungrouped 和单列表因没有单一 Workspace 记账,其顺序始终只保存在浏览器本地。单列表没有父级层次,因此不显示空的左侧状态槽;Session 存在可见状态时仍保留该槽。无论采用哪种 Session 顺序,Workspace 拖拽顺序都由 Host 持久化。
8
8
 
9
- 折叠搜索是视图和添加操作旁的一枚区头按钮。在轨道中,添加和搜索会渲染为沿外壳共用横向进入路径移动的 36px 控件。激活搜索后,输入框会扩展并占据区头;点击外部只会收起经清除首尾空白后为空的查询,而清除控件总会重置并收起搜索。非空白查询会以单一扁平结果列表替代任一浏览模式:不区分大小写的标题和 Workspace 子串匹配项会立即显示,经 250 ms 防抖的 Host 请求则会加入经过排序的当前对话内容匹配项及其摘要片段。英文搜索输入框及其防御性请求路径会移除 NUL,将查询限制在传输 schema 规定的 500 个 UTF-16 代码单元内且不会拆分代理项对,并保留现有的防抖与取消行为。每次新查询都会中止前一个请求;内容搜索失败时,元数据匹配项仍会显示,同时给出警告。列表最多显示 20 条结果,并会在查询过宽时提示用户缩小范围;打开所选 Session 时既不会清除查询,也不会跳转至特定事件。
9
+ 折叠搜索是视图和添加操作旁的一枚区头按钮。在轨道中,添加和搜索会渲染为沿外壳共用横向进入路径移动的 36px 控件。激活搜索后,输入框会扩展并占据区头;点击外部只会收起经清除首尾空白后为空的查询——但轨道搜索手势仍在进行期间(直至列滑动结束、焦点落入输入框)除外,这样触发展开的那次点击不会收起它刚打开的搜索——而清除控件总会重置并收起搜索。非空白查询会以单一扁平结果列表替代任一浏览模式:不区分大小写的标题和 Workspace 子串匹配项会立即显示,经 250 ms 防抖的 Host 请求则会加入经过排序的当前对话内容匹配项及其摘要片段。英文搜索输入框及其防御性请求路径会移除 NUL,将查询限制在传输 schema 规定的 500 个 UTF-16 代码单元内且不会拆分代理项对,并保留现有的防抖与取消行为。每次新查询都会中止前一个请求;内容搜索失败时,元数据匹配项仍会显示,同时给出警告。列表最多显示 20 条结果,并会在查询过宽时提示用户缩小范围;打开所选 Session 时既不会清除查询,也不会跳转至特定事件。
10
10
 
11
- 该选择器通过全局 `useWorkspaces` hook 列出真实的 Host Workspace 实体。选择 Workspace 会调用 slot owner 的 `onPick` 回调,重新定位前端 Session 对象。不同的规范化路径即使 basename 和显示标题相同,仍会作为由 id 区分的独立 Workspace;侧边栏的悬停详情会显示完整路径。每个注册各自声明一个**目录流子 slot**(`single` kind:`conversation.hero.workspace.directoryFlow`/`sidebar.workspaces.directoryFlow`),由组合的选择器包 client half 填入其选取交互——今天是 [`-native`](../../host/directory-picker-native/README.md) 后端的无渲染 OS 选择器驱动,`-browse` 组合下则是应用内浏览对话框。平铺显示的 **添加工作区…** 操作仅在当前界面的 slot 被占用时渲染(每次菜单渲染读取占用状态;slot 为空意味着该组合没有目录选择能力——seam 文档化的无流程默认行为,此时侧边栏区头直接不渲染添加按钮,而非留下一个点了没反应的按钮)。本包持有触发与接纳:占用方通过 slot 的属主交互约定(`open`/`busy`/`onPicked`/`onCancel`/`onError`)每次打开上报一个所选路径,owner 通过对象层接纳它,并等待 Workspace 列表投影刷新后才选中已提交的 Workspace;取消操作不会显示提示,错误落入可重试的文件夹对话框,其 **重新选择** 会重新打开流程。添加只有一条路径:占用者自带的新建文件夹能力已经覆盖了全新目录,因此不再单设按名称创建的对话框。菜单只在确有多个目标可选时出现——没有 Workspace 可列时,锚点手势直接拉起流程,而不是弹出只有一行的浮层;在列表基线落地前,空列表不算最终结果。运行时 Session 与 Workspace 服务负责物化。Workspace 行内的 Delete 操作会打开确认框,说明保留边界、阻止重复提交,并在失败时保持打开;成功后,该分组会被移除,其 Session 则留在 Ungrouped 下。Session 行内的 Rename 操作打开同款浏览器持有的对话框,并以该行的显示标题预填:客户端不设名称冲突规则(host 负责规范化,可能以 `title-invalid` 拒绝,错误渲染在对话框告警区);确认未修改的标题是有意允许的——这正是把当前自动标题钉住、不再被重新生成覆盖的手势。Session 行内的 Archive 操作不经确认对话框直接提交(非破坏性:日志和 workspace 记账席位保持不变),通过 `ctx.workspaces.archiveSession` 归档;归档集合回声落地后,该行从所有分组视图——workspace 分组、Ungrouped、内容搜索和平铺列表——中消失,失败只作为控制台诊断输出,树保持不变。空白的「新会话」行只是占位符:不渲染行菜单和时间标签(其中还没有发生任何事),重命名、fork 和归档都从首条提示词落地后才可用。
11
+ 该选择器通过全局 `useWorkspaces` hook 列出真实的 Host Workspace 实体。选择 Workspace 会调用 slot owner 的 `onPick` 回调,重新定位前端 Session 对象。不同的规范化路径即使 basename 和显示标题相同,仍会作为由 id 区分的独立 Workspace;侧边栏的悬停详情把 POSIX 家目录及其后代显示为 `~`/`~/…`,Windows 路径保持原样。每个注册各自声明一个**目录流子 slot**(`single` kind:`conversation.hero.workspace.directoryFlow`/`sidebar.workspaces.directoryFlow`),由组合的选择器包 client half 填入其选取交互——今天是 [`-native`](../../host/directory-picker-native/README.md) 后端的无渲染 OS 选择器驱动,`-browse` 组合下则是应用内浏览对话框。平铺显示的 **添加工作区…** 操作仅在当前界面的 slot 被占用时渲染(每次菜单渲染读取占用状态;slot 为空意味着该组合没有目录选择能力——seam 文档化的无流程默认行为,此时侧边栏区头直接不渲染添加按钮,而非留下一个点了没反应的按钮)。本包持有触发与接纳:占用方通过 slot 的属主交互约定(`open`/`busy`/`onPicked`/`onCancel`/`onError`)每次打开上报一个所选路径,owner 通过对象层接纳它,并等待 Workspace 列表投影刷新后才选中已提交的 Workspace;取消操作不会显示提示,错误落入可重试的文件夹对话框,其 **重新选择** 会重新打开流程。添加只有一条路径:占用者自带的新建文件夹能力已经覆盖了全新目录,因此不再单设按名称创建的对话框。菜单只在确有多个目标可选时出现——没有 Workspace 可列时,锚点手势直接拉起流程,而不是弹出只有一行的浮层;在列表基线落地前,空列表不算最终结果。运行时 Session 与 Workspace 服务负责物化。Workspace 行内的 Delete 操作会打开确认框,说明保留边界、阻止重复提交,并在失败时保持打开;成功后,该分组会被移除,其 Session 则留在 Ungrouped 下。Session 行内的 Rename 操作打开同款浏览器持有的对话框,并以该行的显示标题预填:客户端不设名称冲突规则(host 负责规范化,可能以 `title-invalid` 拒绝,错误渲染在对话框告警区);确认未修改的标题是有意允许的——这正是把当前自动标题钉住、不再被重新生成覆盖的手势。Session 行内的 Archive 操作不经确认对话框直接提交(非破坏性:日志和 workspace 记账席位保持不变),通过 `ctx.workspaces.archiveSession` 归档;归档集合回声落地后,该行从所有分组视图——workspace 分组、Ungrouped、内容搜索和平铺列表——中消失,失败只作为控制台诊断输出,树保持不变。空白的「新会话」行只是占位符:不渲染行菜单和时间标签(其中还没有发生任何事),重命名、fork 和归档都从首条提示词落地后才可用。
12
12
 
13
13
  Workspace 和 Session 悬浮卡片会复制对应行被截断的值:激活 Workspace 卡片会写入其完整目录路径,激活非空白 Session 卡片则会写入其完整显示标题。临时的空白「新会话」卡片保持只读,因为其本地化标签是占位文案,并非会话内容。只有浏览器接受剪贴板写入后,卡片才会显示由字典提供的已复制状态。
14
14
 
package/lib/client.js CHANGED
@@ -341,40 +341,40 @@ window.__ModuleLoader__.load({
341
341
  document.head.appendChild(tag);
342
342
  }
343
343
  var Rows_module_css_default = {
344
- "hoverTitle": "YDXeBa_hoverTitle",
345
- "title": "YDXeBa_title",
346
- "hoverContent": "YDXeBa_hoverContent",
347
- "dropAfter": "YDXeBa_dropAfter",
348
- "renameInput": "YDXeBa_renameInput",
344
+ "arrow": "YDXeBa_arrow",
345
+ "arrowOpen": "YDXeBa_arrowOpen",
346
+ "chevron": "YDXeBa_chevron",
349
347
  "dot": "YDXeBa_dot",
350
- "hoverTime": "YDXeBa_hoverTime",
351
- "iconButton": "YDXeBa_iconButton",
348
+ "dropAfter": "YDXeBa_dropAfter",
349
+ "dropBefore": "YDXeBa_dropBefore",
352
350
  "flatSessionRowWithoutStatus": "YDXeBa_flatSessionRowWithoutStatus",
353
- "row-in": "YDXeBa_row-in",
354
351
  "folder": "YDXeBa_folder",
352
+ "folderActive": "YDXeBa_folderActive",
353
+ "hoverContent": "YDXeBa_hoverContent",
354
+ "hoverPath": "YDXeBa_hoverPath",
355
+ "hoverStatus": "YDXeBa_hoverStatus",
356
+ "hoverTime": "YDXeBa_hoverTime",
357
+ "hoverTitle": "YDXeBa_hoverTitle",
358
+ "iconButton": "YDXeBa_iconButton",
355
359
  "menuOpen": "YDXeBa_menuOpen",
356
- "selected": "YDXeBa_selected",
357
- "searchResultHeading": "YDXeBa_searchResultHeading",
358
- "searchResultWorkspace": "YDXeBa_searchResultWorkspace",
359
- "visuallyHidden": "YDXeBa_visuallyHidden",
360
+ "meta": "YDXeBa_meta",
360
361
  "projectRow": "YDXeBa_projectRow",
361
- "hoverStatus": "YDXeBa_hoverStatus",
362
- "arrowOpen": "YDXeBa_arrowOpen",
362
+ "projectText": "YDXeBa_projectText",
363
+ "renameInput": "YDXeBa_renameInput",
364
+ "row-in": "YDXeBa_row-in",
363
365
  "rowActions": "YDXeBa_rowActions",
364
- "chevron": "YDXeBa_chevron",
365
- "arrow": "YDXeBa_arrow",
366
- "searchResultTitle": "YDXeBa_searchResultTitle",
366
+ "searchResultHeading": "YDXeBa_searchResultHeading",
367
367
  "searchResultMeta": "YDXeBa_searchResultMeta",
368
+ "searchResultRow": "YDXeBa_searchResultRow",
369
+ "searchResultSnippet": "YDXeBa_searchResultSnippet",
370
+ "searchResultTitle": "YDXeBa_searchResultTitle",
371
+ "searchResultWorkspace": "YDXeBa_searchResultWorkspace",
372
+ "selected": "YDXeBa_selected",
373
+ "sessionRow": "YDXeBa_sessionRow",
368
374
  "slot": "YDXeBa_slot",
369
- "folderActive": "YDXeBa_folderActive",
370
375
  "time": "YDXeBa_time",
371
- "sessionRow": "YDXeBa_sessionRow",
372
- "meta": "YDXeBa_meta",
373
- "dropBefore": "YDXeBa_dropBefore",
374
- "searchResultSnippet": "YDXeBa_searchResultSnippet",
375
- "projectText": "YDXeBa_projectText",
376
- "hoverPath": "YDXeBa_hoverPath",
377
- "searchResultRow": "YDXeBa_searchResultRow"
376
+ "title": "YDXeBa_title",
377
+ "visuallyHidden": "YDXeBa_visuallyHidden"
378
378
  };
379
379
  //#endregion
380
380
  //#region lib/types/client/rows/Rows.js
@@ -413,7 +413,7 @@ window.__ModuleLoader__.load({
413
413
  d: d.getDate()
414
414
  })} ${pad2(d.getHours())}:${pad2(d.getMinutes())}` });
415
415
  }
416
- /** Hover-card body: workspace title, full directory path, absolute creation time. */
416
+ /** Hover-card body: workspace title, display directory path, absolute creation time. */
417
417
  function WorkspaceHoverContent({ label, cwd, createdAt, t }) {
418
418
  return (0, react_jsx_runtime.jsxs)("div", {
419
419
  className: Rows_module_css_default.hoverContent,
@@ -447,10 +447,11 @@ window.__ModuleLoader__.load({
447
447
  * @param props.onToggle - expand/collapse the group.
448
448
  * @param props.onCreate - start a frontend Session inside this Workspace.
449
449
  * @param props.drag - optional workspace-row drag wiring.
450
+ * @param props.home - host account home for POSIX hover-path abbreviation.
450
451
  * @param props.t - the browser root's locale seat.
451
452
  * @returns the row element.
452
453
  */
453
- function ProjectRowItem({ group, onToggle, onCreate, actions, drag, t }) {
454
+ function ProjectRowItem({ group, onToggle, onCreate, actions, drag, home, t }) {
454
455
  const row = group;
455
456
  const label = row.workspaceId === void 0 ? t("group.ungrouped") : row.label;
456
457
  const active = group.expanded && group.containsCurrent;
@@ -538,7 +539,7 @@ window.__ModuleLoader__.load({
538
539
  anchor: ownRow,
539
540
  content: (0, react_jsx_runtime.jsx)(WorkspaceHoverContent, {
540
541
  label: row.label,
541
- cwd: row.cwd,
542
+ cwd: row.cwd === void 0 ? void 0 : (0, _deepseek_ai_dsh_client_runtime_client.abbreviateHomePath)(row.cwd, home),
542
543
  createdAt: row.createdAt,
543
544
  t
544
545
  }),
@@ -805,8 +806,8 @@ window.__ModuleLoader__.load({
805
806
  document.head.appendChild(tag);
806
807
  }
807
808
  var WorkspacePicker_module_css_default = {
808
- "modalAction": "_G5b-a_modalAction",
809
809
  "menuStatus": "_G5b-a_menuStatus",
810
+ "modalAction": "_G5b-a_modalAction",
810
811
  "modalError": "_G5b-a_modalError"
811
812
  };
812
813
  //#endregion
@@ -975,42 +976,42 @@ window.__ModuleLoader__.load({
975
976
  document.head.appendChild(tag);
976
977
  }
977
978
  var WorkspaceBrowser_module_css_default = {
978
- "wide-in": "qDHVXG_wide-in",
979
- "searchWarning": "qDHVXG_searchWarning",
980
- "empty": "qDHVXG_empty",
979
+ "clearButton": "qDHVXG_clearButton",
980
+ "deleteAction": "qDHVXG_deleteAction",
981
981
  "deleteStatus": "qDHVXG_deleteStatus",
982
- "search": "qDHVXG_search",
982
+ "empty": "qDHVXG_empty",
983
983
  "fade": "qDHVXG_fade",
984
- "workspaceDropAfter": "qDHVXG_workspaceDropAfter",
985
- "searchSlot": "qDHVXG_searchSlot",
986
- "rail": "qDHVXG_rail",
987
- "searchSlotExpanded": "qDHVXG_searchSlotExpanded",
988
- "searchButton": "qDHVXG_searchButton",
989
- "workspaceDropBefore": "qDHVXG_workspaceDropBefore",
990
- "deleteAction": "qDHVXG_deleteAction",
991
- "root": "qDHVXG_root",
992
- "clearButton": "qDHVXG_clearButton",
993
- "listTopDropIndicator": "qDHVXG_listTopDropIndicator",
994
- "listTopDropActive": "qDHVXG_listTopDropActive",
984
+ "flatList": "qDHVXG_flatList",
985
+ "groupSection": "qDHVXG_groupSection",
995
986
  "headerActions": "qDHVXG_headerActions",
996
- "searchStatus": "qDHVXG_searchStatus",
997
- "sectionLabelHidden": "qDHVXG_sectionLabelHidden",
998
- "searchInput": "qDHVXG_searchInput",
987
+ "headerActionsHidden": "qDHVXG_headerActionsHidden",
988
+ "iconButton": "qDHVXG_iconButton",
989
+ "list": "qDHVXG_list",
999
990
  "listArea": "qDHVXG_listArea",
991
+ "listTopDropActive": "qDHVXG_listTopDropActive",
992
+ "listTopDropIndicator": "qDHVXG_listTopDropIndicator",
993
+ "rail": "qDHVXG_rail",
994
+ "renameError": "qDHVXG_renameError",
995
+ "renameInput": "qDHVXG_renameInput",
996
+ "root": "qDHVXG_root",
997
+ "search": "qDHVXG_search",
998
+ "searchButton": "qDHVXG_searchButton",
1000
999
  "searchExpanded": "qDHVXG_searchExpanded",
1001
- "list": "qDHVXG_list",
1002
- "iconButton": "qDHVXG_iconButton",
1000
+ "searchInput": "qDHVXG_searchInput",
1001
+ "searchSlot": "qDHVXG_searchSlot",
1002
+ "searchSlotExpanded": "qDHVXG_searchSlotExpanded",
1003
+ "searchStatus": "qDHVXG_searchStatus",
1004
+ "searchTree": "qDHVXG_searchTree",
1005
+ "searchWarning": "qDHVXG_searchWarning",
1006
+ "sectionHeader": "qDHVXG_sectionHeader",
1003
1007
  "sectionLabel": "qDHVXG_sectionLabel",
1004
- "groupSection": "qDHVXG_groupSection",
1005
- "renameInput": "qDHVXG_renameInput",
1008
+ "sectionLabelHidden": "qDHVXG_sectionLabelHidden",
1006
1009
  "sessionOverflowButton": "qDHVXG_sessionOverflowButton",
1007
1010
  "treeBody": "qDHVXG_treeBody",
1008
1011
  "wide": "qDHVXG_wide",
1009
- "flatList": "qDHVXG_flatList",
1010
- "searchTree": "qDHVXG_searchTree",
1011
- "sectionHeader": "qDHVXG_sectionHeader",
1012
- "headerActionsHidden": "qDHVXG_headerActionsHidden",
1013
- "renameError": "qDHVXG_renameError"
1012
+ "wide-in": "qDHVXG_wide-in",
1013
+ "workspaceDropAfter": "qDHVXG_workspaceDropAfter",
1014
+ "workspaceDropBefore": "qDHVXG_workspaceDropBefore"
1014
1015
  };
1015
1016
  //#endregion
1016
1017
  //#region lib/types/client/WorkspaceBrowser.js
@@ -1196,7 +1197,7 @@ window.__ModuleLoader__.load({
1196
1197
  return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
1197
1198
  }
1198
1199
  /** The scrolling session tree; unmounting drops the sessions subscription and expand-all state. */
1199
- function SessionTree({ useSessions, startSession, open, forkSession, workspaces, archivedSessionIds, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore, insertSessionBefore, orderBy, groupExpansion, setGroupExpanded, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, t }) {
1200
+ function SessionTree({ useSessions, startSession, open, forkSession, workspaces, archivedSessionIds, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore, insertSessionBefore, orderBy, groupExpansion, setGroupExpanded, sessionOrderByAccount, sessionUpdatedAtByAccount, syncSessionOrderAccount, setSessionOrder, home, t }) {
1200
1201
  const list = useSessions((s) => s);
1201
1202
  const current = list.current;
1202
1203
  const [expandedSessionGroups, setExpandedSessionGroups] = (0, react.useState)([]);
@@ -1376,6 +1377,7 @@ window.__ModuleLoader__.load({
1376
1377
  children: [
1377
1378
  (0, react_jsx_runtime.jsx)(ProjectRowItem, {
1378
1379
  group,
1380
+ home,
1379
1381
  t,
1380
1382
  onToggle: () => {
1381
1383
  if (group.expanded) setExpandedSessionGroups((keys) => keys.filter((key) => key !== group.key));
@@ -1644,7 +1646,8 @@ window.__ModuleLoader__.load({
1644
1646
  * @param props - composed slot props (shell owner share + store + injected actions).
1645
1647
  * @returns the region element tree.
1646
1648
  */
1647
- function WorkspaceBrowser({ wide, expandSidebar, useSessions, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, renderSlot, t }) {
1649
+ function WorkspaceBrowser({ wide, expandSidebar, useSessions, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostDescription, renderSlot, t }) {
1650
+ const home = useHostDescription((description) => description?.home);
1648
1651
  const workspaces = useWorkspaces((state) => state.items);
1649
1652
  const workspacePhase = useWorkspaces((state) => state.phase);
1650
1653
  const archivedSessionIds = useWorkspaces((state) => state.archivedSessionIds);
@@ -1701,7 +1704,7 @@ window.__ModuleLoader__.load({
1701
1704
  searchOnExpand
1702
1705
  ]);
1703
1706
  (0, react.useEffect)(() => {
1704
- if (!wide || !searchExpanded) return;
1707
+ if (!wide || !searchExpanded || searchOnExpand) return;
1705
1708
  const onClick = (event) => {
1706
1709
  if (!(event.target instanceof Node) || searchRoot.current?.contains(event.target) === true) return;
1707
1710
  searchInput.current?.blur();
@@ -1715,7 +1718,8 @@ window.__ModuleLoader__.load({
1715
1718
  }, [
1716
1719
  normalizedQuery,
1717
1720
  wide,
1718
- searchExpanded
1721
+ searchExpanded,
1722
+ searchOnExpand
1719
1723
  ]);
1720
1724
  (0, react.useEffect)(() => {
1721
1725
  if (normalizedQuery === "") {
@@ -2022,6 +2026,7 @@ window.__ModuleLoader__.load({
2022
2026
  insertWorkspaceBefore,
2023
2027
  insertSessionBefore,
2024
2028
  orderBy,
2029
+ home,
2025
2030
  t,
2026
2031
  onRenameRequest: (workspaceId, currentTitle) => {
2027
2032
  setRenameTarget({
@@ -2326,7 +2331,8 @@ window.__ModuleLoader__.load({
2326
2331
  "slots",
2327
2332
  "sessions",
2328
2333
  "workspaces",
2329
- "locale"
2334
+ "locale",
2335
+ "connection"
2330
2336
  ];
2331
2337
  /**
2332
2338
  * Register the browser and picker once their slot declarations are on the
@@ -2335,6 +2341,7 @@ window.__ModuleLoader__.load({
2335
2341
  * @param ctx - client root context.
2336
2342
  */
2337
2343
  function apply(ctx) {
2344
+ const hostDescription = ctx.get("connection").hostDescription;
2338
2345
  ctx.effect(() => ctx.locale.register(NS, {
2339
2346
  zh,
2340
2347
  en
@@ -2389,7 +2396,10 @@ window.__ModuleLoader__.load({
2389
2396
  await ctx.workspaces.insertSessionBefore(workspaceId, sessionId, beforeSessionId);
2390
2397
  },
2391
2398
  createWorkspace: (input) => ctx.workspaces.create(input),
2392
- hooks: { directoryFlow: browserFlowSource }
2399
+ hooks: {
2400
+ directoryFlow: browserFlowSource,
2401
+ hostDescription
2402
+ }
2393
2403
  });
2394
2404
  const pickerInjected = () => ({
2395
2405
  createWorkspace: (input) => ctx.workspaces.create(input),
@@ -4,5 +4,5 @@ import type { WorkspaceBrowserProps } from './contract/slots.ts';
4
4
  * @param props - composed slot props (shell owner share + store + injected actions).
5
5
  * @returns the region element tree.
6
6
  */
7
- export declare function WorkspaceBrowser({ wide, expandSidebar, useSessions, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, renderSlot, t, }: WorkspaceBrowserProps): import("react").JSX.Element;
7
+ export declare function WorkspaceBrowser({ wide, expandSidebar, useSessions, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostDescription, renderSlot, t, }: WorkspaceBrowserProps): import("react").JSX.Element;
8
8
  //# sourceMappingURL=WorkspaceBrowser.d.ts.map
@@ -22,7 +22,8 @@
22
22
  * and a hole has exactly one declaring entry — they carry the same owner
23
23
  * contract and the same occupant.
24
24
  */
25
- import type { HostObservable, PropsLocale, PropsRenderSlots, PropsRuntime, PropsStore, SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots';
25
+ import type { HostDescriptionSource } from '@deepseek-ai/dsh-client-connection/client';
26
+ import type { HostObservable, PropsHooks, PropsLocale, PropsRenderSlots, PropsRuntime, PropsStore } from '@deepseek-ai/dsh-client-ui-slots';
26
27
  import type { SessionId, SessionSearchResultItem, WorkspaceId, WorkspaceView } from '@deepseek-ai/dsh-client-runtime/client';
27
28
  import type { createWorkspaceViewStore } from '../stores.ts';
28
29
  /**
@@ -74,16 +75,17 @@ export type DirectoryPickingInjected = {
74
75
  };
75
76
  };
76
77
  /** Component-side view of the picking share: the bound occupancy selector hook. */
77
- export type DirectoryPickingHooks = {
78
- /** Selector hook over this surface's directory-flow occupancy. */
79
- useDirectoryFlow: SnapshotSelectorHook<boolean>;
80
- };
78
+ export type DirectoryPickingHooks = PropsHooks<DirectoryPickingInjected['hooks']>;
81
79
  /**
82
80
  * Browser-private injected share (arrives via the register inject factory).
83
81
  * Data reads use the global framework hooks; these are the Host actions the
84
82
  * browsing region drives.
85
83
  */
86
- export type WorkspaceBrowserInjected = DirectoryPickingInjected & {
84
+ export type WorkspaceBrowserInjected = {
85
+ hooks: DirectoryPickingInjected['hooks'] & {
86
+ /** Current generation's Host description, bound by the slot renderer. */
87
+ hostDescription: HostDescriptionSource;
88
+ };
87
89
  /**
88
90
  * Start a New Session in a Workspace: reuse-or-create its blank session and
89
91
  * open it; without an explicit workspace, inherit the current Session
@@ -133,7 +135,7 @@ export type WorkspaceBrowserInjected = DirectoryPickingInjected & {
133
135
  }) => Promise<WorkspaceView>;
134
136
  };
135
137
  /** Full browser props: shell owner share + viewing store + injected actions + the locale seat. */
136
- export type WorkspaceBrowserProps = PropsRuntime<'sidebar.workspaces'> & PropsRenderSlots<'sidebar.workspaces.directoryFlow'> & PropsStore<ReturnType<typeof createWorkspaceViewStore>> & Omit<WorkspaceBrowserInjected, 'hooks'> & DirectoryPickingHooks & PropsLocale<'workspace'>;
138
+ export type WorkspaceBrowserProps = PropsRuntime<'sidebar.workspaces'> & PropsRenderSlots<'sidebar.workspaces.directoryFlow'> & PropsStore<ReturnType<typeof createWorkspaceViewStore>> & Omit<WorkspaceBrowserInjected, 'hooks'> & PropsHooks<WorkspaceBrowserInjected['hooks']> & PropsLocale<'workspace'>;
137
139
  /**
138
140
  * Picker-private injected share. Pick semantics remain in the owner's onPick
139
141
  * callback; this callback creates only the real Host Workspace. A type alias
@@ -32,10 +32,11 @@ interface WorkspaceRowDragProps {
32
32
  * @param props.onToggle - expand/collapse the group.
33
33
  * @param props.onCreate - start a frontend Session inside this Workspace.
34
34
  * @param props.drag - optional workspace-row drag wiring.
35
+ * @param props.home - host account home for POSIX hover-path abbreviation.
35
36
  * @param props.t - the browser root's locale seat.
36
37
  * @returns the row element.
37
38
  */
38
- export declare function ProjectRowItem({ group, onToggle, onCreate, actions, drag, t }: {
39
+ export declare function ProjectRowItem({ group, onToggle, onCreate, actions, drag, home, t }: {
39
40
  group: GroupNode;
40
41
  onToggle: () => void;
41
42
  onCreate: () => void;
@@ -46,6 +47,8 @@ export declare function ProjectRowItem({ group, onToggle, onCreate, actions, dra
46
47
  } | undefined;
47
48
  /** Present only for real Workspace rows in the grouped view. */
48
49
  drag?: WorkspaceRowDragProps | undefined;
50
+ /** Host account home; POSIX home-rooted hover paths display as `~`. */
51
+ home?: string | undefined;
49
52
  t: RowTranslate;
50
53
  }): import("react").JSX.Element;
51
54
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-workspace",
3
3
  "description": "Workspace picker plugin: one WorkspacePicker registered into the sidebar and empty-state workspace slots",
4
- "version": "0.1.0-rc.6",
4
+ "version": "0.1.0-rc.8",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -32,6 +32,7 @@
32
32
  "dsh": {
33
33
  "client": {
34
34
  "inject": [
35
+ "@deepseek-ai/dsh-client-connection",
35
36
  "@deepseek-ai/dsh-client-locale",
36
37
  "@deepseek-ai/dsh-client-runtime",
37
38
  "@deepseek-ai/dsh-client-ui-conversation",
@@ -45,25 +46,26 @@
45
46
  "clsx": "^2.0.0"
46
47
  },
47
48
  "peerDependencies": {
48
- "react": "^18.2.0",
49
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
50
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
51
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
52
- "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
49
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.8",
50
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.8",
51
+ "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8",
53
52
  "@deepseek-ai/cordis": "^4.0.1",
54
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6"
53
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.8",
54
+ "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.0-rc.8",
55
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.8"
55
56
  },
56
57
  "devDependencies": {
57
58
  "@types/react": "~18.3.1",
58
59
  "react": "^18.2.0",
59
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
60
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
61
- "@deepseek-ai/dsh-client-test-runtime": "^0.1.0-rc.6",
62
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6",
63
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
64
- "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.0-rc.6",
65
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
66
- "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
60
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.8",
61
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.8",
62
+ "@deepseek-ai/dsh-client-test-runtime": "^0.1.0-rc.8",
63
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.8",
64
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.8",
65
+ "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.0-rc.8",
66
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.8",
67
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.8",
68
+ "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8",
67
69
  "@deepseek-ai/cordis": "^4.0.1"
68
70
  },
69
71
  "files": [