fix(host): review round 22 — note enumeration includes the create path; cross-method equality pinned; NFD-volume boundary recorded

This commit is contained in:
creatixchu
2026-07-30 02:53:45 +08:00
parent 8016591ebe
commit de24461b91
10 changed files with 22 additions and 13 deletions

View File

@@ -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 packages/host/directory-picker-browse/README.md
README.md: 23153881b84dcb71dfb05d4f297a5818c410ca77
README.zh.md: d7010e2941a801ba6358082824330eaae46e42b7
README.md: 01e2b9e5afcfd7c47a3f42a76cc1388a25477334
README.zh.md: 8cb63048713dc964f92762546de783d2ce6ce5a7

View File

@@ -19,5 +19,6 @@ None; this package neither assembles nor sends a provider request.
## Known Limitations and Deferred Work
- **Windows hidden attribute is not read** — Node dirents do not expose `FILE_ATTRIBUTE_HIDDEN`, so `hidden` means dot-prefixed on every platform until a native probe is worth its cost.
- **Name-normalizing volumes void the create-path equality** — `createDirectory` promises its return verbatim-equal to the child's next `entries[].path`; Node's namespaced Win32 paths store even trailing-dot/space segments literally, but a volume that rewrites names on storage (NFD normalization on HFS+-style volumes) breaks the match, and the create landing degrades to the documented single-pane / edit-zone fallback.
- **No drive-root enumeration** — on Windows the ancestry stops at the drive root; crossing drives waits for the browser UI's path-entry affordance rather than an enumeration primitive here.
- **Whole-filesystem scope** — no per-deployment browse-root restriction; `workspace.create` accepts arbitrary paths today, so a root here would be UX scoping, not a boundary — deferred until a deployment needs it.

View File

@@ -19,5 +19,6 @@
## 已知限制与延期工作
- **不读取 Windows 隐藏属性**——Node 的 dirent 不暴露 `FILE_ATTRIBUTE_HIDDEN`,因此在所有平台上 `hidden` 都意味着点前缀,直到原生探测值回其成本为止。
- **名称规范化的卷会使创建路径等式失效**——`createDirectory` 承诺其返回值与该子项下一次的 `entries[].path` 逐字相等Node 带命名空间的 Win32 路径连末尾点空格段都按字面存储但在存储时改写名称的卷HFS+ 风格卷上的 NFD 规范化)会破坏这一匹配,创建落地随之退化为文档所述的单栏/编辑区回退。
- **不枚举盘符根**——Windows 上祖先链止于盘符根;跨盘依赖浏览器 UI 的路径输入入口,而不是这里的枚举原语。
- **全盘可浏览**——没有按部署限定的浏览根;`workspace.create` 今天就接受任意路径,这里的根只会是 UX 范围而非边界——等到有部署需要时再做。

View File

@@ -85,12 +85,12 @@ function foldSeparatorsFor(sep: '\\' | '/'): (value: string) => string {
* Lexically normalizes a typed absolute path for comparisons against the
* backend's resolved ones (every listing path arrives in the
* DirectoryListing contract's canonical shape; only the DRAFT side, the
* one path a user types, needs this): collapses
* repeated and trailing separators, drops `.` segments, and applies `..`
* without ever crossing the root — POSIX's `/`, a drive's `C:`, or UNC's
* `\\server\share` pair — mirroring resolve()'s lexical behavior. Expects
* separators already folded to `sep` (foldSeparatorsFor); a lexical mirror
* only, symlinks are the backend's business.
* one path a user types, needs this): collapses repeated and trailing
* separators, drops `.` segments, and applies `..` without ever crossing
* the root — POSIX's `/`, a drive's `C:`, or UNC's `\\server\share` pair —
* mirroring resolve()'s lexical behavior. Expects separators already
* folded to `sep` (foldSeparatorsFor); a lexical mirror only, symlinks are
* the backend's business.
*/
function normalizePathFor(sep: '\\' | '/'): (value: string) => string {
return (value) => {

View File

@@ -38,5 +38,6 @@ it('resolves a decorated homedir before stamping listing.home', async () => {
const listing = await picked.list()
expect(listing.home).toBe(resolve(scratch))
expect(listing.path).toBe(listing.home)
expect(listing.crumbs.at(-1)!.path).toBe(listing.home)
await fiber.dispose()
})

View File

@@ -207,6 +207,10 @@ describe('BrowseDirectoryPicker', () => {
expect(created).toBe(join(root, 'fresh'))
const listing = await capability.list(root)
expect(listing.entries.map(entry => entry.name)).toContain('fresh')
// The contract's cross-method equality: the returned path is verbatim
// the child's entries[].path (clients anchor the create landing's
// selection and focus on it).
expect(listing.entries.find(entry => entry.name === 'fresh')!.path).toBe(created)
})
it('refuses an existing child with directory-exists', async () => {