Skip to content

桌面端增强本地文件链接打开体验 - #7985

Open
PMKang wants to merge 13 commits into
esengine:main-v2from
PMKang:feat/local-path-context-menu
Open

桌面端增强本地文件链接打开体验#7985
PMKang wants to merge 13 commits into
esengine:main-v2from
PMKang:feat/local-path-context-menu

Conversation

@PMKang

@PMKang PMKang commented Aug 8, 2026

Copy link
Copy Markdown

Documentation-impact: none - 本次改动仅涉及桌面端本地文件链接交互和安全处理,现有 docs/*.md 未描述该实现细节,因此现有文档仍然正确。

背景

Reasonix 对 DeepSeek 模型适配很好,响应速度快、使用成本低,已经能够承接不少原本在 Codex 中完成的日常工作流。实际使用中,我经常需要查看 Agent 输出中的 Markdown、HTML、文本和其他本地文档。

目前 Reasonix 可以渲染本地文件路径,但缺少 Codex 中那种直接点击打开、右键选择打开方式和复制路径的体验。这个小功能会让文档检查可以留在 Agent 界面内完成,减少为了查看一个文件而单独切换到 VS Code 或 Finder 的操作。

改动内容

  • 本地文件链接左键使用系统默认应用打开。
  • 右键提供默认应用打开、使用检测到的外部应用打开、在 Finder 中显示、复制路径和另存为。
  • 过滤重复的文件管理器打开器,避免同时出现 Finder 和“在文件管理器中显示”。
  • 修复 macOS file:///Users/... 路径解析时丢失开头 / 的问题,同时保留 Windows 驱动器路径和 UNC 路径兼容性。
  • 复用现有路径安全校验,拒绝直接打开可执行目标。
  • 新增前端回归测试,覆盖本地文件点击、右键菜单、应用选择和 Unix 绝对路径解析。

验证

  • TypeScript 类型检查通过。
  • 本地路径测试:52 项通过。
  • 点击与右键菜单测试:13 项通过。
  • Wails v2.12.0 源码开发桌面端已实际启动并验证。
  • 已在 macOS 实机完成启动和交互验证;目前没有 Windows 实机,Windows 行为未做人工验证,Windows CI 仅作为自动编译检查。

说明

本次不加入“复制文件内容”,避免对二进制文件、敏感文件和大文件引入额外行为;保留“复制路径”和“另存为”作为通用操作。

@github-actions github-actions Bot added v2 Go rewrite (1.x) — main-v2 branch, active development desktop Wails desktop app (desktop/**) labels Aug 8, 2026
@SivanCola

Copy link
Copy Markdown
Collaborator

我来看下

PMKang and others added 9 commits August 9, 2026 00:43
Problem: Save As could truncate the source through a case-insensitive, symlink, or hard-link alias, and Linux/Windows/macOS terminal launchers could receive a regular file as their working directory.

Root cause: SaveLocalPathAs compared only cleaned path strings, while platform launchers reused the requested path for both the file argument and process working directory.

Fix: compare filesystem identity before opening the destination, centralize parent-directory selection for file launches, preserve the original file argument for editors, and add alias and regular-file regression coverage.

Verification: go test ./..., go test -race ./..., go vet ./..., Windows CGO-disabled test-binary cross-compile, frontend typecheck, lint, focused local-path tests, and production build.

Co-authored-by: SivanCola <32437197+SivanCola@users.noreply.github.com>
Problem: local file links missed authority-form UNC URLs, stale opener discovery could overwrite newer menu state, and Save As could truncate a source alias during a check-then-open sequence.

Root cause: URL handling assumed file:/// paths, async opener results had no request ordering, and Save As opened the destination with O_TRUNC after a separate source identity check.

Fix: parse canonical and slash-form UNC file URLs through the standard URL API, validate local links at the markdown sanitizer boundary, ignore stale or post-unmount opener responses, and copy Save As data to a synced temporary file before atomic replacement with platform-specific handling.

Verification: local-path tests 57/57; click/context-menu E2E 17/17; pnpm typecheck; frontend lint and CSS checks; focused Go tests; go test -race ./...; go vet ./...; GOOS=windows CGO_ENABLED=0 go test -c; full frontend suite 120 suites passed.

Co-authored-by: SivanCola <32437197+SivanCola@users.noreply.github.com>
Problem: plain-text authority-form UNC file URLs were inert, and exported Markdown dropped their href because allowlists only recognized file:/// URLs.

Root cause: local-path linkification used a file:/// only matcher, while Markdown rendering, click handling, and export each carried separate file URL validation rules.

Fix: centralize strict file URL parsing with the standard URL API, recognize canonical and slash-form UNC URLs, reject malformed candidates before linkification, preserve valid file URLs when generating anchors, and reuse the validator for Markdown and export sanitizers.

Verification: local-path tests 61/61; click/context-menu E2E 17/17; session export tests; pnpm test (120 suites); pnpm typecheck; frontend hooks lint and CSS checks; frontend production build and bundle budgets; cd desktop && go test ./...; go test -race ./...; go vet ./... .

Co-authored-by: PM-Kang <liangkang90@126.com>

Co-authored-by: SivanCola <32437197+SivanCola@users.noreply.github.com>
Problem: plain text containing another URI such as profile://nas/share/report.md could be split at the embedded file:// substring and rendered as a clickable local-path link.

Root cause: FILE_RE matched file URLs without checking the character immediately before the candidate.

Fix: apply a URI-token prefix boundary to file URL candidates and add regressions for custom and nested URI forms.

Verification:
- desktop/frontend/src/__tests__/local-path-links.test.tsx passed (63/63)
- pnpm test passed (120 suites)
- pnpm typecheck, hooks lint, CSS checks, production build, and bundle budgets passed
- cd desktop && go test ./..., go test -race ./..., go vet ./... passed
- Windows desktop packages cross-compiled with CGO_ENABLED=0

Co-authored-by: PM-Kang <liangkang90@126.com>
Co-authored-by: SivanCola <32437197+SivanCola@users.noreply.github.com>
Problem: PR esengine#7985 adds bridge bindings and the latest main-v2 ratchet rejects the merged bridge.ts size.

Root cause: the new bindings and mock bridge methods were left on separate lines, exceeding the existing file-size baseline after main-v2 advanced.

Fix: compact only the two PR-owned bridge hunks without changing runtime behavior or local browser mock scenarios.

Verification: staged-tree go run ./tools/repolint; git diff --check.

Co-authored-by: SivanCola <32437197+SivanCola@users.noreply.github.com>
Problem: main-v2 advanced with a Markdown Worker/history-rendering refactor, leaving PR esengine#7985 conflicting in MarkdownRenderer and allowing the shared pipeline to recognize only file:/// links.

Root cause: local-file sanitization had been wired into the pre-refactor renderer, while the new shared markdown pipeline kept a separate file:/// allowlist.

Fix: merge the latest main-v2 changes, retain the shared renderer architecture, route both streaming and worker HAST URL transforms through isLocalFileHref, and add an authority-form UNC regression. Preserve the cross-platform opener, context-menu, Save As, and path-boundary fixes.

Verification: pnpm test (132 suites); local-path-links (63); local-path-click-e2e (17); markdown-pipeline (60); pnpm typecheck; test:typecheck; lint:hooks; check:css; production build and bundle budgets; cd desktop && go test ./..., go test -race ./..., go vet ./...; GOOS=windows CGO_ENABLED=0 go test -c; go run ./tools/repolint.

Co-authored-by: PM-Kang <liangkang90@126.com>

Co-authored-by: SivanCola <32437197+SivanCola@users.noreply.github.com>
Problem: Right-button auxclick opened links alongside the context menu, StrictMode discarded opener discovery, active local targets and unsafe Windows path forms could reach OS openers, and a failed Windows Save As replacement could remove the existing destination.

Root cause: Link handlers accepted every auxiliary button, the mount guard was not reset during effect replay, validation relied on frontend-normalized paths and suffixes alone, and Windows replacement used a delete-then-rename fallback.

Fix: Restrict auxiliary opens to the middle button, restore StrictMode discovery, validate file URLs and Windows device/stream syntax at frontend and native boundaries, reject executable modes, application bundles and executable symlink targets, and use atomic MoveFileEx replacement on Windows.

Verification: pnpm test (132 suites); local-path-links (81); local-path-click-e2e (20); markdown-pipeline (65); session-export; pnpm build and bundle budgets; pnpm test:typecheck; go run ./tools/repolint; root and desktop go test ./...; desktop go test -race ./...; root and desktop go vet ./...; GOOS=windows CGO_ENABLED=0 go test -c; clean synthetic merge with origin/main-v2@9b0fdc5df.

Co-authored-by: SivanCola <32437197+SivanCola@users.noreply.github.com>
Problem: Desktop CI rejected the local path validation loop under golangci-lint's stringsseq rule.

Root cause: The loop materialized a temporary slice with strings.Split even though it only iterated once.

Fix: Iterate over strings.SplitSeq directly without changing validation behavior.

Verification: golangci-lint run --timeout=5m; go test ./... (desktop); focused local path tests; gofmt -d.

Co-authored-by: SivanCola <32437197+SivanCola@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

desktop Wails desktop app (desktop/**) v2 Go rewrite (1.x) — main-v2 branch, active development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants