@deepseek-ai/dsh-client-ui-sidebar-files 0.1.5-alpha.1 → 0.1.5-alpha.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
@@ -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-sidebar-files/README.md
5
- README.md: 6e9e305b15058797156f832a56a29c095b0641f3
6
- README.zh.md: 95a15444182a988fdf512735be4e0fbfcaad6bdd
5
+ README.md: cadadc03f96f39f9b48813f25179f4506b6de791
6
+ README.zh.md: e25d86b27556bac746c53abf016b37ce1ded81df
package/README.md CHANGED
@@ -25,14 +25,15 @@ The right Sidebar's navigator tab type: the session's workspace root as a tree,
25
25
  ## What it registers
26
26
 
27
27
  - **The type** — `ctx.sidebarRightTabs.register(...)` with kind `files`, id `@deepseek-ai/dsh-client-ui-sidebar-files`, band `builtin`, no patterns, and one guide entry (order 10, titled from the `sidebarFiles` namespace) that opens the type.
28
- - **The body** — the keyed `sidebar.right.pane.tab` seat under that id: the tree, with its one control, reload, at the right of its header row.
28
+ - **The body** — the keyed `sidebar.right.pane.tab` seat under that id: a header row under the strip, then the tree. The header row is the document preview's (`ui-sidebar-documentpreview`): the root path, its directories greyed and its last segment in full ink, never ellipsized (a path wider than the row keeps its end and fades its start), with the one control, reload, at its right. The row is copied rather than shared because a plugin bundle shares runtime code only through the platform modules; once the artifact and slot surfaces settle, one copy in `ui-primitives` could serve every pane header.
29
+ - **The chip title** — the keyed `sidebar.right.pane.tab.title` seat under that id: a shared `FileTypeIcon` folder glyph at 16px before the type's label. The tree's own rows never draw this sheet.
29
30
 
30
- Six source files under `src/client/`: `definition.ts` (the type), `store.ts` (what it keeps), `face.ts` (how it lists, Remote binding included), `FilesBody.tsx` (what it draws, with its ordering and failure-line helpers), `locales.ts` (what it says), and `index.ts` (the wiring).
31
+ Seven source files under `src/client/`: `definition.ts` (the type), `store.ts` (what it keeps), `face.ts` (how it lists, Remote binding included), `FilesBody.tsx` (what it draws, with its ordering and failure-line helpers), `FilesTitle.tsx` (the chip title), `locales.ts` (what it says), and `index.ts` (the wiring).
31
32
 
32
33
  <a id="the-tree"></a>
33
34
  ## The tree
34
35
 
35
- The root is the session's working directory, read from `useSessions().byId[sessionId].cwd`, and labelled by `workspaceTitleOf` from `@deepseek-ai/dsh-util-workspace-path`. Every level is keyed by absolute path; a child's path is its parent's joined with the entry name by `/`. A level is listed when it is first expanded, through `remote.workspaceFiles.list(sessionId, absolutePath)` on the `@deepseek-ai/dsh-api-workspace-files` namespace; the adapter keeps the listing's entries and truncation flag and drops its workspace-relative path. Rows are ordered directories first, then by natural, case-insensitive name; dotfiles are shown like any other entry.
36
+ The root is the session's working directory, read from `useSessions().byId[sessionId].cwd`, and split for the header row by `pathPartsOf` from `@deepseek-ai/dsh-util-workspace-path`. Every level is keyed by absolute path; a child's path is its parent's joined with the entry name by `/`. A level is listed when it is first expanded, through `remote.workspaceFiles.list(sessionId, absolutePath)` on the `@deepseek-ai/dsh-api-workspace-files` namespace; the adapter keeps the listing's entries and truncation flag and drops its workspace-relative path. Rows are ordered directories first, then by natural, case-insensitive name; dotfiles are shown like any other entry.
36
37
 
37
38
  | Entry type | Row |
38
39
  |---|---|
package/README.zh.md CHANGED
@@ -25,14 +25,15 @@ kind: "package-reference"
25
25
  ## 注册了什么
26
26
 
27
27
  - **类型**:`ctx.sidebarRightTabs.register(...)`,kind 为 `files`,id 为 `@deepseek-ai/dsh-client-ui-sidebar-files`,档位 `builtin`,没有 patterns,另有一个打开该类型的引导页入口(order 10,标题取自 `sidebarFiles` 命名空间)。
