@qilin-agent/client-ui-sidebar-files 0.0.0-stage → 3.1.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kkutysllb
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,37 @@
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
3
+ # After editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/client/ui-sidebar-files/README.md
5
+ /:
6
+ en: 9d1a6fd0e15334af
7
+ zh: 039c887fbe62563d
8
+ /qilin-agent-client-ui-sidebar-files:
9
+ en: 6d5bba3b98a43bb9
10
+ zh: 8e8bf7ec214d7000
11
+ /qilin-agent-client-ui-sidebar-files/summary:
12
+ en: 646be55dab8b8d64
13
+ zh: 324cab934fd2dc2a
14
+ /qilin-agent-client-ui-sidebar-files/table-of-contents:
15
+ en: 20d740a79f7fcd5d
16
+ zh: fb6ce45e71bdbfdc
17
+ /qilin-agent-client-ui-sidebar-files/what-it-registers:
18
+ en: 17086bd6d2fcca95
19
+ zh: a67338ebfdd401f0
20
+ /qilin-agent-client-ui-sidebar-files/the-tree:
21
+ en: c1c59c3049b9f2d4
22
+ zh: 93c3709a7565bb4d
23
+ /qilin-agent-client-ui-sidebar-files/the-editor:
24
+ en: 54f764f5ef9c461a
25
+ zh: 88e0e76b22b4463f
26
+ /qilin-agent-client-ui-sidebar-files/model-experience:
27
+ en: 337ea3bcf76f4839
28
+ zh: 141cfc1bbb74fe28
29
+ /qilin-agent-client-ui-sidebar-files/model-experience/kv-cache-effect:
30
+ en: bb56e6824621946b
31
+ zh: 21d28194e27cdde3
32
+ /qilin-agent-client-ui-sidebar-files/known-limitations-and-deferred-work:
33
+ en: abfa57c3de3a5048
34
+ zh: 7f090d26061985e6
35
+ /qilin-agent-client-ui-sidebar-files/known-limitations-and-deferred-work/dev-note:
36
+ en: e56c9cd5faac2459
37
+ zh: 22ae86f6dd9ff4c0
package/README.md CHANGED
@@ -1,3 +1,91 @@
1
- # Temporary Holding Version
1
+ ---
2
+ description: "The right Sidebar's file-tree and file-editor tab types for the qilin web client: the session workspace root listed one level at a time over the wire, files opened by resource address, and text or code files edited and saved back with version-checked writes."
3
+ kind: "package-reference"
4
+ ---
2
5
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
6
+ # @qilin-agent/client-ui-sidebar-files
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ The right Sidebar's navigator: the session's workspace root as a tree, listed one level at a time over the wire, plus a workbench that edits text and code files in place. Two tab types ship from this package. The `files` page is reached from the guide and claims no address; it opens files by address for the `qilin-resource://file` viewers to claim — nothing in `ui-sidebar-right` knows this package. The `file` type claims session file addresses with editable extensions, shows the same tree in a side pane, saves through the `workspaceFiles` write, and hands files to the preview viewer.
13
+
14
+ ## Table of Contents
15
+
16
+ - [What it registers](#what-it-registers)
17
+ - [The tree](#the-tree)
18
+ - [The editor](#the-editor)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="what-it-registers"></a>
26
+ ## What it registers
27
+
28
+ - **The `files` type** — `ctx.sidebarRightTabs.register(...)` with kind `files`, id `@qilin-agent/client-ui-sidebar-files`, band `builtin`, no patterns, and one guide entry (order 10, its title and description from the `sidebarFiles` namespace, its glyph the shared folder icon) that opens the type. One workspace tree per surface: it declares `single: true`.
29
+ - **The `file` type** — kind `file`, id `@qilin-agent/client-ui-sidebar-files/file`, band `builtin`, patterns `qilin-resource://file/**`. Its `canOpen` takes only session-scoped addresses whose path carries a known text or code extension (the shared editable set in `@qilin-agent/util-workspace-path`), so images, PDFs, unknown extensions, and bare absolute addresses fall through to the `text` fallback viewer; an absolute address is refused because saving it has no authorizing Session. It declares no `single` (one tab per address, the registry's default), no guide entry, and no static `icon` — the chip's sheet is the open file's own, drawn by the title slot. The tab title is the address's decoded basename.
30
+ - **The `files` body and chip title** — the keyed `sidebar.right.pane.tab` and `sidebar.right.pane.tab.title` seats under `@qilin-agent/client-ui-sidebar-files`: the header row, the filename search, and 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.
31
+ - **The `file` body and chip title** — the same two seats under `@qilin-agent/client-ui-sidebar-files/file`: the workbench (tree pane left at a fixed width, collapsible; editor right) and the per-file `FileTypeIcon` sheet before the captured basename.
32
+
33
+ Both seats share one store instance per session, bucketed by tab id: the tree buckets and the editor buckets never mix.
34
+
35
+ The browser half lives under `src/client/`: `definition.tsx` and `file-definition.ts` (what each type is), `store.ts` (what they keep), `face.ts`, `file-mutations.ts`, `file-face.ts`, and `file-pages.ts` (how they list, mutate, read, and save, Remote binding included), `file-preview.ts` (the preview open), `file-editor.ts` (the CodeMirror adapter), `file-guard.ts` (the claim gate and naming), `file-lang.ts` (grammar per extension), `file-failure.ts` (the editor's failure lines), `FileTree.tsx` (the shared tree), `FileSearch.tsx` (the search box and its matches), `FilesBody.tsx`, `FilesTitle.tsx`, `FileBody.tsx`, `FileTitle.tsx` (what they draw), `locales.ts` (what they say), and `index.ts` (the wiring).
36
+
37
+ <a id="the-tree"></a>
38
+ ## The tree
39
+
40
+ The root is the session's working directory, read from `useSessions().byId[sessionId].cwd`, and split for the header row by `pathPartsOf` from `@qilin-agent/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 `@qilin-agent/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.
41
+
42
+ | Entry type | Row |
43
+ |---|---|
44
+ | `directory` | Toggles; the level is fetched the first time it opens and kept while collapsed. |
45
+ | `file` | Opens `qilin-resource://file/session/<sessionId>/<encoded path relative to the root>`, built by `fileAddressFor` from `@qilin-agent/util-workspace-path`, through `useTabInfo().tab.actions.openResource`, landing in the tab's own pane. From the editor's tree the open lands on the `file` type itself and de-duplicates by address, so the open file never switches in place. |
46
+ | `other` | Shown greyed and not clickable, so the directory is reported whole. |
47
+
48
+ A level cut by the endpoint's entry cap ends with a marker; an empty level says so; a level that failed shows one line per code — `workspace-file/not-found`, `outside-workspace`, `not-directory` — and the transport's own message otherwise. Reload drops every listed level and asks again for the expanded ones; collapsed levels are fetched again when they next open. A session without a working directory shows a single line instead of a tree.
49
+
50
+ Dropping files onto the pane uploads them (`UploadOverlay.tsx`): each file is PUT whole to the `preview-media` host route `sidebar/media/upload`, which writes it under the workspace root through the filesystem's `writeBytes`, and a finished batch reloads the open levels. The overlay reports per-file results; a failed upload changes nothing on disk.
51
+
52
+ A right-click on a directory or file row opens the row's own menu at the cursor (`FileTree.tsx`): a file row offers open, every row offers copying the workspace-relative path and the absolute path (`copyTextOf`, written to the host clipboard through `ui-primitives`' `writeClipboard`; the root itself copies as `.`). An `other` row has no menu. A child whose listed name is itself a symbolic link carries the link glyph beside its name, because its row icon and type still describe what it resolves to.
53
+
54
+ That menu also carries the tree's mutations (`file-mutations.ts`): a directory offers creating a file or a folder inside it, every row offers renaming itself and deleting itself, and a right-click on the tree's own background creates at the workspace root. A rename replaces the row with a name box — Enter commits, Escape abandons, and a composing Enter is left to its IME; creating and deleting ask through a dialog, because a name and a removal that takes everything inside a directory both need an answer. A name is one path segment: surrounding spaces are dropped, and a blank, a dot segment, or anything carrying a separator is refused in place. Creating writes an empty file through `workspaceFiles.write` without a `baseVersion`, or makes a directory through `createDirectory`; renaming moves the entry through `move`, which refuses an existing destination; deleting removes it through `remove`, with `recursive` for a directory. Each gesture's outcome lands in the tab's mutation record, which the strip above the rows reports — a `workspace-file/*` code in the tree's own words, the carrier's message otherwise — and a dismissal clears. On success the directory that held the entry is listed again, and the tabs open on it follow: a tab whose address names the moved path is reopened at its new address in its own place, and a tab at or under a removed path is closed, so none is left pointing at a path nothing holds. The Host refuses a final symbolic link for all three, and the strip says so rather than hiding it.
55
+
56
+ Under the header row sits the filename search (`FileSearch.tsx`): one box whose text, once it settles, asks the `workspaceFiles` `searchNames` Remote for the files below the root whose basenames contain it, case-insensitively. While a query stands the matches take the body — a blank box, not an empty result, brings the tree back — and a click opens the match through the same session-scoped address a tree row uses. The box's text and its latest answer live in the tab's store bucket, so a remounted body comes back with both; typing drops the previous answer, because it describes a query the box no longer holds.
57
+
58
+ <a id="the-editor"></a>
59
+ ## The editor
60
+
61
+ The tab's address names its whole file identity: `file-guard.sessionFileOf` decodes it once per render into the session and path the endpoints receive, and the address's session — not the slot's — authorizes both. The file is read whole on mount through `workspaceFiles.read`, one page of lines at a time until `eof`; the page walk assembles the exact disk text, restoring a file-final newline through the reported byte size, and the first page's byte size refuses anything past the editor's 2 MB cap with the endpoint's own `workspace-file/too-large` code. The grammar comes from `file-lang.ts`: the installed `@codemirror/lang-*` packages plus legacy modes for the shell family and config files; anything else edits as plain text.
62
+
63
+ The surface is CodeMirror 6 behind one adapter (`file-editor.ts`): line numbers, undo history, bracket matching, a wrap compartment, and a theme plus a syntax `HighlightStyle` (comment, string, keyword, number, function, type, property) colored only through `--qilin-*` tokens, so the token cascade carries light and dark schemes without a second style. A navigation carrying `{ line }` — a tool card's line reference — selects and scrolls to that line, clamped into the file, once per navigation revision; the answered revision is remembered in the bucket, so a remount keeps the reader where they were. Saving runs through `workspaceFiles.write(sessionId, path, text, { baseVersion }, signal)`, where `baseVersion` is the last version a read or save reported; `Mod-s` and the toolbar button both save, controls disable while a save is in flight, and a finished save becomes the new clean content with its version. The draft lives in the package store, so it survives the body's unmounts; the surface remounts only when a load changes the file (the store's `loadSeq`), never for a draft or a save.
64
+
65
+ The toolbar's preview control (`file-preview.ts`) opens the tab's own address on the `text` type by naming that kind through the Sidebar controller with the tab's session as scope — the tab's own ranked open would land back on this type, which outranks the viewer. The preview shows the disk content, so an unsaved draft stays here untouched, and the viewer's matching edit control opens the ranked claim, which is this type. A save the disk refuses with `workspace-file/stale` keeps the draft intact and raises the conflict banner, whose two actions are the escape hatch: load the disk copy (a plain re-read that discards the draft) or overwrite with the editor's version (a forced write without `baseVersion`). Reload with unsaved changes asks first, for the same reason. Read and save failures show one line per code — `not-found`, `too-large` (sized), `not-text`, `not-regular-file`, `outside-workspace`, `stale` — and the transport's message otherwise.
66
+
67
+ <a id="model-experience"></a>
68
+ ## Model Experience
69
+
70
+ None, as this package draws a workspace file tree and an editor in the browser and registers nothing model-facing.
71
+
72
+ #### KV Cache effect
73
+
74
+ None; directory listings and file content travel over the Remote and assemble no model request.
75
+
76
+ ## Known Limitations and Deferred Work
77
+
78
+ <a id="known-limitations-and-deferred-work"></a>
79
+ - **Listing, search, and row mutations only.** No artifact filter, current-file highlight, or filesystem watching; a level changes only through reload or a mutation's own re-list. Name search is the one global view (`searchNames`); nothing filters the tree in place, and upload is the tree's other write gesture (drop, `ui-sidebar-documentpreview` `preview-media`). A rename keeps the entry in its directory — there is no move, no multi-select, and no drag-and-drop of a row onto another directory; the tab reopened at the new address reads the file again, so an unsaved draft in a renamed file's tab does not survive the rename.
80
+ - **One root.** The tree is rooted at the session's working directory; there is no way to browse above it, and the Host refuses paths outside the workspace root anyway.
81
+ - **Editor caps.** A file past 2 MB does not open (the Host's own per-page caps bound each read); there is no search panel and no column selection.
82
+
83
+ <a id="dev-note"></a>
84
+ ### Dev Note
85
+
86
+ <details>
87
+ <summary>Working context for maintainers — click to expand</summary>
88
+
89
+ The two tab kinds share one store factory (`createFilesStore`) and one registration-time handle, split into `byTab` (tree) and `edits` (editor) buckets keyed by tab id. The `files` page never seeds an `edits` bucket and the `file` tab renders the same tree component, so a file tab's tree and the page's tree stay independent buckets of one instance.
90
+
91
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,91 @@
1
+ ---
2
+ description: "qilin Web 客户端右侧 Sidebar 的文件树与文件编辑器 tab 类型:逐层经线上列出会话工作区根目录,按资源地址打开文件,并把文本或代码文件经版本校验的写回存到工作区。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @qilin-agent/client-ui-sidebar-files
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ 右侧 Sidebar 的导航器:把会话的工作区根目录画成一棵树,逐层经线上列出,外加一个就地编辑文本与代码文件的工作台。本包注册两个 tab 类型。`files` 页从引导页进入,不认领任何地址;它按地址打开文件,交给 `qilin-resource://file` 的查看器认领:`ui-sidebar-right` 里没有任何东西认识本包。`file` 类型认领扩展名可编辑的会话文件地址,在侧栏窗格里显示同一棵树,经 `workspaceFiles` 写回保存,并把文件交给预览查看器。
13
+
14
+ ## 目录
15
+
16
+ - [注册了什么](#what-it-registers)
17
+ - [树](#the-tree)
18
+ - [编辑器](#the-editor)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与暂缓事项](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="what-it-registers"></a>
26
+ ## 注册了什么
27
+
28
+ - **`files` 类型**:`ctx.sidebarRightTabs.register(...)`,kind 为 `files`,id 为 `@qilin-agent/client-ui-sidebar-files`,档位 `builtin`,没有 patterns,另有一个打开该类型的引导页入口(order 10,标题与描述取自 `sidebarFiles` 命名空间,图标是共享的文件夹图标)。整个侧栏只有一棵工作区树:它声明了 `single: true`。
29
+ - **`file` 类型**:kind 为 `file`,id 为 `@qilin-agent/client-ui-sidebar-files/file`,档位 `builtin`,patterns 为 `qilin-resource://file/**`。它的 `canOpen` 只接受路径扩展名属于已知文本或代码集合(即 `@qilin-agent/util-workspace-path` 里的共享可编辑集合)的会话地址,图片、PDF、未知扩展名与裸绝对地址都落到 `text` 兜底查看器;绝对地址被拒绝,因为保存它没有授权会话。它不声明 `single`(按注册表默认,一个地址一个 tab)、没有引导页入口、也没有静态 `icon`——标签页上的图标就是打开文件自己的类型图标,由标题坑位绘制。tab 标题是地址解码后的基名。
30
+ - **`files` 正文与标签页标题**:以 `@qilin-agent/client-ui-sidebar-files` 为键的 `sidebar.right.pane.tab` 与 `sidebar.right.pane.tab.title` 两个坑位:标题行、文件名搜索与树。标题行与文档预览(`ui-sidebar-documentpreview`)的相同:根路径,目录部分灰色、最后一段正色,从不省略号截断(比行宽的路径保留末尾、淡出开头),右端是它唯一的控件、重新读取。这一行是复制而非共享,因为插件 bundle 只经平台模块共享运行时代码;待 artifact 与各 slot 的形态定下来后,可以在 `ui-primitives` 放一份供每个 pane 标题行使用。
31
+ - **`file` 正文与标签页标题**:同样两个坑位,键为 `@qilin-agent/client-ui-sidebar-files/file`:工作台(左侧固定宽度、可收起的树窗格;右侧编辑器)与打开文件的 `FileTypeIcon` 图标加基名。
32
+
33
+ 两组坑位共享一个按会话实例化的 store,按 tab id 分桶:树的桶与编辑器的桶互不掺混。
34
+
35
+ 浏览器半部住在 `src/client/` 下:`definition.tsx` 与 `file-definition.ts`(各类型是什么)、`store.ts`(它们保存什么)、`face.ts`、`file-mutations.ts`、`file-face.ts` 与 `file-pages.ts`(它们如何列目录、改动、读文件与保存,含 Remote 绑定)、`file-preview.ts`(预览打开)、`file-editor.ts`(CodeMirror 适配层)、`file-guard.ts`(认领门槛与命名)、`file-lang.ts`(按扩展名的语法)、`file-failure.ts`(编辑器的失败行)、`FileTree.tsx`(共享的树)、`FileSearch.tsx`(搜索框与匹配结果)、`FilesBody.tsx`、`FilesTitle.tsx`、`FileBody.tsx`、`FileTitle.tsx`(它们画什么)、`locales.ts`(它们说什么)、`index.ts`(接线)。
36
+
37
+ <a id="the-tree"></a>
38
+ ## 树
39
+
40
+ 根是会话的工作目录,读自 `useSessions().byId[sessionId].cwd`,标题行里的拆分由 `@qilin-agent/util-workspace-path` 的 `pathPartsOf` 给出。每一层以绝对路径为键;子路径是父路径以 `/` 拼上条目名。一层在首次展开时经 `@qilin-agent/api-workspace-files` 命名空间的 `remote.workspaceFiles.list(sessionId, absolutePath)` 列出;适配层保留列表的条目与截断标志,丢弃其工作区相对路径。行序为目录优先,其后按自然序、不分大小写的名称排列;dotfiles 与其他条目一样显示。
41
+
42
+ | 条目类型 | 行 |
43
+ |---|---|
44
+ | `directory` | 切换展开与折叠;该层在首次打开时拉取,折叠期间保留。 |
45
+ | `file` | 经 `useTabInfo().tab.actions.openResource` 打开 `qilin-resource://file/session/<sessionId>/<编码后的相对路径>`,地址由 `@qilin-agent/util-workspace-path` 的 `fileAddressFor` 生成,落在该 tab 自己的 pane 里。从编辑器的树里打开时,它落到本包的 `file` 类型上并按地址去重,所以打开的文件不会就地切换。 |
46
+ | `other` | 灰显且不可点击,使目录被完整报告。 |
47
+
48
+ 被端点条目上限截断的层以一条标记收尾;空层如实说明;失败的层按错误码各显示一行(`workspace-file/not-found`、`outside-workspace`、`not-directory`),其他情况显示传输层自己的消息。重新读取丢弃所有已列出的层并只对展开中的层重新请求;折叠的层在下次打开时重新拉取。没有工作目录的会话只显示一行说明,而不是树。
49
+
50
+ 把文件拖到窗格上即上传(`UploadOverlay.tsx`):每个文件整体 PUT 到 `preview-media` 宿主路由 `sidebar/media/upload`,由它经文件系统的 `writeBytes` 写到工作区根之下,完成一批后重载展开中的层。覆盖层按文件报告结果;失败的上传不会改动磁盘。
51
+
52
+ 右键目录或文件行会在光标处打开该行自己的菜单(`FileTree.tsx`):文件行提供打开,所有行都提供复制工作区相对路径与绝对路径(`copyTextOf`,经 `ui-primitives` 的 `writeClipboard` 写入宿主剪贴板;根自身复制为 `.`)。`other` 行没有菜单。名字本身是符号链接的子项在名称旁带链接图标,因为行图标与类型描述的仍是它解析到的目标。
53
+
54
+ 同一个菜单也承载树的改动(`file-mutations.ts`):目录提供在其中新建文件或文件夹,每一行都提供就地重命名与删除,右键树的空白背景则在工作区根新建。重命名把那行换成名称输入框——Enter 提交、Escape 放弃、处于输入法组字中的 Enter 留给输入法;新建与删除走对话框,因为一个名字、以及一次会连同目录内容一起的删除,都需要一个答复。名称是单个路径段:首尾空白被去掉,空白、点段、或带分隔符的任何输入都就地拒绝。新建经 `workspaceFiles.write`(不带 `baseVersion`)写入空文件,或经 `createDirectory` 建目录;重命名经 `move` 移动该条目,目标已存在会被拒绝;删除经 `remove`,目录带 `recursive`。每次手势的结果落进该 tab 的改动记录,由行上方的提示条报告——`workspace-file/*` 码用树自己的话,其余用传输层的消息——点关闭即清空。成功后会重新列出该条目所在的目录,打开它的 tab 也随之跟进:地址指向被移动路径的 tab 会在原位置按新地址重新打开,位于被删除路径之上或之下的 tab 会被关闭,因此不会留下指向不存在路径的 tab。宿主对三者都拒绝末端符号链接,提示条如实说明而不隐藏。
55
+
56
+ 标题行下方是文件名搜索(`FileSearch.tsx`):一个输入框,文本停稳后向 `workspaceFiles` 的 `searchNames` Remote 询问根之下 basename 包含它的文件,不区分大小写。查询存在时匹配结果占据正文——让树回来的是清空的输入框,而不是空结果——点击匹配走树行同一套会话作用域地址打开。输入框文本与最近一次回答都住在该 tab 的 store 桶里,重挂载的正文连同两者一起回来;输入即丢弃上一个回答,因为它描述的是输入框已不再持有的查询。
57
+
58
+ <a id="the-editor"></a>
59
+ ## 编辑器
60
+
61
+ tab 的地址就是它的整个文件身份:`file-guard.sessionFileOf` 在每次渲染时把它解码成端点接收的会话与路径,而授权读取与写入的是地址里的会话,不是坑位的。文件在挂载时经 `workspaceFiles.read` 整读:一页页行直到 `eof`;分页行走把磁盘上的原文拼装出来——凭上报的字节数补回文件末尾的换行——并且第一页的字节数会以端点自己的 `workspace-file/too-large` 码拒绝超过编辑器 2 MB 上限的文件。语法来自 `file-lang.ts`:装好的 `@codemirror/lang-*` 包,外加 shell 族与配置文件的 legacy 模式;其余按纯文本编辑。
62
+
63
+ 界面是 CodeMirror 6 藏在一个适配层(`file-editor.ts`)后面:行号、撤销历史、括号匹配、一个换行 compartment,以及一套只用 `--qilin-*` token 上色的主题加语法 `HighlightStyle`(注释、字符串、关键字、数字、函数、类型、属性),于是明暗两套配色由 token 级联承担,不需要第二套样式。携带 `{ line }` 的导航——工具卡的行引用——会选中并滚动到该行(越界钳到文件边界),每次导航修订只落一次;应答过的修订记在桶里,重新挂载后读者停在原处。保存经 `workspaceFiles.write(sessionId, path, text, { baseVersion }, signal)`,其中 `baseVersion` 是最近一次读取或保存上报的版本;`Mod-s` 与工具栏按钮都触发保存,保存进行中禁用控件,完成的保存带着新版本成为新的干净内容。草稿住在包的 store 里,所以正文卸载再挂载它还在;只有加载改变了文件(store 的 `loadSeq`)才会重挂界面,草稿或保存都不会。
64
+
65
+ 工具栏的预览控件(`file-preview.ts`)把 tab 自己的地址经 Sidebar 控制器指名 `text` 类型打开,会话作用域取 tab 自己的——走 tab 自己的按排名打开只会落回本类型,因为它的档位高于查看器。预览显示的是磁盘内容,所以未保存的草稿在这里原样不动,查看器侧对应的编辑控件走按排名的认领,落回本类型。被磁盘以 `workspace-file/stale` 拒绝的保存会原样保住草稿并升起冲突横幅,横幅上的两个动作就是出口:载入磁盘内容(一次丢弃草稿的重新读取)或用编辑器里的版本覆盖(不带 `baseVersion` 的强制写)。有未保存修改时的重新读取也会先询问,理由相同。读取与保存的失败各按错误码显示一行——`not-found`、`too-large`(带大小)、`not-text`、`not-regular-file`、`outside-workspace`、`stale`——其余显示传输层的消息。
66
+
67
+ <a id="model-experience"></a>
68
+ ## 模型体验
69
+
70
+ 无,因为本包在浏览器里绘制工作区文件树与编辑器,不注册任何面向模型的内容。
71
+
72
+ #### KV Cache 影响
73
+
74
+ 无;目录列表与文件内容经 Remote 传输,不会组装模型请求。
75
+
76
+ ## 已知限制与暂缓事项
77
+
78
+ <a id="known-limitations-and-deferred-work"></a>
79
+ - **只有列目录、搜索与行改动。**没有产物过滤、当前文件高亮或文件系统监听;一层只会因重新读取或改动自身的重列而变化。名称搜索是唯一的全局视图(`searchNames`);没有就地过滤树的能力,上传是树另一项写入手势(拖放,`ui-sidebar-documentpreview` 的 `preview-media`)。重命名把条目留在原目录——没有移动、没有多选,也不能把一行拖到另一个目录上;在新地址重新打开的 tab 会重新读取文件,所以被重命名文件的 tab 里未保存的草稿不会随重命名留下。
80
+ - **只有一个根。**树以会话工作目录为根;没有办法浏览到它之上,而 Host 本来也拒绝工作区根之外的路径。
81
+ - **编辑器上限。**超过 2 MB 的文件不打开(Host 自己的每页上限约束每次读取);没有搜索面板、没有列选择。
82
+
83
+ <a id="dev-note"></a>
84
+ ### 开发备注
85
+
86
+ <details>
87
+ <summary>维护者工作上下文——点击展开</summary>
88
+
89
+ 两个 tab kind 共享一个 store 工厂(`createFilesStore`)与注册期的一个句柄,按 tab id 分成 `byTab`(树)与 `edits`(编辑器)两组桶。`files` 页从不播种 `edits` 桶,`file` tab 渲染同一个树组件,所以一个文件 tab 的树与页面的树是同一实例下互不相干的桶。
90
+
91
+ </details>