diff --git a/packages/tui/src/feature-plugins/system/diff-viewer-file-menu.tsx b/packages/tui/src/feature-plugins/system/diff-viewer-file-menu.tsx
new file mode 100644
index 00000000000..710f7696bcd
--- /dev/null
+++ b/packages/tui/src/feature-plugins/system/diff-viewer-file-menu.tsx
@@ -0,0 +1,87 @@
+/** @jsxImportSource @opentui/solid */
+import type { Plugin } from "@opencode-ai/plugin/tui"
+import { BoxRenderable, MouseButton } from "@opentui/core"
+import { Portal, useTerminalDimensions } from "@opentui/solid"
+import { createSignal, onCleanup } from "solid-js"
+
+export function DiffFileMenu(props: {
+ context: Plugin.Context
+ state: { fileIndex: number; x: number; y: number }
+ reviewed: boolean
+ onToggle: () => void
+ onClose: () => void
+}) {
+ const dimensions = useTerminalDimensions()
+ const theme = props.context.theme.contextual.overlay
+ const [hovered, setHovered] = createSignal(false)
+ const label = () => (props.reviewed ? "Mark incomplete" : "Mark complete")
+ const width = () => Math.min(19, dimensions().width)
+ const run = () => {
+ props.onClose()
+ props.onToggle()
+ }
+ onCleanup(props.context.keymap.mode.push("menu"))
+ props.context.keymap.layer(() => ({
+ mode: "menu",
+ commands: [
+ { bind: "escape,ctrl+c", title: "Close file menu", group: "Diff", run: props.onClose },
+ { bind: "return", title: label(), group: "Diff", run },
+ ],
+ }))
+
+ return (
+ {
+ if (!(container instanceof BoxRenderable)) return
+ // Portal's wrapper must also escape root flow, not follow the full-height app.
+ container.position = "absolute"
+ container.left = 0
+ container.top = 0
+ container.zIndex = 2600
+ }}
+ >
+
+
+ )
+}
diff --git a/packages/tui/src/feature-plugins/system/diff-viewer.tsx b/packages/tui/src/feature-plugins/system/diff-viewer.tsx
index 3bb6aabd235..a5e7d5fe9e7 100644
--- a/packages/tui/src/feature-plugins/system/diff-viewer.tsx
+++ b/packages/tui/src/feature-plugins/system/diff-viewer.tsx
@@ -14,6 +14,7 @@ import { filetype } from "../../util/filetype"
import { useRenderer, useTerminalDimensions } from "@opentui/solid"
import { createEffect, createMemo, createResource, createSignal, For, Match, onCleanup, Show, Switch } from "solid-js"
import { DiffViewerFileTree } from "./diff-viewer-file-tree"
+import { DiffFileMenu } from "./diff-viewer-file-menu"
import { DiffViewerImage, isDiffImageFile } from "./diff-viewer-image"
import { DialogSelect } from "../../ui/dialog-select"
import { EmptyBorder } from "../../ui/border"
@@ -1076,76 +1077,6 @@ export function DiffViewerContent(props: {
)
}
-function DiffFileMenu(props: {
- context: Plugin.Context
- state: FileMenuState
- reviewed: boolean
- onToggle: () => void
- onClose: () => void
-}) {
- const dimensions = useTerminalDimensions()
- const theme = props.context.theme.contextual.overlay
- const [hovered, setHovered] = createSignal(false)
- const label = () => (props.reviewed ? "Mark incomplete" : "Mark complete")
- const run = () => {
- props.onClose()
- props.onToggle()
- }
- onCleanup(props.context.keymap.mode.push("menu"))
- props.context.keymap.layer(() => ({
- mode: "menu",
- commands: [
- { bind: "escape,ctrl+c", title: "Close file menu", group: "Diff", run: props.onClose },
- { bind: "return", title: label(), group: "Diff", run },
- ],
- }))
-
- return (
-
- )
-}
-
function DiffViewerHelpDialog(props: { context: Plugin.Context; single: boolean }) {
const dimensions = useTerminalDimensions()
const theme = props.context.theme.contextual.elevated