Skip to content

Feat/file tree row menu - #648

Merged
xintaofei merged 7 commits into
xintaofei:mainfrom
mrytsr:feat/file-tree-row-menu
Sep 4, 2026
Merged

Feat/file tree row menu#648
xintaofei merged 7 commits into
xintaofei:mainfrom
mrytsr:feat/file-tree-row-menu

Conversation

@mrytsr

@mrytsr mrytsr commented Sep 4, 2026

Copy link
Copy Markdown

背景

文件树在桌面端可以右键打开菜单,触屏端之前依赖长按。长按接下来要留给拖拽用,所以触屏端需要另一条入口。

改动

每个文件/文件夹/工作区根目录行的右侧加一个横向三点的 RowMoreButton。点击时阻止冒泡,向最近的 [data-tree-row-path] 祖先派发一个合成的 contextmenu MouseEvent。Radix 已有的 ContextMenuTriggerasChild 包裹整行)收到这个事件就打开和右键、长按完全同一个菜单——零菜单项重复。

  • FileTreeFile / FileTreeFolder 新增 actions?: ReactNode,渲染在已有的 FileTreeActions 包裹里(自动 stopPropagation)。
  • RowMoreButton(新组件 + 3 个单测)处理按钮的渲染和派发逻辑。
  • Folder.fileTreeTab.moreActions 文案 10 个语言全量新增。
  • useLongPressToOpenMenu 保留(测试一起恢复)备用,文件树不再依赖它。

验证

  • eslint . → exit 0
  • vitest run → 5786 个测试通过(406 个文件;比之前 +13:恢复的 10 个 hook 测试 + 新增的 3 个 RowMoreButton 测试)
  • next build → 成功

Your Name and others added 6 commits September 4, 2026 01:58
The three ContextMenuTrigger wrappers around FileTreeFile,
FileTreeFolder, and the RootDropFolder root-drop menu rendered as bare
<span> elements. HTML parser rules disallow <span> from containing <div>
children, so the row div ended up as the span's sibling and Radix's
pointerdown / contextmenu handlers — and its WebkitTouchCallout: none
style — never reached the actual row.

Effect on touch:
* iOS Safari showed its native long-press callout (Copy / Share / Save
  Image), eating the gesture before Radix's 700ms long-press timer could
  open the menu.
* Android Chrome and desktop touchscreens likewise had no path to open
  the per-row menu.

Desktop right-click worked because onContextMenu propagates by capture
and bubble independently of the parent-child DOM relationship.

Fix: pass asChild to each of the three ContextMenuTrigger instances so
Radix's Slot mechanism merges the trigger props onto the row's own div.
This matches every other ContextMenuTrigger usage in the codebase
(tabs/tab-item, automations-page, settings/skills-settings,
settings/mcp-settings, conversations/sidebar-conversation-list, etc.).
Bring the mobile virtual key bar for the terminal into main.
Touch devices don't get a free way to open the file-tree context menu now
that the long-press gesture is reserved for drag. Add a small horizontal
three-dots button (MoreHorizontal) on the right of every file, folder,
and workspace-root row. Clicking it dispatches a synthetic contextmenu
MouseEvent on the row so Radix's existing ContextMenuTrigger opens the
same menu the right-click and long-press paths open — no menu items
duplicated.

- FileTreeFile / FileTreeFolder gain an optional actions?: ReactNode prop
  rendered inside the existing FileTreeActions wrapper (which already
  stops click bubbling).
- RowMoreButton (new component) walks up to the nearest
  [data-tree-row-path] ancestor and dispatches the contextmenu event.
- Folder.fileTreeTab.moreActions added to all 10 locales.
- useLongPressToOpenMenu is kept (and its tests restored) for reuse
  elsewhere; the file tree now relies on the more button + native
  right-click instead of the long-press hook.
Four defects in the row-menu button, three of them regressions the PR
introduces.

The workspace-root row lost its context menu entirely. `asChild` was
added to a `ContextMenuTrigger` whose child is a `DesktopDropDirContext.
Provider`: Radix's Slot clones the child ELEMENT and hands it the
trigger's props, and a Context.Provider drops every prop it doesn't know
— so the trigger rendered NO element at all, and right-click, long-press
and the new ⋯ all did nothing on the root row. `RootDropFolder` swallowed
its props too. The provider now sits outside the menu and
`RootDropFolder` spreads `...props` onto the row.

The folder ⋯ was a `<button>` inside the header `<button>`. HTML forbids
that and React reports it as a hydration error on every render; the
header's accessible name also absorbed the action's label, so the row
announced as "src More actions". The action is now a sibling overlaid on
the header, inside a wrapper that owns the row highlight — hovering a
sibling would otherwise switch the tint off, since :hover only propagates
to ancestors. The header keeps spanning the whole row so the folder's
drop area and its `data-tree-drop-dir` hit-test marker are unchanged, and
a reserved right padding keeps the longest name out from under the ⋯.

The aux tree is a roving-focus widget: the container is its single tab
stop and owns the arrow keys. A native button per row put every row back
in the tab sequence, and `FileTreeActions` swallows keydown, so the arrow
keys died wherever focus landed. The button now reads the tree's mode and
drops out of the tab order there.

The menu was anchored at the click's clientX/clientY, which a keyboard or
programmatic activation reports as 0 — parking the menu in the viewport's
top-left corner. It now anchors at the button's own box, like the sidebar
conversation row's ⋯ that this pattern comes from.

Also adopted from that prior art: hidden at rest on pointer devices
(right-click is the primary affordance and one ⋯ per row is a lot of
ink), pinned visible where there is no hover, `aria-haspopup` and a
title. Dropped the unused `i18nNamespace` prop, whose other allowed value
named a key that does not exist.

The tests now fire at a real Radix trigger around a real row instead of a
bare div — the old ones could not tell a working button from one whose
trigger never reached the DOM.
…ested long-press

Two defects a Codex review found in the previous commit and in PR xintaofei#648.

The folder's drop zone stopped at the action. Once the ⋯ became a sibling
of the header, `data-tree-drop-dir` and the drag handlers were still on
the header, and `resolveFileTreeDropZone` walks UP from whatever the
pointer hit — so dragging an entry over the ⋯ strip of a destination
folder found no zone on desktop, and on the web the dragover/drop
handlers never fired there either. The move just silently did nothing.
When a row has actions the wrapper IS the row now: it carries the drop
marker, `rowProps`, and the highlight, and the header keeps only what
belongs to the header. That also lets the drag source's `opacity-70` dim
the action along with the rest of the row. `rowProps` is typed against
HTMLElement because the row element is a button or a div depending on
whether the row has actions.

Long-press on a nested row opened the wrong menu. A folder's trigger
encloses its expanded descendants' triggers, and PR xintaofei#648 spread
`useLongPressToOpenMenu` onto every one of them — so a single touch
pointerdown armed a timer per ancestor, and each fired its own
contextmenu from its OWN element. Long-pressing a file two levels deep
opened three menus, the outermost winning the screen. Radix's built-in
long-press survives nesting because all the triggers share one bubbling
event that the innermost preventDefaults; separate dispatches carry no
such interlock. The hook is unwired — which is what the PR description
said the tree does anyway — leaving the ⋯ button as the touch entry
point and Radix's own long-press underneath it. The hook and its tests
stay, now carrying the nesting warning.

Also corrected the asChild rationale in the two row comments: React
builds this DOM with appendChild, so the old wrapper span really did
contain the row div — the reason to merge onto the row is the inline
span's effect on the row's w-max/min-w-full sizing, not a parser split.

Geometry verified against the built CSS in headless Chrome: the drop
marker resolves from the ⋯ for both folder rows and spans the full
525.4px row; the ⋯ aligns at the same x on file and folder rows and
never overlaps a name, including the longest one.
… its action

Moving `rowProps` to the wrapper also took `min-w-full` off the header
button, so an action-bearing folder row shrank its trigger to the label:
clicking anywhere between the name and the ⋯ hit the wrapper, which owns
no click handler and cannot bubble into its sibling button, and the
folder neither selected nor expanded.

`grow` restores the full-width target. Not `flex-1` — its zero basis
would drop the label out of the row's intrinsic width, and this tree
sizes itself to its longest row and scrolls horizontally.

Measured in a browser against the built CSS: the header is 493px of a
525px row (the remaining 32px is the action strip), and the tree's total
content width is unchanged at 537px.
@xintaofei

Copy link
Copy Markdown
Owner

codeg work task 155 is done — #648 (7 files, +613/-304).

Clears the PR conflict GitHub reported. main has since taken PR xintaofei#647, so
the terminal key bar this branch was carrying its own copy of arrives
from main instead and drops out of the PR diff entirely — what is left is
just the file-tree row menu.

Nothing needed a hand-resolution: git merges all 67 files cleanly (the
row-menu additions to aux-panel-file-tree-tab-source.test.ts sit beside
the markdown-preview assertions main moved into their own describe).

Merged tree verified: eslint 0, tsc 0, 409 test files / 5892 tests, next
build clean. src-tauri is byte-identical to main.
@xintaofei
xintaofei merged commit 1f86f62 into xintaofei:main Sep 4, 2026
1 of 7 checks passed
xintaofei added a commit that referenced this pull request Sep 5, 2026
Talking to an agent while it is still replying now works on every session that has a delivery channel — with attachments, shown as a real message in the transcript, and recoverable if the agent finished without reading it.
Mobile gets a terminal key bar and a ⋯ menu on every file-tree row.