28
- - **正文**:以该 id 为键的 `sidebar.right.pane.tab` 坑位:树本身,以及它唯一的控件、位于标题行右端的重新读取。
28
+ - **正文**:以该 id 为键的 `sidebar.right.pane.tab` 坑位:strip 下的一行标题行,然后是树。标题行与文档预览(`ui-sidebar-documentpreview`)的相同:根路径,目录部分灰色、最后一段正色,从不省略号截断(比行宽的路径保留末尾、淡出开头),右端是它唯一的控件、重新读取。这一行是复制而非共享,因为插件 bundle 只经平台模块共享运行时代码;待 artifact 与各 slot 的形态定下来后,可以在 `ui-primitives` 放一份供每个 pane 标题行使用。
29
+ - **标签页标题**:以该 id 为键的 `sidebar.right.pane.tab.title` 坑位:类型标签前的一枚 16px 共享 `FileTypeIcon` 文件夹图标。树本身的行不画这枚图标。
29
30
 
30
- `src/client/` 下六个源文件:`definition.ts`(类型是什么)、`store.ts`(它保存什么)、`face.ts`(它如何列目录,含 Remote 绑定)、`FilesBody.tsx`(它画什么,含排序与失败行两个辅助函数)、`locales.ts`(它说什么)、`index.ts`(接线)。
31
+ `src/client/` 下七个源文件:`definition.ts`(类型是什么)、`store.ts`(它保存什么)、`face.ts`(它如何列目录,含 Remote 绑定)、`FilesBody.tsx`(它画什么,含排序与失败行两个辅助函数)、`FilesTitle.tsx`(标签页标题)、`locales.ts`(它说什么)、`index.ts`(接线)。
31
32
 
32
33
  <a id="the-tree"></a>
33
34
  ## 树
34
35
 
35
- 根是会话的工作目录,读自 `useSessions().byId[sessionId].cwd`,标签由 `@deepseek-ai/dsh-util-workspace-path` 的 `workspaceTitleOf` 给出。每一层以绝对路径为键;子路径是父路径以 `/` 拼上条目名。一层在首次展开时经 `@deepseek-ai/dsh-api-workspace-files` 命名空间的 `remote.workspaceFiles.list(sessionId, absolutePath)` 列出;适配层保留列表的条目与截断标志,丢弃其工作区相对路径。行序为目录优先,其后按自然序、不分大小写的名称排列;dotfiles 与其他条目一样显示。
36
+ 根是会话的工作目录,读自 `useSessions().byId[sessionId].cwd`,标题行里的拆分由 `@deepseek-ai/dsh-util-workspace-path` 的 `pathPartsOf` 给出。每一层以绝对路径为键;子路径是父路径以 `/` 拼上条目名。一层在首次展开时经 `@deepseek-ai/dsh-api-workspace-files` 命名空间的 `remote.workspaceFiles.list(sessionId, absolutePath)` 列出;适配层保留列表的条目与截断标志,丢弃其工作区相对路径。行序为目录优先,其后按自然序、不分大小写的名称排列;dotfiles 与其他条目一样显示。
36
37
 
37
38
  | 条目类型 | 行 |
38
39
  |---|---|
