fix(tui): delay background work hint (#41577)
This commit is contained in:
@@ -102,11 +102,13 @@ import { useSessionTabs } from "../../context/session-tabs"
|
||||
import { createSingleFlight } from "../../util/single-flight"
|
||||
import type { SessionPending } from "@opencode-ai/schema/session-pending"
|
||||
import { generateThinkingSyntax } from "./thinking-syntax"
|
||||
import { createDelayedPresence } from "../../util/delayed-presence"
|
||||
|
||||
addDefaultParsers(parsers.parsers)
|
||||
|
||||
// Exclude temporary bottom space when measuring the real transcript height.
|
||||
const NAVIGATION_SLACK_ID = "session-navigation-slack"
|
||||
const BACKGROUND_TOOL_HINT_DELAY = 1_000
|
||||
|
||||
// Tail-first transcript mounting: rows mounted with the session, then backfill cadence.
|
||||
// The tail comfortably overfills a tall viewport; backfill drains a 200-message transcript
|
||||
@@ -1326,18 +1328,20 @@ function turnTokenToolSummary(tool: SessionMessageAssistantTool) {
|
||||
function BackgroundToolHint(props: { messages: SessionMessageInfo[] }) {
|
||||
const theme = useTheme()
|
||||
const shortcut = Keymap.useShortcut("session.background")
|
||||
const visible = createMemo(() => {
|
||||
const running = createMemo(() => {
|
||||
if (!shortcut()) return
|
||||
const current = props.messages.findLast(
|
||||
(message): message is SessionMessageAssistant => message.type === "assistant" && !message.time.completed,
|
||||
)
|
||||
return (
|
||||
current?.content.some((part) => {
|
||||
if (part.type !== "tool" || part.state.status !== "running") return false
|
||||
const display = toolDisplay(part.name)
|
||||
return display === "shell" || display === "subagent"
|
||||
}) ?? false
|
||||
)
|
||||
const part = current?.content.find((part): part is SessionMessageAssistantTool => {
|
||||
if (part.type !== "tool" || part.state.status !== "running") return false
|
||||
const name = canonicalToolName(part.name)
|
||||
return name === "shell" || name === "subagent"
|
||||
})
|
||||
if (!current || !part) return
|
||||
return `${current.id}:${part.id}`
|
||||
})
|
||||
const visible = createDelayedPresence(running, BACKGROUND_TOOL_HINT_DELAY)
|
||||
return (
|
||||
<Show when={visible() && shortcut()}>
|
||||
{(value) => (
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createEffect, createSignal, onCleanup, type Accessor } from "solid-js"
|
||||
|
||||
export function createDelayedPresence<T>(source: Accessor<T | undefined>, delay: number) {
|
||||
const [visible, setVisible] = createSignal(false)
|
||||
|
||||
createEffect(() => {
|
||||
const value = source()
|
||||
setVisible(false)
|
||||
if (value === undefined) return
|
||||
|
||||
const timer = setTimeout(() => setVisible(true), delay)
|
||||
onCleanup(() => clearTimeout(timer))
|
||||
})
|
||||
|
||||
return visible
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { expect, jest, test } from "bun:test"
|
||||
import { createRoot, createSignal } from "solid-js"
|
||||
import { createDelayedPresence } from "../../src/util/delayed-presence"
|
||||
|
||||
test("shows only after the same value remains present for the delay", async () => {
|
||||
jest.useFakeTimers()
|
||||
const scope = createRoot((dispose) => {
|
||||
const [value, setValue] = createSignal<string>()
|
||||
return { dispose, setValue, visible: createDelayedPresence(value, 1_000) }
|
||||
})
|
||||
|
||||
try {
|
||||
scope.setValue("first")
|
||||
await Promise.resolve()
|
||||
jest.advanceTimersByTime(500)
|
||||
expect(scope.visible()).toBe(false)
|
||||
|
||||
scope.setValue("second")
|
||||
await Promise.resolve()
|
||||
jest.advanceTimersByTime(999)
|
||||
expect(scope.visible()).toBe(false)
|
||||
jest.advanceTimersByTime(1)
|
||||
expect(scope.visible()).toBe(true)
|
||||
} finally {
|
||||
scope.dispose()
|
||||
jest.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
test("cancels the delay when the value disappears or the owner is disposed", async () => {
|
||||
jest.useFakeTimers()
|
||||
const scope = createRoot((dispose) => {
|
||||
const [value, setValue] = createSignal<string>()
|
||||
return { dispose, setValue, visible: createDelayedPresence(value, 1_000) }
|
||||
})
|
||||
|
||||
try {
|
||||
scope.setValue("running")
|
||||
await Promise.resolve()
|
||||
jest.advanceTimersByTime(500)
|
||||
scope.setValue(undefined)
|
||||
await Promise.resolve()
|
||||
jest.advanceTimersByTime(1_000)
|
||||
expect(scope.visible()).toBe(false)
|
||||
|
||||
scope.setValue("running")
|
||||
await Promise.resolve()
|
||||
scope.dispose()
|
||||
jest.advanceTimersByTime(1_000)
|
||||
expect(scope.visible()).toBe(false)
|
||||
} finally {
|
||||
scope.dispose()
|
||||
jest.useRealTimers()
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user