## New

- **Send a message into a running turn on any session with a delivery channel** — Codex, Grok, Gemini and OpenCode sessions get the mid-turn send too, worded honestly per channel: Claude inserts into the current turn, the others leave a note the agent picks up at its next check, shown as waiting until it actually does. (#637, @Adam-Dalloul)
- **A message sent mid-turn now reads as a message** — it lands in the transcript as its own turn, and the reply to it starts a new turn instead of running into the previous answer mid-paragraph. (#636, @Adam-Dalloul)
- **Mid-turn sends carry images and file references**, instead of going dark the moment you stage an attachment. An image shows on the message right away in the running turn rather than only after reopening the conversation, and if the turn ends first the whole draft — attachment included — is queued for the next one rather than dropped. (#640, @Adam-Dalloul)
- **A note the agent finished without reading is no longer lost** — the list stays up after the turn, says the agent never got to it, and offers to send it as an ordinary message or dismiss it.
- **"Adapter version" is now a per-agent setting** — follow the newest adapter release instead of waiting for the reviewed pin, which is what makes a brand-new model reachable the day it ships. Pinned stays the default and recommended; the newest release is unreviewed and can break the agent, and an install that can't fetch it falls back to the pinned version rather than failing. (#635, @Adam-Dalloul)
- **A virtual key bar for the terminal on mobile** — ESC, TAB, CTRL, ALT, arrows, Home/End and PgUp/PgDn, with CTRL/ALT latches that wrap the next key you type, lifted clear of the soft keyboard and collapsible from the tab bar. (#647, @mrytsr)
- **Every file-tree row gets a ⋯ button** that opens the same menu right-click does, so touch devices have a way in now that long-press is reserved for drag. (#648, @mrytsr)
- **Transcript file links open beside the conversation when the workspace column is off screen** — on the task board and the infinite canvas, a file badge, a markdown link or "view diff" now opens a read-only viewer next to the transcript, with "Open in workspace" one click away, instead of doing nothing visible.
- **Codex reports its background terminals** (codex-acp 1.10.0) — a long-running command Codex pushes to the background is badged "Background" and joins the live task strip with its Stop button and output link, the way Claude's already did.
- **"Disable hardware acceleration" now works on Linux**, not just Windows.

## Improved

- **Updated bundled agents:** Kimi Code 0.41.0, CodeBuddy 2.144.0, Qoder 1.1.44, Cursor 2026.09.02, Antigravity 1.1.1, OpenCode 1.18.29, plus the Claude and Codex adapters (claude-agent-acp 0.74.0, codex-acp 1.10.0).
- **Antigravity now takes a custom version** in Agent Settings, like the other downloaded agents — and a version that was never published now fails the download instead of quietly filing the pinned build under the number you asked for.
- **Signing out of Claude mid-session now ends only that turn** — the session stays open and asks to be signed in again, instead of the connection dropping and the conversation flipping to cancelled. A delegated sub-agent in the same state reports "needs sign-in" rather than an unrecognized failure.

## Fixed

- **A Codex search that finds nothing now says "No matches"** instead of a red failed tool call. (#651, reported by @rubybrowncoat; fixed by @dawNotPoi in #652)
- **Cleaning up a task with no changes now removes its worktree completely**, instead of half-removing it and then reporting it still holds uncommitted files so the retry never finished. (#642, reported by @rubybrowncoat; fixed by @dawNotPoi in #650)
- **Worktree removal now acts on exactly the checkout you pointed at.** Git matches a worktree argument by path suffix before resolving it as a path, so a same-named checkout belonging to another project could be deleted, uncommitted files included; a path that resolves two ways is now refused rather than guessed at.
- **A `/goal` turn no longer appears twice** — a slash command is now matched against what codeg actually sent, so its turn stops coming back as a background overlay beside itself. (#643, @Adam-Dalloul)
- **"Fork from here" no longer silently forks the whole reply** when you point at the half before a mid-turn message — the button greys out with an explanation until the fork point is named, the way it already does during a running turn.
- **Turning "Disable hardware acceleration" back off now takes effect** after the restart, instead of the setting being inherited by the new process and staying on.

Thanks to @Adam-Dalloul, @mrytsr and @dawNotPoi for contributing to this release, and to @rubybrowncoat for the reports.

-----------------------------

# 发布版本 0.30.3

智能体正在回复时插话,现在在每一个有投递通道的会话上都能用了——能带图片和文件、在会话记录里就是一条正经消息,智能体没来得及读完就收尾的,也还能捞回来。
移动端还多了终端虚拟按键条,文件树每一行也多了 ⋯ 菜单。

## 新增

- **只要会话有投递通道,就能往正在跑的这一轮里发消息**——Codex、Grok、Gemini、OpenCode 也都有了,而且措辞按通道如实区分:Claude 是直接插进当前轮次,其余的是留一张便条、等智能体下次查看时取走,在它真的读到之前一直显示为等待中。(#637@Adam-Dalloul)
- **中途发出的消息现在就是一条消息**——它会作为独立的一轮出现在会话记录里,针对它的回答也会另起一轮,不再和上一段答案粘在同一个气泡里连成一片。(#636@Adam-Dalloul)
- **中途发送支持图片和文件引用**,不再一贴附件按钮就变灰。图片当场就显示在正在进行的这一轮里,不用关掉会话再打开才看得到;万一那一轮刚好结束了,整条草稿(连同附件)会排进下一轮,而不是被丢掉。(#640@Adam-Dalloul)
- **智能体没读就收尾的便条不会再消失**——列表在这一轮结束后仍然保留,写明智能体没来得及读,并提供「作为消息发送」或直接忽略。
- **「适配器版本」成为每个智能体各自的设置项**——可以选择跟随最新的适配器发布,不必等官方钉住的版本更新,新模型上线当天就能用上。默认仍是钉住的版本并标为推荐;最新版未经审核、可能让智能体不可用,安装时若取不到最新版会自动回退到钉住的版本,而不是直接装不上。(#635@Adam-Dalloul)
- **移动端终端虚拟按键条**——ESC、TAB、CTRL、ALT、方向键、Home/End、PgUp/PgDn,CTRL/ALT 按下后会锁定并作用于你输入的下一个字符,按键条会浮在软键盘上方,也可以从标签栏收起。(#647@mrytsr)
- **文件树每一行都多了 ⋯ 按钮**,点开就是右键那份菜单——长按手势已留给拖拽,触屏这下有了入口。(#648@mrytsr)
- **工作区被挡住时,会话里的文件链接改在旁边打开**——在任务看板和无限画布这类整页视图下,点文件徽章、Markdown 链接或「查看差异」会在会话旁弹出只读预览(「在工作区中打开」也只有一步之遥),不再点了没反应。
- **Codex 的后台终端能报上来了**(随 codex-acp 1.10.0 到位)——被 Codex 挂到后台的长命令会标上「Background」,并进入顶部的实时任务条,带停止按钮和输出链接,和 Claude 的一样。
- **「禁用硬件加速」在 Linux 上也生效了**,不再只支持 Windows。

## 改进

- **内置智能体版本更新:** Kimi Code 0.41.0、CodeBuddy 2.144.0、Qoder 1.1.44、Cursor 2026.09.02、Antigravity 1.1.1、OpenCode 1.18.29,以及 Claude 和 Codex 的适配器(claude-agent-acp 0.74.0、codex-acp 1.10.0)。
- **Antigravity 现在也能指定自定义版本了**,和其他下载安装的智能体一样;填了一个从未发布过的版本号会直接下载失败,而不是把钉住的那份安装包悄悄记成你填的号。
- **会话进行中登出 Claude 现在只结束当前这一轮**——会话本身保留,只是提示需要重新登录,不再整条连接断开、会话被标成已取消。委托出去的子智能体遇到同样情况会报「需要登录」,而不是一句看不懂的异常结束。

## 修复

- **Codex 搜索没有命中时显示「无匹配结果」**,不再是一张红色的失败工具卡。(#651,由 @rubybrowncoat 反馈;@dawNotPoi#652 中修复)
- **清理没有改动的任务时,工作树现在能被完整删除**,不再删一半、然后反过来说它还有未提交的改动,导致重试永远走不完。(#642,由 @rubybrowncoat 反馈;@dawNotPoi#650 中修复)
- **删除工作树只会作用于你指定的那一个。** git 会先按路径后缀去匹配工作树参数、再当作路径解析,因此另一个项目下同名的 checkout 有可能被连同未提交文件一起删掉;现在遇到会有两种解释的路径会直接拒绝,而不是替你猜。
- **`/goal` 之类的斜杠命令不再出现两遍**——斜杠命令现在会和 codeg 实际发出的内容比对,那一轮不会再以「后台活动」的形式在自己旁边重复一次。(#643@Adam-Dalloul)
- **「从这里分叉」不再悄悄把整条回复都分出去**——当你点的是中途插话之前的那半段时,按钮会像回复生成中那样变灰并说明原因,直到分叉点被确定。
- **「禁用硬件加速」关掉后重启真的会生效**,不再被新进程继承下来、看起来像是没关掉。

感谢 @Adam-Dalloul@mrytsr@dawNotPoi 为本次发布做出的贡献,也感谢 @rubybrowncoat 的问题反馈。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants