Skip to content

PLAN-FNOS-034 fnOS 授权目录选择器改为输入卡片宽浮层 ​

字段内容
计划编号PLAN-FNOS-034
计划日期2026-10-28
对应需求FNOS-034 fnOS 授权目录选择器改为输入卡片宽浮层
计划状态阶段一至四全部完成(真实 NAS 验收于 2026-10-30 通过)

实施结果(2026-10-28) ​

项结果
阶段一 实现完成:新增 picker-store.ts、picker-tree.ts,面板改挂 conversation.input.overlay(id: dsh-fnos-path-panel、order: 10),按钮只翻转 store
阶段二 包级验证完成:pnpm --filter @tnnevol/dsh-fnos run check 35 文件 / 270 条全绿;run build 产出 lib/client.js 2.13 MB
阶段三 真机验收完成(2026-10-30):FPK 在真实 NAS 安装后由提出人验收通过;浮层位置与宽度、层级、取消授权与标题栏文案按钮均符合预期
阶段四 文档与版本需求/计划/测试用例已回填,manifest 已更新,索引与 sidebar 已登记

验收以 FPK 真机为准

@tnnevol/dsh-fnos 脱离 fnOS 宿主没有可提供的能力,按本地 DSH Web的约定不进本地 profile。实现期间做过一次卡在临时 profile 上的前端几何自检(仅用于确认锚点与宽度算式成立,结果见下),不计入验收;浮层的实际呈现与交互一律以真实 NAS 上安装 FPK 后的表现为准。

实现了「位置相同」这件事的结构性依据是:面板 offsetParent 是上游输入卡片内的 [data-composer-overlay] 锚点,与斜杠指令面板同一个定位上下文;几何也只复用卡片宽度轴,不写像素常量。

计划目标 ​

实现 FNOS-034:把选择器从「锚在 28px 按钮上的 TreeSelect 下拉」改为 conversation.input.overlay 里的卡片宽浮层,交互与斜杠指令面板一致。

技术边界:只改 plugins/dsh-fnos-plugin 的客户端呈现层,不修改 DSH 上游、不改 Host 路由与授权目录契约。

调研依据 ​

事实出处
指令面板占用 conversation.input.overlay,id: 'slash-menu'、order: 0上游 packages/client/ui-input-trigger/src/client/index.ts
锚点是卡片内零高定位上下文,宽度等于卡片上游 InputBar.tsx(overlayAnchor)、InputBar.module.css(inset: 0 0 auto; height: 0)
指令面板几何:bottom: calc(100% + 4px); left: 0; right: 0; z-index: 100上游 MenuView.module.css
该槽现有条目:slash-menu(0)、command-popup(1)、feedback-dialog(2)cordis_inspect_query 的 Slots.listSubTree 实测
槽在册 owner props 为空;标准 props 含 useInput、inputActions、sessionId同上(ownerProps: [])
槽只在有会话时挂载上游 InputBar.tsx 的 sessionId !== undefined
卡片宽度轴来自 --dsh-composer-card-max-width,iframe 内为 min(calc(100% - 16px), 952px)上游 ConversationRoot.module.css 的 .body / .embeddedBody
Semi Tree 支持 multiple、checkRelation="unRelated"、loadData@douyinfe/semi-ui 2.90.2 的 TreeProps

当前实现与目标设计 ​

领域当前实现目标实现迁移影响
承载形式TreeSelect 下拉,弹层由 Semi 写内联 top/left/transform自有浮层面板 + DshTree,几何由 CSS 决定不再依赖覆盖 Semi 内联样式
触发位置conversation.input.left 的 28px 图标按钮按钮保留,但只切开关 store,不再承载弹层用户点击路径不变
浮层位置锚定按钮,min(560px, 100vw - 32px) 固定宽conversation.input.overlay,贴齐卡片、宽度自适应与指令面板一致
选择语义父子独立勾选、勾选即插入、取消即移除原样复用(同一份 occurrence 对账逻辑)行为不变
关闭方式点弹层外(Semi 行为)点卡片外 / Esc / 再点按钮更明确
会话隔离TreeSelect 组件内部状态会话键控的轻量 store不串数据

