Conversation
- 用 vault.getFiles() 替换递归文件夹搜索,简化 basename 查找 - 修复 dragEnterCount 可能为负的问题(改用 Math.max) - 改进 isObsidianFileDrag 检测,排除 URL/书签/外部纯文本拖拽 - 引入 pendingContextFiles 队列,实现发送时消费并附加 <context_files> - 修复重复附加同一文件的 no-op 处理 - 更新测试 mock 以包含 consumeContextFiles Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
该 PR 为聊天输入框增加了从 Obsidian 左侧文件栏拖拽文件的能力:拖拽后以 chip 形式附加到会话上下文,并在发送时将文件路径以 <context_files> 注入 prompt;同时对跨平台路径解析/拼接做了多处健壮性改进并补充了单元测试覆盖。
Changes:
- 新增
FileDragDropManager:识别 Obsidian 文件树拖拽并将文件路径回调给FileContextManager进行附加与提示。 FileContextManager/InputController增加 pending context files 队列:发送时注入<context_files>,并在会话切换时清空。- 强化跨平台路径处理(PATH 解析、posix/win32 join、MSYS 路径转换等)并更新/新增相关单测与样式、i18n 文案。
Reviewed changes
Copilot reviewed 32 out of 32 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/utils/path.test.ts | 调整 normalizePathForFilesystem 环境变量展开断言以匹配新行为 |
| tests/unit/features/chat/ui/FileDragDropManager.test.ts | 新增拖拽管理器单测覆盖 overlay、解析与去重逻辑 |
| tests/unit/features/chat/ui/FileContextManager.test.ts | 增加 chip 渲染与移除行为测试 |
| tests/unit/features/chat/tabs/Tab.test.ts | 为新增 UI 组件 FileDragDropManager 补齐 mock |
| tests/unit/features/chat/controllers/InputController.test.ts | 为 consumeContextFiles 补齐 mock 以适配发送逻辑变更 |
| tests/unit/core/agents/AgentManager.test.ts | 修复测试中 Windows 路径分隔符导致的匹配问题 |
| src/utils/sdkSession.ts | 统一用 posix join + 斜杠规范化生成 SDK session 路径 |
| src/utils/path.ts | 重构 PATH 解析/探测与多处跨平台 join/normalize 行为 |
| src/utils/externalContext.ts | child 冲突检测改为“优先 parent 冲突,否则返回首个 child 冲突” |
| src/utils/env.ts | 平台路径 API 统一化、增强 PATH 拼接与 node 发现逻辑 |
| src/style/index.css | 引入新增拖拽样式文件 |
| src/style/features/file-drag-drop.css | 新增拖拽 overlay 与动画样式 |
| src/i18n/types.ts | 添加 chat.fileDrop.* 翻译 key 类型 |
| src/i18n/locales/*.json | 各语言补齐 file drop notice/overlay 文案 |
| src/features/chat/ui/index.ts | 导出 FileDragDropManager |
| src/features/chat/ui/file-context/view/FileChipsView.ts | renderCurrentNote 支持渲染当前文件 + 已附加文件 chips |
| src/features/chat/ui/FileDragDropManager.ts | 新增拖拽解析与 overlay 展示、vault 路径解析逻辑 |
| src/features/chat/ui/FileContext.ts | 增加 pendingContextFiles + attach/consume API,并在会话切换时清空 |
| src/features/chat/tabs/types.ts | TabUIComponents 增加 fileDragDropManager |
| src/features/chat/tabs/Tab.ts | 初始化/销毁 FileDragDropManager,drop 后 attach 并 Notice 提示 |
| src/features/chat/controllers/InputController.ts | 发送前注入 <context_files>(来自 consumeContextFiles()) |
| src/core/agents/AgentManager.ts | agents 目录扫描路径规范化;允许文件 agent 覆盖 builtin 同名 ID |
| scripts/run-jest.js | 固定 jest 运行 cwd 为项目根目录 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| this.refreshCurrentNoteChip(); | ||
| } | ||
|
|
||
| this.state.detachFile(filePath); |
There was a problem hiding this comment.
pendingContextFiles is not updated when an attachment chip is removed. After a drag-drop attach, clicking the remove button calls state.detachFile(...) but the removed path remains in pendingContextFiles, so it can still be injected into the next prompt via consumeContextFiles() even though the UI shows it removed. Ensure detach/removal also deletes the path from pendingContextFiles (and consider updating the set on rename/delete as well so injected paths reflect current attachments).
| this.state.detachFile(filePath); | |
| this.state.detachFile(filePath); | |
| this.pendingContextFiles.delete(filePath); |
There was a problem hiding this comment.
Fixed in c94ddf5. Added this.pendingContextFiles.delete(filePath) in the onRemoveAttachment callback so that removing a chip also removes it from the pending queue, preventing it from being injected into the next prompt.
| const url = new URL(trimmed); | ||
| if (url.hostname !== 'open') return null; | ||
| const file = url.searchParams.get('file'); | ||
| return file ? decodeURIComponent(file) : null; |
There was a problem hiding this comment.
URLSearchParams.get('file') already returns a decoded string. Calling decodeURIComponent again can throw for legitimate filenames containing % (e.g. 100% done.md), causing obsidian://open links to be ignored. Return the file value directly (or only decode when it still contains percent-escapes) to avoid double-decoding.
| return file ? decodeURIComponent(file) : null; | |
| return file || null; |
There was a problem hiding this comment.
Fixed in c94ddf5. Removed the redundant decodeURIComponent call — URLSearchParams.get('file') already returns a decoded string, so double-decoding would throw for filenames containing literal % characters. Now returning file || null directly.
| display: flex; | ||
| animation: claudian-drop-pulse 0.5s ease-in-out infinite; | ||
| } | ||
|
|
There was a problem hiding this comment.
The overlay uses an infinite pulse animation when visible. Consider respecting prefers-reduced-motion by disabling the animation under @media (prefers-reduced-motion: reduce) so users who reduce motion aren't forced to see continuous animation.
| @media (prefers-reduced-motion: reduce) { | |
| .claudian-file-drop-overlay.visible { | |
| animation: none; | |
| } | |
| } |
There was a problem hiding this comment.
Fixed in c94ddf5. Added a @media (prefers-reduced-motion: reduce) block that disables the pulse animation for users who have requested reduced motion.
| if (process.platform !== 'win32') { | ||
| rawEntries = pathValue.split(':'); | ||
| } else if (/^[A-Za-z]:[\\/]/.test(pathValue)) { | ||
| // Single Windows absolute path entry (e.g., C:\custom\bin) | ||
| rawEntries = [pathValue]; | ||
| } else if (pathValue.includes(';')) { | ||
| rawEntries = pathValue |
There was a problem hiding this comment.
parsePathEntries will mis-parse a normal Windows PATH that starts with a drive letter (e.g. C:\Windows\System32;...). The /^[A-Za-z]:[\\/]/ branch treats the entire PATH string as a single entry, so subsequent ;-separated segments are never split. This can break CLI/node discovery and enhanced PATH construction on Windows. Reorder the conditions (split on ; first) or additionally require that the value does not contain ; before treating it as a single entry, and add a unit test covering a typical Windows PATH with multiple entries.
There was a problem hiding this comment.
Fixed in c94ddf5. Added !pathValue.includes(';') guard to the drive-letter branch so that a Windows PATH like C:\Windows\System32;D:\tools falls through to the semicolon-split branch instead of being treated as a single entry. Also added a unit test covering this case.
- FileContext: delete from pendingContextFiles on chip remove to prevent removed files being injected into next prompt - FileDragDropManager: remove double decodeURIComponent (URLSearchParams already decodes, double-decode throws for filenames containing %) - file-drag-drop.css: respect prefers-reduced-motion by disabling pulse animation - path.ts: fix parsePathEntries treating multi-entry Windows PATH as single entry when starting with drive letter; add unit test Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
<context_files>形式注入 promptvault.getFiles()替换低效的递归文件夹搜索Test plan
<context_files>及文件路径npm run test -- --selectProjects unit🤖 Generated with Claude Code