mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 19:46:16 +03:00
feat(app): add experimental turn navigator setting (#57522)
This commit is contained in:
committed by
GitHub
Unverified
parent
356f739f38
commit
5761fd1a79
@@ -1,6 +1,6 @@
|
||||
import { expect, test, type Page } from "@playwright/test"
|
||||
import type { ConfigEntry, OpenCodeEvent, SessionMessageInfo, WorktreeDirectory } from "@opencode/client/promise"
|
||||
import { NO_PROVIDER, REMOTE_SERVER, SERVER, holdRoute, project, seed, session } from "../utils/app"
|
||||
import { NO_PROVIDER, REMOTE_SERVER, SERVER, holdRoute, pageMessagesFrom, project, seed, session } from "../utils/app"
|
||||
import { mockMissingLocation, mockOpenCodeServer } from "../utils/mock-server"
|
||||
import { mockRemoteServer, mockWorkspace, openSession, openSettings, type WorkspaceInput } from "../utils/workspace"
|
||||
|
||||
@@ -94,6 +94,62 @@ test("a settings page survives refresh", async ({ page }) => {
|
||||
await expect(page).toHaveURL("/settings?tab=appearance")
|
||||
})
|
||||
|
||||
test("the experimental turn navigator defaults on and can be disabled persistently", async ({ page }) => {
|
||||
const id = "ses_navigator_settings"
|
||||
const messages: SessionMessageInfo[] = Array.from({ length: 60 }, (_, index) => ({
|
||||
id: `msg_navigator_${String(index).padStart(4, "0")}`,
|
||||
type: "user",
|
||||
text: `Prompt ${index}`,
|
||||
time: { created: 1700000000000 + index },
|
||||
}))
|
||||
const indexes: string[] = []
|
||||
page.on("request", (request) => {
|
||||
const url = new URL(request.url())
|
||||
|
||||
if (url.pathname === `/api/session/${id}/message` && url.searchParams.get("type") === "user")
|
||||
indexes.push(request.url())
|
||||
})
|
||||
await openSession(page, {
|
||||
name: "Turn navigator settings",
|
||||
sessions: [{ id, title: "Turn navigator settings" }],
|
||||
pageMessages: pageMessagesFrom({ [id]: messages }),
|
||||
})
|
||||
const navigator = page.locator('[data-component="session-turn-navigator"]')
|
||||
await expect(navigator.locator("[data-turn]")).toHaveCount(60)
|
||||
expect(indexes).toHaveLength(1)
|
||||
|
||||
await page.keyboard.press("Control+,")
|
||||
const settings = page.getByTestId("settings-screen")
|
||||
await settings.getByRole("tab", { name: "Experimental", exact: true }).click()
|
||||
await expect(settings.getByRole("heading", { name: "Experimental", exact: true })).toBeVisible()
|
||||
const toggle = settings.getByRole("switch", { name: "Turn navigator", exact: true })
|
||||
await expect(toggle).toBeChecked()
|
||||
await toggle.press("Space")
|
||||
await expect(toggle).not.toBeChecked()
|
||||
await settings.getByRole("button", { name: "Back to app", exact: true }).click()
|
||||
await expect(navigator).toHaveCount(0)
|
||||
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => JSON.parse(localStorage.getItem("settings.v3") ?? "{}").general?.showTurnNavigator))
|
||||
.toBe(false)
|
||||
await page.clock.install()
|
||||
await page.reload()
|
||||
await expect(page.locator('[data-component="composer-editor"]')).toBeEditable()
|
||||
await expect(page.locator("#message-msg_navigator_0059")).toBeVisible()
|
||||
await page.clock.fastForward(1500)
|
||||
await expect(navigator).toHaveCount(0)
|
||||
expect(indexes).toHaveLength(1)
|
||||
|
||||
await page.keyboard.press("Control+,")
|
||||
await settings.getByRole("tab", { name: "Experimental", exact: true }).click()
|
||||
await expect(toggle).not.toBeChecked()
|
||||
await toggle.press("Space")
|
||||
await expect(toggle).toBeChecked()
|
||||
await settings.getByRole("button", { name: "Back to app", exact: true }).click()
|
||||
await expect(navigator.locator("[data-turn]")).toHaveCount(60)
|
||||
expect(indexes).toHaveLength(2)
|
||||
})
|
||||
|
||||
for (const locale of ["en", "ar"]) {
|
||||
test(`background activity alignment stays physical and persists (${locale})`, async ({ page }) => {
|
||||
const id = "ses_activity_settings"
|
||||
|
||||
@@ -753,6 +753,9 @@ export const dict = {
|
||||
"settings.experimental.description": "Try experimental features",
|
||||
"settings.experimental.row.generatedUI.title": "Generated UI",
|
||||
"settings.experimental.row.generatedUI.description": "Let agents show dashboards, charts and forms in the timeline",
|
||||
"settings.experimental.row.turnNavigator.title": "Turn navigator",
|
||||
"settings.experimental.row.turnNavigator.description":
|
||||
"Show a rail beside the timeline to preview and jump between prompts",
|
||||
"settings.guiExtensions.title": "Extensions",
|
||||
"settings.guiExtensions.description": "Built-in GUI extensions in this window. Development builds only.",
|
||||
"settings.guiExtensions.reload": "Reload",
|
||||
|
||||
@@ -27,6 +27,7 @@ import { parseCommentNote, readPromptPresentation } from "@/composer/comment-not
|
||||
import { parseVoiceDelegation } from "./voice-delegation"
|
||||
import { useCommand } from "@/shell/commands/command"
|
||||
import { useExtensionScoped } from "@/runtime/extension/commands"
|
||||
import { useSettings } from "@/settings/model"
|
||||
import {
|
||||
SessionAncestorTrail,
|
||||
SessionHeaderLayout,
|
||||
@@ -132,6 +133,7 @@ function MessageTimelineView(
|
||||
const language = useLanguage()
|
||||
const command = useCommand()
|
||||
const extensionScoped = useExtensionScoped()
|
||||
const settings = useSettings()
|
||||
const server = useServer()
|
||||
const data = server.ctx.data
|
||||
const sdk = useWorkspaceLocation()
|
||||
@@ -464,7 +466,7 @@ function MessageTimelineView(
|
||||
return (
|
||||
<VirtualizedTimeline
|
||||
navigator={
|
||||
<Show when={props.onRevealTurn && sessionID()} keyed>
|
||||
<Show when={settings.general.showTurnNavigator() && props.onRevealTurn && sessionID()} keyed>
|
||||
{(id) => (
|
||||
<TurnNavigator
|
||||
sessionID={id}
|
||||
|
||||
@@ -39,6 +39,20 @@ export const SettingsExperimental: Component = () => {
|
||||
/>
|
||||
</div>
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
title={language.t("settings.experimental.row.turnNavigator.title")}
|
||||
description={language.t("settings.experimental.row.turnNavigator.description")}
|
||||
>
|
||||
<div data-action="settings-turn-navigator">
|
||||
<Switch
|
||||
checked={settings.general.showTurnNavigator()}
|
||||
onChange={(checked) => settings.general.setShowTurnNavigator(checked)}
|
||||
hideLabel
|
||||
>
|
||||
{language.t("settings.experimental.row.turnNavigator.title")}
|
||||
</Switch>
|
||||
</div>
|
||||
</SettingsRow>
|
||||
</SettingsList>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -94,6 +94,7 @@ describe("settings schema", () => {
|
||||
timelineDetail: timelinePresets[2].value,
|
||||
showCustomAgents: false,
|
||||
showTurnDiffs: false,
|
||||
showTurnNavigator: true,
|
||||
backgroundActivity: "left",
|
||||
generatedUI: true,
|
||||
mobileTitlebarPosition: "top",
|
||||
|
||||
@@ -121,6 +121,7 @@ const generalSchema = Persistence.struct({
|
||||
}),
|
||||
showCustomAgents: Schema.Boolean,
|
||||
showTurnDiffs: Schema.Boolean,
|
||||
showTurnNavigator: Schema.Boolean,
|
||||
backgroundActivity: Schema.Literals(["off", "left", "center", "right"]),
|
||||
// Lets agents render generated UI in the timeline; an experiment users can turn off.
|
||||
generatedUI: Schema.Boolean,
|
||||
@@ -290,6 +291,7 @@ export const defaultSettings: Settings = {
|
||||
timelineDetail: { ...timelinePresets[2].value },
|
||||
showCustomAgents: false,
|
||||
showTurnDiffs: false,
|
||||
showTurnNavigator: true,
|
||||
backgroundActivity: "left",
|
||||
generatedUI: true,
|
||||
mobileTitlebarPosition: "top",
|
||||
@@ -379,6 +381,13 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
||||
setShowTurnDiffs(value: boolean) {
|
||||
setStore("general", "showTurnDiffs", value)
|
||||
},
|
||||
showTurnNavigator: withFallback(
|
||||
() => store.general?.showTurnNavigator,
|
||||
defaultSettings.general.showTurnNavigator,
|
||||
),
|
||||
setShowTurnNavigator(value: boolean) {
|
||||
setStore("general", "showTurnNavigator", value)
|
||||
},
|
||||
backgroundActivity: withFallback(
|
||||
() => store.general?.backgroundActivity,
|
||||
defaultSettings.general.backgroundActivity,
|
||||
|
||||
@@ -89,6 +89,13 @@ export const clientSettings: Entry<SettingsRootTab>[] = [
|
||||
description: "settings.experimental.row.generatedUI.description",
|
||||
keywords: "genui generative interface dashboards charts forms",
|
||||
},
|
||||
{
|
||||
tab: "experimental",
|
||||
label: "settings.experimental.row.turnNavigator.title",
|
||||
target: "settings-turn-navigator",
|
||||
description: "settings.experimental.row.turnNavigator.description",
|
||||
keywords: "conversation prompts messages timeline navigation rail",
|
||||
},
|
||||
{
|
||||
tab: "appearance",
|
||||
label: "settings.general.row.colorScheme.title",
|
||||
|
||||
Reference in New Issue
Block a user