package/lib/client.js CHANGED
@@ -27,7 +27,6 @@ window.__ModuleLoader__.load({
27
27
  guide: [{
28
28
  order: 10,
29
29
  title: () => t("guide.title"),
30
- description: () => t("guide.description"),
31
30
  icon: _deepseek_ai_dsh_client_ui_primitives.IconFolderClose16
32
31
  }]
33
32
  };
@@ -135,26 +134,14 @@ window.__ModuleLoader__.load({
135
134
  return path.split("/").map(encodeSegment).join("/");
136
135
  }
137
136
  /**
138
- * Build the address of a file inside one Session's workspace.
139
- * @param sessionId - the Session whose workspace root the path is relative to.
140
- * @param path - workspace-relative path; backslashes are normalized to `/`, and a leading `./` or `/` is dropped.
137
+ * Build the address of a file read through one Session.
138
+ * @param sessionId - the Session whose Host workspace resolves the path.
139
+ * @param path - absolute or workspace-relative path; backslashes are normalized to `/`, and leading `./` prefixes are dropped.
141
140
  * @returns the `dsh-resource://file/session/<sessionId>/<path>` address.
142
141
  */
143
142
  function sessionFileAddress(sessionId, path) {
144
- const relative = path.replace(/\\/g, "/").replace(/^(?:\.\/)+/, "").replace(/^\/+/, "");
145
- return `${FILE_ADDRESS_PREFIX}session/${encodeSegment(sessionId)}/${encodePath(relative)}`;
146
- }
147
- /**
148
- * Build the address of a file by its absolute path.
149
- * @param path - absolute path; backslashes are normalized to `/` and the leading `/` is dropped,
150
- * except that a UNC path (`\\server\share`) keeps one empty first segment.
151
- * @returns the `dsh-resource://file/absolute/<path>` address.
152
- */
153
- function absoluteFileAddress(path) {
154
- const normalized = path.replace(/\\/g, "/");
155
- const unc = normalized.startsWith("//");
156
- const absolute = normalized.replace(/^\/+/, "");
157
- return `${FILE_ADDRESS_PREFIX}absolute/${unc ? "/" : ""}${encodePath(absolute)}`;
143
+ const normalized = path.replace(/\\/g, "/").replace(/^(?:\.\/)+/, "");
144
+ return `${FILE_ADDRESS_PREFIX}session/${encodeSegment(sessionId)}/${encodePath(normalized)}`;
158
145
  }
159
146
  //#endregion
160
147
  //#region ../../util/workspace-path/src/index.ts
@@ -175,21 +162,31 @@ window.__ModuleLoader__.load({
175
162
  return path.startsWith("/") || isWindowsStylePath(path);
176
163
  }
177
164
  /**
178
- * Read the final non-empty segment of a Workspace path for display.
179
- * Workspace-label surfaces use this helper instead of deriving another basename.
180
- * @param path - Workspace directory path using POSIX or Windows separators.
181
- * @returns the final segment, or an empty string for a separator-only path.
165
+ * Split a path for display: the directories through their last separator, and
166
+ * the final segment after it. Both `/` and `\` separate, so a Windows path
167
+ * splits where its own segments end; trailing separators are dropped first, so
168
+ * a directory path names its own last segment. A path with no separator, or a
169
+ * separator-only path, is all name.
170
+ * @param path - file or directory path using POSIX or Windows separators.
171
+ * @returns the directory prefix (possibly empty) and the final segment.
182
172
  */
183
- function workspaceTitleOf(path) {
173
+ function pathPartsOf(path) {
184
174
  const trimmed = path.replace(/[/\\]+$/, "");
185
- const separator = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
186
- return trimmed.slice(separator + 1);
175
+ if (trimmed === "") return {
176
+ directory: "",
177
+ name: path
178
+ };
179
+ const cut = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\")) + 1;
180
+ return {
181
+ directory: trimmed.slice(0, cut),
182
+ name: trimmed.slice(cut)
183
+ };
187
184
  }
188
185
  /**
189
186
  * The address for a path as a caller holds it: a relative path, or an absolute
190
187
  * path inside the Session's workspace, becomes a `session`-scoped address; an
191
- * absolute path outside it, or one whose workspace root is unknown, becomes an
192
- * `absolute`-scoped address.
188
+ * absolute path outside it, or one whose workspace root is unknown, keeps its
189
+ * absolute path in that Session's address.
193
190
  * @param sessionId - the Session the path is read in.
194
191
  * @param cwd - that Session's workspace root, when known.
195
192
  * @param path - absolute or workspace-relative path, in either separator spelling.
@@ -201,11 +198,11 @@ window.__ModuleLoader__.load({
201
198
  const root = cwd === void 0 ? "" : cwd.replace(/\\/g, "/").replace(/\/+$/, "");
202
199
  if (root !== "" && normalized === root) return sessionFileAddress(sessionId, "");
203
200
  if (root !== "" && normalized.startsWith(`${root}/`)) return sessionFileAddress(sessionId, normalized.slice(root.length + 1));
204
- return absoluteFileAddress(normalized);
201
+ return sessionFileAddress(sessionId, normalized);
205
202
  }
206
203
  //#endregion
207
204
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-sidebar-files/src/client/FilesBody.module.css.mjs
208
- const css = ".k-1LKG_root{min-height:0;color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size-secondary,13px);flex-direction:column;flex:auto;padding:4px 0 8px;line-height:1.5;display:flex;overflow:auto}.k-1LKG_header{color:var(--dsw-alias-label-secondary);flex:none;align-items:center;gap:6px;padding:4px 10px;font-weight:500;display:flex}.k-1LKG_level{margin:0;padding:0;list-style:none}.k-1LKG_level .k-1LKG_level{padding-left:14px}.k-1LKG_item{margin:0;padding:0}.k-1LKG_row{width:100%;min-width:0;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:6px;align-items:center;gap:6px;padding:3px 10px;display:flex}.k-1LKG_row:hover{background:var(--dsw-alias-interactive-bg-hover)}.k-1LKG_icon{color:var(--dsw-alias-label-secondary);flex:none}.k-1LKG_fileIcon{flex:none;width:14px;height:16px}.k-1LKG_name{white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}.k-1LKG_other{color:var(--dsw-alias-label-tertiary);cursor:default}.k-1LKG_other:hover{background:0 0}.k-1LKG_note{color:var(--dsw-alias-label-tertiary);margin:0;padding:3px 10px;font-size:12px}.k-1LKG_status{flex-direction:column;padding:12px 10px;display:flex}.k-1LKG_statusLine{color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size-secondary,13px);margin:0;line-height:1.6}.k-1LKG_tool{width:24px;height:24px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;flex:none;justify-content:center;align-items:center;margin-left:auto;padding:0;display:inline-flex}.k-1LKG_tool:hover{background:var(--dsw-alias-interactive-bg-hover)}";
205
+ const css = ".k-1LKG_root{height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size-secondary,13px);flex-direction:column;flex:auto;line-height:1.5;display:flex}.k-1LKG_header{box-sizing:border-box;border-bottom:.5px solid var(--dsw-alias-border-l3);flex:none;align-items:center;gap:4px;height:38px;padding:0 6px 0 16px;display:flex}.k-1LKG_path{white-space:nowrap;flex:auto;justify-content:flex-end;min-width:0;margin-right:12px;font-size:12px;display:flex;overflow:hidden}.k-1LKG_path[data-files-path-clipped]{mask-image:linear-gradient(90deg,#0000,#000 28px)}.k-1LKG_pathText{flex:none;margin-right:auto}.k-1LKG_pathDirectory{color:var(--dsw-alias-label-tertiary)}.k-1LKG_pathName{color:var(--dsw-alias-label-primary)}.k-1LKG_body{scrollbar-gutter:stable;flex:auto;min-height:0;margin-right:2px;padding:8px 0 8px 8px;overflow:auto}.k-1LKG_body::-webkit-scrollbar-track{margin:2px}.k-1LKG_level{margin:0;padding:0;list-style:none}.k-1LKG_level .k-1LKG_level{padding-left:18px}.k-1LKG_item{margin:0;padding:0}.k-1LKG_row{width:100%;min-width:0;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:10px;align-items:center;gap:6px;padding:5px 10px;display:flex}.k-1LKG_row:hover{background:var(--dsw-alias-interactive-bg-hover)}.k-1LKG_icon{color:var(--dsw-alias-label-tertiary);flex:none}.k-1LKG_fileIcon{flex:none}.k-1LKG_name{white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}.k-1LKG_other{color:var(--dsw-alias-label-tertiary);cursor:default}.k-1LKG_other:hover{background:0 0}.k-1LKG_note{color:var(--dsw-alias-label-tertiary);margin:0;padding:3px 10px;font-size:12px}.k-1LKG_status{flex-direction:column;padding:12px 10px;display:flex}.k-1LKG_statusLine{color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size-secondary,13px);margin:0;line-height:1.6}.k-1LKG_tool{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:28px;flex:none;justify-content:center;align-items:center;padding:6px;line-height:1;display:inline-flex}.k-1LKG_tool svg{width:15px;height:15px}.k-1LKG_tool:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.k-1LKG_titleIcon{flex:none}";
209
206
  const tagId = "@deepseek-ai/dsh-client-ui-sidebar-files/FilesBody.module.css";
210
207
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
211
208
  const tag = document.createElement("style");
@@ -215,6 +212,7 @@ window.__ModuleLoader__.load({
215
212
  document.head.appendChild(tag);
216
213
  }
217
214
  var FilesBody_module_css_default = {
215
+ "body": "k-1LKG_body",
218
216
  "fileIcon": "k-1LKG_fileIcon",
219
217
  "header": "k-1LKG_header",
220
218
  "icon": "k-1LKG_icon",
@@ -223,10 +221,15 @@ window.__ModuleLoader__.load({
223
221
  "name": "k-1LKG_name",
224
222
  "note": "k-1LKG_note",
225
223
  "other": "k-1LKG_other",
224
+ "path": "k-1LKG_path",
225
+ "pathDirectory": "k-1LKG_pathDirectory",
226
+ "pathName": "k-1LKG_pathName",
227
+ "pathText": "k-1LKG_pathText",
226
228
  "root": "k-1LKG_root",
227
229
  "row": "k-1LKG_row",
228
230
  "status": "k-1LKG_status",
229
231
  "statusLine": "k-1LKG_statusLine",
232
+ "titleIcon": "k-1LKG_titleIcon",
230
233
  "tool": "k-1LKG_tool"
231
234
  };
232
235
  //#endregion
@@ -238,9 +241,10 @@ window.__ModuleLoader__.load({
238
241
  * for goes through its injected face. The component itself only decides what to
239
242
  * draw for each absolute path and what a click means: a directory toggles, a
240
243
  * file opens through the owner's `tabActions` for a `file:` viewer to claim, and
241
- * anything else is shown but refuses to open. The header row carries the one
242
- * control: reload, which drops every listed level and asks again for the
243
- * expanded ones.
244
+ * anything else is shown but refuses to open. The header row is the text
245
+ * preview's: the root's path, directories greyed and the last segment in full
246
+ * ink, then the one control at its end, reload, which drops every listed level
247
+ * and asks again for the expanded ones.
244
248
  */
245
249
  /** Natural, case-insensitive name order, so `file2` precedes `file10`. */
246
250
  const byName = new Intl.Collator(void 0, {
@@ -274,6 +278,35 @@ window.__ModuleLoader__.load({
274
278
  default: return t("error.unavailable", { message: failure.message });
275
279
  }
276
280
  }
281
+ /**
282
+ * Keep the path row's `data-files-path-clipped` current: set while the path's
283
+ * text is wider than its box, so the stylesheet fades the clipped start. Read
284
+ * after each commit that can change the path or mount the header, and whenever
285
+ * either box resizes; written to the DOM directly because it changes only how
286
+ * the stylesheet fades what is already rendered.
287
+ */
288
+ function usePathClipped(box, text, path) {
289
+ (0, react.useLayoutEffect)(() => {
290
+ const outer = box.current;
291
+ const inner = text.current;
292
+ if (outer === null || inner === null) return void 0;
293
+ const apply = () => {
294
+ if (inner.offsetWidth > outer.clientWidth) outer.dataset.filesPathClipped = "";
295
+ else delete outer.dataset.filesPathClipped;
296
+ };
297
+ apply();
298
+ const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(apply);
299
+ observer?.observe(outer);
300
+ observer?.observe(inner);
301
+ return () => {
302
+ observer?.disconnect();
303
+ };
304
+ }, [
305
+ box,
306
+ text,
307
+ path
308
+ ]);
309
+ }
277
310
  /** One entry's row, and its children when it is an expanded directory. */
278
311
  function Entry({ parent, entry, tree }) {
279
312
  const path = childPath(parent, entry.name);
@@ -313,7 +346,11 @@ window.__ModuleLoader__.load({
313
346
  onClick: () => {
314
347
  tree.onOpen(path);
315
348
  },
316
- children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DocumentFileIcon, { className: FilesBody_module_css_default.fileIcon }), (0, react_jsx_runtime.jsx)("span", {
349
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.FileTypeIcon, {
350
+ kind: (0, _deepseek_ai_dsh_client_ui_primitives.classifyFileType)(entry.name),
351
+ size: 16,
352
+ className: FilesBody_module_css_default.fileIcon
353
+ }), (0, react_jsx_runtime.jsx)("span", {
317
354
  className: FilesBody_module_css_default.name,
318
355
  children: entry.name
319
356
  })]
@@ -374,6 +411,9 @@ window.__ModuleLoader__.load({
374
411
  const { signal, actions: tabActions } = tab;
375
412
  const cwd = useSessions((sessions) => sessions.byId[sessionId]?.cwd);
376
413
  const state = useStore((store) => store.byTab[tab.id]);
414
+ const pathRef = (0, react.useRef)(null);
415
+ const pathTextRef = (0, react.useRef)(null);
416
+ usePathClipped(pathRef, pathTextRef, state?.root);
377
417
  (0, react.useEffect)(() => {
378
418
  if (state !== void 0 || cwd === void 0 || signal.aborted) return;
379
419
  start(tab.id, cwd, signal);
@@ -407,45 +447,71 @@ window.__ModuleLoader__.load({
407
447
  actions.reset(tab.id);
408
448
  for (const path of state.expanded) load(tab.id, path, signal);
409
449
  };
410
- const title = workspaceTitleOf(state.root) || state.root;
450
+ const { directory, name } = pathPartsOf(state.root);
411
451
  return (0, react_jsx_runtime.jsxs)("div", {
412
452
  className: FilesBody_module_css_default.root,
413
453
  "data-files-state": "tree",
414
454
  "data-files-root": state.root,
415
455
  children: [(0, react_jsx_runtime.jsxs)("div", {
416
456
  className: FilesBody_module_css_default.header,
417
- children: [
418
- (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, { className: FilesBody_module_css_default.icon }),
419
- (0, react_jsx_runtime.jsx)("span", {
420
- className: FilesBody_module_css_default.name,
421
- children: title
422
- }),
423
- (0, react_jsx_runtime.jsx)("button", {
424
- type: "button",
425
- className: FilesBody_module_css_default.tool,
426
- "aria-label": t("reload"),
427
- title: t("reload"),
428
- "data-files-reload": true,
429
- onClick: reload,
430
- children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, {})
457
+ children: [(0, react_jsx_runtime.jsx)("div", {
458
+ ref: pathRef,
459
+ className: FilesBody_module_css_default.path,
460
+ title: state.root,
461
+ "data-files-path": true,
462
+ children: (0, react_jsx_runtime.jsxs)("span", {
463
+ ref: pathTextRef,
464
+ className: FilesBody_module_css_default.pathText,
465
+ children: [directory !== "" && (0, react_jsx_runtime.jsx)("span", {
466
+ className: FilesBody_module_css_default.pathDirectory,
467
+ children: directory
468
+ }), (0, react_jsx_runtime.jsx)("span", {
469
+ className: FilesBody_module_css_default.pathName,
470
+ children: name
471
+ })]
472
+ })
473
+ }), (0, react_jsx_runtime.jsx)("button", {
474
+ type: "button",
475
+ className: FilesBody_module_css_default.tool,
476
+ "aria-label": t("reload"),
477
+ title: t("reload"),
478
+ "data-files-reload": true,
479
+ onClick: reload,
480
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, {})
481
+ })]
482
+ }), (0, react_jsx_runtime.jsx)("div", {
483
+ className: FilesBody_module_css_default.body,
484
+ children: (0, react_jsx_runtime.jsx)("ul", {
485
+ className: FilesBody_module_css_default.level,
486
+ children: (0, react_jsx_runtime.jsx)(Level, {
487
+ path: state.root,
488
+ tree
431
489
  })
432
- ]
433
- }), (0, react_jsx_runtime.jsx)("ul", {
434
- className: FilesBody_module_css_default.level,
435
- children: (0, react_jsx_runtime.jsx)(Level, {
436
- path: state.root,
437
- tree
438
490
  })
439
491
  })]
440
492
  });
441
493
  }
442
494
  //#endregion
495
+ //#region lib/types/client/FilesTitle.js
496
+ /**
497
+ * The title as the chip and a floating panel's header show it.
498
+ * @param props - the tab information hook.
499
+ * @returns the folder sheet followed by the tab's title text.
500
+ */
501
+ function FilesTitle({ useTabInfo }) {
502
+ const { tab } = useTabInfo();
503
+ return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.FileTypeIcon, {
504
+ kind: "folder",
505
+ size: 16,
506
+ className: FilesBody_module_css_default.titleIcon
507
+ }), tab.title] });
508
+ }
509
+ //#endregion
443
510
  //#region lib/types/client/locales.js
444
511
  /** Simplified Chinese dictionary and key-set source of truth. */
445
512
  const zh = {
446
513
  "type.label": "文件",
447
- "guide.title": "文件",
448
- "guide.description": "浏览这个会话工作区里的文件,点开就能查看。",
514
+ "guide.title": "工作区文件",
449
515
  loading: "正在读取…",
450
516
  empty: "空目录",
451
517
  truncated: "条目太多,只显示了一部分。",
@@ -460,11 +526,10 @@ window.__ModuleLoader__.load({
460
526
  /** English dictionary, checked against the Chinese key set. */
461
527
  const en = {
462
528
  "type.label": "Files",
463
- "guide.title": "Files",
464
- "guide.description": "Browse the files in this session's workspace and open any of them.",
529
+ "guide.title": "Workspace files",
465
530
  loading: "Reading…",
466
531
  empty: "Empty directory",
467
- truncated: "Too many entries; showing only some of them.",
532
+ truncated: "Too many entries, showing only some of them.",
468
533
  noWorkspace: "This session has no workspace directory.",
469
534
  reload: "Reload",
470
535
  "entry.other": "Not a file or a directory, so it cannot be opened.",
@@ -610,7 +675,7 @@ window.__ModuleLoader__.load({
610
675
  "remote.workspaceFiles"
611
676
  ];
612
677
  /**
613
- * Client plugin body: register the type, its dictionaries, then its body.
678
+ * Client plugin body: register the type, its dictionaries, its body, and its chip title.
614
679
  * @param ctx - client root context carrying the registry, the slots, and the Remote face.
615
680
  */
616
681
  function apply(ctx) {
@@ -629,6 +694,10 @@ window.__ModuleLoader__.load({
629
694
  store,
630
695
  inject
631
696
  }, FilesBody)), "ui-sidebar-files: files tab body");
697
+ ctx.effect(() => ctx.slots.inject("sidebar.right.pane.tab.title", () => ctx.slots.register({
698
+ name: "sidebar.right.pane.tab.title",
699
+ key: FILES_ID
700
+ }, FilesTitle)), "ui-sidebar-files: files tab title");
632
701
  }
633
702
  //#endregion
634
703
  exports.apply = apply;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The files type's chip title: the folder sheet before the type's label.
3
+ * Registered under `sidebar.right.pane.tab.title`; without it the chip would
4
+ * show the bare label. The tree in the body draws its own row glyphs and never
5
+ * this one.
6
+ */
7
+ import type { ReactNode } from 'react';
8
+ import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
9
+ /**
10
+ * The title as the chip and a floating panel's header show it.
11
+ * @param props - the tab information hook.
12
+ * @returns the folder sheet followed by the tab's title text.
13
+ */
14
+ export declare function FilesTitle({ useTabInfo }: PropsRuntime<'sidebar.right.pane.tab.title'>): ReactNode;
15
+ //# sourceMappingURL=FilesTitle.d.ts.map
@@ -2,12 +2,13 @@
2
2
  * Browser half: register `files` as a right-Sidebar tab type.
3
3
  *
4
4
  * The public two-stage path, unmodified: the type into `ctx.sidebarRightTabs`,
5
- * the body into the keyed `sidebar.right.pane.tab` seat under the type's `id`.
5
+ * the body into the keyed `sidebar.right.pane.tab` seat and the chip title into
6
+ * the keyed `sidebar.right.pane.tab.title` seat, both under the type's `id`.
6
7
  *
7
8
  * The file split is this package's layering: what the type IS
8
9
  * (`definition.ts`), what it keeps (`store.ts`), how it lists (`face.ts`), what
9
- * it draws (`FilesBody.tsx`), what it says (`locales.ts`), and this module,
10
- * which only wires them together.
10
+ * it draws (`FilesBody.tsx`, `FilesTitle.tsx`), what it says (`locales.ts`),
11
+ * and this module, which only wires them together.
11
12
  */
12
13
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
13
14
  export type { SidebarFilesKey } from './locales.ts';
@@ -20,7 +21,7 @@ export type { FilesBodyProps } from './FilesBody.tsx';
20
21
  */
21
22
  export declare const inject: string[];
22
23
  /**
23
- * Client plugin body: register the type, its dictionaries, then its body.
24
+ * Client plugin body: register the type, its dictionaries, its body, and its chip title.
24
25
  * @param ctx - client root context carrying the registry, the slots, and the Remote face.
25
26
  */
26
27
  export declare function apply(ctx: ClientContext): void;
@@ -8,7 +8,6 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
8
8
  export declare const zh: {
9
9
  'type.label': string;
10
10
  'guide.title': string;
11
- 'guide.description': string;
12
11
  loading: string;
13
12
  empty: string;
14
13
  truncated: string;
@@ -26,7 +25,6 @@ export type SidebarFilesKey = keyof typeof zh;
26
25
  export declare const en: {
27
26
  'type.label': string;
28
27
  'guide.title': string;
29
- 'guide.description': string;
30
28
  loading: string;
31
29
  empty: string;
32
30
  truncated: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-sidebar-files",
3
3
  "description": "Workspace file tree tab type for the right Sidebar: lazy directory listing over the workspaceFiles Remote namespace, opening files into the Sidebar",
4
- "version": "0.1.5-alpha.1",
4
+ "version": "0.1.5-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -37,11 +37,6 @@
37
37
  }
38
38
  },
39
39
  "license": "MIT",
40
- "dependencies": {
41
- "clsx": "^2.0.0",
42
- "react": "^18.2.0",
43
- "react-dom": "^18.2.0"
44
- },
45
40
  "peerDependencies": {
46
41
  "@deepseek-ai/cordis": "^4.0.2"
47
42
  },
@@ -49,20 +44,23 @@
49
44
  "@testing-library/react": "^16.1.0",
50
45
  "@types/react": "~18.3.1",
51
46
  "@types/react-dom": "~18.3.0",
47
+ "react": "^18.2.0",
48
+ "clsx": "^2.0.0",
49
+ "react-dom": "^18.2.0",
50
+ "@deepseek-ai/dsh-api-remotes": "^0.1.5-alpha.2",
51
+ "@deepseek-ai/dsh-api-workspace-files": "^0.1.5-alpha.2",
52
+ "@deepseek-ai/dsh-client-locale": "^0.1.5-alpha.2",
53
+ "@deepseek-ai/dsh-client-store": "^0.1.5-alpha.2",
52
54
  "@deepseek-ai/cordis": "^4.0.2",
53
- "@deepseek-ai/dsh-api-workspace-files": "^0.1.5-alpha.1",
54
- "@deepseek-ai/dsh-api-remotes": "^0.1.5-alpha.1",
55
- "@deepseek-ai/dsh-client-locale": "^0.1.5-alpha.1",
56
- "@deepseek-ai/dsh-client-store": "^0.1.5-alpha.1",
57
- "@deepseek-ai/dsh-client-test-runtime": "^0.1.5-alpha.1",
58
- "@deepseek-ai/dsh-client-ui-dockkit": "^0.1.5-alpha.1",
59
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-alpha.1",
60
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-alpha.1",
61
- "@deepseek-ai/dsh-client-ui-session": "^0.1.5-alpha.1",
62
- "@deepseek-ai/dsh-client-ui-sidebar-right": "^0.1.5-alpha.1",
63
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-alpha.1",
64
- "@deepseek-ai/dsh-session": "^0.1.5-alpha.1",
65
- "@deepseek-ai/dsh-util-workspace-path": "^0.1.5-alpha.1"
55
+ "@deepseek-ai/dsh-client-test-runtime": "^0.1.5-alpha.2",
56
+ "@deepseek-ai/dsh-client-ui-dockkit": "^0.1.5-alpha.2",
57
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-alpha.2",
58
+ "@deepseek-ai/dsh-client-ui-session": "^0.1.5-alpha.2",
59
+ "@deepseek-ai/dsh-client-ui-sidebar-right": "^0.1.5-alpha.2",
60
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-alpha.2",
61
+ "@deepseek-ai/dsh-util-workspace-path": "^0.1.5-alpha.2",
62
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-alpha.2",
63
+ "@deepseek-ai/dsh-session": "^0.1.5-alpha.2"
66
64
  },
67
65
  "files": [
68
66
  "lib/index.js",