fix(host,client): stream bounded listings, declare schemastery, guard Choose again
The browse level now streams through opendir into a name-sorted window of maxEntries + 1 candidates (boundedInsert), so memory stays O(maxEntries) no matter how many children a directory holds and enterability probing touches only windowed candidates; a windowed broken symlink is not backfilled since the eviction already marks the level truncated. schemastery joins the package's runtime dependencies (the source launcher and isolated installs failed to resolve the value import). The folder-error dialog's Choose again goes inert while the flow hole is empty, and the withdrawal effect also keys on the open transition, so a flow can never open over a hole nobody serves.
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-28-directory-picker-capability-seam.md
|
||||
2026-07-28-directory-picker-capability-seam.md: 29c00c370b4fe00f528db62751f1a5df014ffd32
|
||||
2026-07-28-directory-picker-capability-seam.zh.md: 740d098ce881dd38f01281313bf44421d54b64c8
|
||||
2026-07-28-directory-picker-capability-seam.md: 7f78f7db0cca36c5aaeeb4efcab51d826945a4a1
|
||||
2026-07-28-directory-picker-capability-seam.zh.md: 31537fe1f21c1acbca73763bdd20e6d5f91a8d5e
|
||||
|
||||
@@ -20,7 +20,7 @@ Placement and policy rulings folded into this decision:
|
||||
- **Dependency survey (hand-roll vs adopt).** Node's stdlib *is* the maintained cross-platform OS layer (`readdir(withFileTypes)`, `homedir`, path semantics); surveyed alternatives fail the dependency bar — file-manager packages (`node-file-manager`, `files-and-folders`, Syncfusion's provider) are whole HTTP apps (fit), drive-letter helpers (`drivelist` native addon, `windows-drive-letters` ~7y stale) fail health/proportionality. The browse backend is a thin adapter over stdlib.
|
||||
- **Hidden entries: return-and-flag.** The host stamps `hidden` (POSIX dot convention) and returns everything; the client filters. Display policy stays client-side, and the planned show-hidden toggle becomes a client-only change. Windows' `FILE_ATTRIBUTE_HIDDEN` is not exposed by dirents — documented limitation until a native probe pays for itself.
|
||||
- **Symlinks: follow for enterability.** `stat` probes symlinks (broken/cyclic → skipped); crumbs keep the logical path the operator navigated, and `workspace.create` already canonicalizes via realpath at adoption.
|
||||
- **Listing levels are bounded.** One `list` call materializes at most `maxEntries` rows (config, default 1000 — GitHub's web-UI directory-listing bound): candidates sort before probing so a cut level keeps the name-sorted head and probing stops with the bound, and the wire `DirectoryListing` carries a required `truncated` flag so the client states incompleteness instead of silently missing tail entries. An unbounded level is a memory/responsiveness hole for large or adversarial directories.
|
||||
- **Listing levels are bounded, and streamed.** One `list` call returns at most `maxEntries` rows (config, default 1000 — GitHub's web-UI directory-listing bound). The level streams via `opendir` into a name-sorted window of `maxEntries + 1` candidates, so memory stays O(maxEntries) and enterability probing touches only windowed candidates; the wire `DirectoryListing` carries a required `truncated` flag so the client states incompleteness instead of silently missing tail entries. A windowed broken symlink is not backfilled from beyond the window — the eviction already marks the level truncated. An unbounded level is a memory/responsiveness hole for large or adversarial directories.
|
||||
- **Whole-filesystem scope, no roots config.** `workspace.create` accepts arbitrary paths and the API serves bash-driving methods, so a browse root would be UX scoping, not a boundary; configurability without a consumer fails the evidence bar. Deferred until a deployment needs it.
|
||||
- **The native backend stays.** Plugin-form was the point: multiple providers can serve the seam (an Electron shell would provide the `native` interaction through its own dialog API). Kind naming: `dialog` was the first pick and was dropped — the browse interaction also presents a dialog (the in-app modal), so the word failed to discriminate; `native` names where the chooser runs.
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ web GUI 的"打开本地文件夹"流程被焊死在一种交互上:`host.pick
|
||||
- **依赖调研(手写 vs 引入)。** Node 标准库本身就是维护中的跨平台 OS 层(`readdir(withFileTypes)`、`homedir`、路径语义);调研过的替代品都过不了依赖门槛——文件管理器包(`node-file-manager`、`files-and-folders`、Syncfusion 的 provider)是整套 HTTP 应用(契合度不过),盘符工具(原生插件 `drivelist`、约七年未更的 `windows-drive-letters`)健康度/比例失当。browse 后端是标准库上的薄适配。
|
||||
- **隐藏条目:返回并打标。** 宿主标注 `hidden`(POSIX 点前缀约定)并返回全部条目;客户端过滤。展示策略留在客户端,计划中的"显示隐藏"开关变成纯客户端改动。Windows 的 `FILE_ATTRIBUTE_HIDDEN` 不被 dirent 暴露——记为限制,直到原生探测值回其成本。
|
||||
- **符号链接:为可进入性而跟随。** 用 `stat` 探测符号链接(断链/循环→跳过);面包屑保留操作者导航的逻辑路径,`workspace.create` 在接纳时本就做 realpath 规范化。
|
||||
- **列举层级有上限。** 单次 `list` 至多物化 `maxEntries` 行(配置项,默认 1000——GitHub 网页端目录列举的同一上限):候选先按名排序再探测,因此被截断的层级保留排序后的头部、探测随上限一同停止;线上 `DirectoryListing` 携带必填的 `truncated` 标志,让客户端明示不完整而不是静默缺尾。无上限的层级对超大或恶意构造的目录就是内存/响应性漏洞。
|
||||
- **列举层级有上限,且流式处理。** 单次 `list` 至多返回 `maxEntries` 行(配置项,默认 1000——GitHub 网页端目录列举的同一上限)。层级经 `opendir` 流入一个按名排序、容量 `maxEntries + 1` 的候选窗口,内存保持 O(maxEntries),可进入性探测只触及窗口内候选;线上 `DirectoryListing` 携带必填的 `truncated` 标志,让客户端明示不完整而不是静默缺尾。窗口内的断链符号链接不从窗口外回填——发生过驱逐本身已把层级标记为截断。无上限的层级对超大或恶意构造的目录就是内存/响应性漏洞。
|
||||
- **全盘可浏览,不做 roots 配置。** `workspace.create` 接受任意路径且 API 本就提供驱动 bash 的方法,浏览根只会是 UX 范围而非边界;没有消费方的可配置性过不了证据门槛。等到有部署需要再做。
|
||||
- **native 后端保留。** 插件化正是目的:多方都能提供该 seam(Electron 壳可以经自己的对话框 API 提供 `native` 交互)。kind 命名:最初选了 `dialog` 后被放弃——browse 交互同样以对话框呈现(应用内弹窗),这个词起不到判别作用;`native` 命名的是选择器运行的位置。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user