组件拆分 ​

FnosAuthorizedPathPicker.tsx 现在是一个组件同时做三件事:TreeSelect 触发按钮、浮层内容、引用对账。拆成:

文件职责
src/client/input-references/picker-store.ts会话键控的开关 store(useSyncExternalStore 兼容),按钮与浮层共享
src/client/input-references/picker-tree.ts纯函数:AuthorizedEntry → 树节点、updateChildren、selectedPaths,便于单测
src/components/FnosAuthorizedPathPicker.tsx浮层面板本体:树、加载、勾选、引用插入与移除对账
src/components/FnosInputPickerButton.tsx工具栏触发器:只负责切换 store,保留 fnOS 图标与忙碌态

交互重编排 ​

mermaid
sequenceDiagram
  participant U as 用户
  participant B as 工具栏按钮
  participant S as picker-store
  participant P as 浮层面板
  participant I as DSH 输入机
  U->>B: 点击 fnOS 图标
  B->>S: toggle(sessionId)
  S-->>P: 订阅变更后渲染浮层
  U->>P: 勾选目录
  P->>I: slash/input-insert-reference
  I-->>P: occurrences 更新
  P->>P: 对账:取消勾选则移除对应引用
  U->>P: Esc / 点卡片外
  P->>S: close(sessionId)

分阶段任务 ​

阶段一:实现 ​

任务关联验收内容依赖验证
PLAN-FNOS-034-T01-01FNOS-034-01 / AC-01新增 conversation.input.overlay 注册条目(自有 id、order: 10),仅在 fnOS 会话内挂载无槽位单测
PLAN-FNOS-034-T01-02FNOS-034-01 / AC-02、AC-03浮层几何:贴齐卡片、无固定宽常量、最大高度按可用空间钳制T01-01样式契约测试
PLAN-FNOS-034-T01-03FNOS-034-02 / AC-01、AC-02、AC-03抽出 store 与树纯函数,浮层复用现有勾选与引用对账;会话隔离T01-01单测
PLAN-FNOS-034-T01-04FNOS-034-03 / AC-01、AC-02、AC-03关闭出口:卡片外点击、Esc、再点按钮T01-03单测
PLAN-FNOS-034-T01-05本地化新增浮层标题、关闭、空态等文案的中英文案无文案单测
PLAN-FNOS-034-T01-06FNOS-034-04 / AC-01浮层 z-index 取 99,严格低于指令面板的 100T01-02样式契约测试
PLAN-FNOS-034-T01-07FNOS-034-05 / AC-01移除 Tree 的 filterTreeNode 与搜索占位符无样式契约测试
PLAN-FNOS-034-T01-08FNOS-034-06 / AC-01、AC-02目录右键先弹上下文菜单(新建文件夹/新建文件)再新建;文件右键不弹菜单;不设标题栏新建入口无契约测试
PLAN-FNOS-034-T01-09FNOS-034-06 / AC-03、AC-05命名弹框、错误文案映射、成功后并入当前层(不整树重取)T01-08契约测试
PLAN-FNOS-034-T01-10FNOS-034-06 / AC-04Host 新增 authorized-directories/create 路由:父目录走既有授权校验,名称单段校验,错误码分级无单测 + 类型检查
PLAN-FNOS-034-T01-11FNOS-034-07 / AC-01、AC-02、AC-03节点标签改用 Typography.Text 的 ellipsis={{ showTooltip: true, rows: 1 }}:省略与「溢出才提示」都由 Semi 内部 isOverflowed 判定;容器 min-width 改为 0,且不自定义 text-overflow 以免与 Typography 自带规则抵消T01-08契约测试

新建条目的 Host 实现要点 ​

关注点处理
授权边界复用 validateReadableAuthorizedPath(授权根包含 + 真实路径解析 + 当前 NAS 用户可读),不新增权限模型
名称合法性parseCreateRequest 只接受单段名:无分隔符、无控制字符、非 ./..、长度 ≤255、排除 : * ? " < > |(SMB 兼容)
二次落点校验拼接后再用 isPathWithinAuthorizedDirectory 确认目标仍在授权根内
不覆盖数据目录用 mkdir、文件用 writeFile(..., { flag: 'wx' });EEXIST 映射为 409,绝不静默覆盖
错误分级EEXIST→409、EACCES/EPERM→403、ENOSPC/EDQUOT→507、EROFS→403,客户端逐条翻译成可操作文案

阶段二:包级验证 ​

任务关联验收内容依赖验证
PLAN-FNOS-034-T02-01全部pnpm --filter @tnnevol/dsh-fnos run check(typecheck + vitest)阶段一命令输出
PLAN-FNOS-034-T02-02全部pnpm --filter @tnnevol/dsh-fnos run build,确认 lib/client.js 正常产出阶段一构建输出
PLAN-FNOS-034-T02-03契约包契约测试:客户端 externals 未新增、overlay 注册形状正确T01-01测试

阶段三:真机验收 ​

任务关联验收内容依赖验证
PLAN-FNOS-034-T03-01FNOS-034-01构建 FPK 并在真实 NAS 走应用中心安装,观察浮层位置与宽度跟随T02-02实机截图
PLAN-FNOS-034-T03-02FNOS-034-02/03勾选父子目录、取消勾选移除引用、Esc 与点击外部收起T03-01实机操作记录

阶段四:文档与版本 ​

任务关联验收内容依赖验证
PLAN-FNOS-034-T04-01全部回填 docs/tests/FNOS-034-*.md;更新 manifest desc 与 changelog阶段二、三文档构建
PLAN-FNOS-034-T04-02全部pnpm run version 升版本(独立提交,Tag 不推送)T04-01git log

技术方案 ​

为什么不用 conversation.input.dock ​

dock 是在流内的卡片:宽度算式要扣掉两侧 clearance 与 dock inset(左右各内缩 16px),会挤占对话区高度,且和指令面板不在同一层。需求要的是「与指令放的位置差不多」,只有 overlay 满足。

为什么不把 TreeSelect 的弹层搬进来 ​

Semi 的 popupContainer / getPopupContainer 只改挂载点,弹层仍由 popper 写内联 top/left/transform;要贴齐卡片只能靠 !important 压掉这些内联值,属于跟上游实现细节对赌,升级易碎。改用 DshTree 直接渲染在自有容器里,几何完全由本插件 CSS 决定。

关闭与焦点 ​

  • 点击卡片外:在 pointerdown 捕获阶段判断事件目标是否落在 [data-composer-card] 内,不在则关闭(与上游 MenuView 同一策略)。
  • Esc:浮层根节点监听 keydown。
  • 再点按钮:store 的 toggle。

会话隔离 ​

store 内部是 Map<SessionId, boolean>,快照按「当前是否有展开会话」投影;会话销毁时不需要额外清理,因为面板随会话槽位卸载,再次打开时由 visible 变化重置。

风险与回滚 ​

风险影响应对
浮层与指令面板同时打开时重叠视觉遮挡打开浮层时先 dismiss 当前输入触发菜单(若可解析);否则按 z-index 顺序接受,两者不会同时由用户触发
上游调整 overlayAnchor 或槽位语义几何失效几何只用卡片宽度轴,不写死像素;契约测试钉住注册形状
大目录树渲染卡顿交互迟滞沿用现有 loadData 懒加载;不引入 virtualize 以免与 loadData 组合出边界问题
回滚—恢复 FnosInputPickerButton 内的 TreeSelect 渲染并删除 overlay 注册即可,Host 与数据层无改动

完成定义 ​

  • [ ] FNOS-034-01/02/03 的验收条件全部有测试或实机证据
  • [ ] pnpm --filter @tnnevol/dsh-fnos run check 全绿
  • [ ] pnpm --filter @tnnevol/dsh-fnos run build 产出 lib/client.js
  • [ ] docs/tests/FNOS-034-*.md 已回填
  • [ ] manifest desc 与 changelog 已更新
  • [ ] 版本已用 pnpm run version 提升(独立提交)

基于 VitePress 构建