mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-15 07:48:24 -04:00
chore(tui): remove turn flash scaffolding
This commit is contained in:
@@ -4,6 +4,6 @@
|
||||
- Put the current treatment and meaningfully different variants in the story. Expose replay and tuning controls in `StoryFooter`, including reset when values are adjustable.
|
||||
- Let the user choose or tune a variant in the story before selecting production defaults.
|
||||
- After selection, register the behavior in `src/component/dialog-experiments.tsx` and gate it with `config.data.experimental?.<id> === true`; experiments must not change default behavior.
|
||||
- Keep the story as the safe tuning and regression fixture for the experiment.
|
||||
- Treat tuning-only stories as local scaffolding and remove them and their registration before committing. Commit a story only when the user explicitly wants it retained as a reusable regression fixture.
|
||||
- Use OpenCode Drive with a simulated LLM for deterministic turn/session behavior. Do not invoke a real model only to verify TUI behavior.
|
||||
- Run the story with `OPENCODE_STORY=<story-id> bun run dev:live` and exercise relevant wide and narrow terminal sizes.
|
||||
|
||||
@@ -4,7 +4,6 @@ import { createSignal, For, type JSX } from "solid-js"
|
||||
import { StoryFooter } from "./footer"
|
||||
import { sessionTabsStory } from "./session-tabs"
|
||||
import { sessionLocationMissingStory } from "./session-location-missing"
|
||||
import { turnSummaryStory } from "./turn-summary"
|
||||
|
||||
/**
|
||||
* A story is a full-screen, fixture-driven simulation of a real production component. Stories own
|
||||
@@ -16,7 +15,7 @@ export type Story = {
|
||||
render: (context: Plugin.Context) => JSX.Element
|
||||
}
|
||||
|
||||
const stories: Story[] = [sessionTabsStory, turnSummaryStory, sessionLocationMissingStory]
|
||||
const stories: Story[] = [sessionTabsStory, sessionLocationMissingStory]
|
||||
|
||||
function Commands(props: { context: Plugin.Context }) {
|
||||
props.context.keymap.layer(() => ({
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
import { Plugin } from "@opencode-ai/plugin/tui"
|
||||
import { useTerminalDimensions } from "@opentui/solid"
|
||||
import { createSignal, For } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { AssistantSummary } from "../../../component/assistant-summary"
|
||||
import { useLocal } from "../../../context/local"
|
||||
import { StoryFooter } from "./footer"
|
||||
import type { Story } from "./index"
|
||||
|
||||
const PRESETS = [
|
||||
{ name: "Snap", duration: 0.18, intensity: 0.9 },
|
||||
{ name: "Current", duration: 0.32, intensity: 0.75 },
|
||||
{ name: "Gentle", duration: 0.5, intensity: 0.6 },
|
||||
{ name: "Lingering", duration: 0.8, intensity: 0.7 },
|
||||
]
|
||||
|
||||
function TurnSummaryStory(props: { context: Plugin.Context }) {
|
||||
const dimensions = useTerminalDimensions()
|
||||
const theme = props.context.theme
|
||||
const local = useLocal()
|
||||
const [presets, setPresets] = createStore(PRESETS.map((preset) => ({ ...preset, trigger: 0 })))
|
||||
const [selected, setSelected] = createSignal(1)
|
||||
const [lastEvent, setLastEvent] = createSignal("press space to compare all four")
|
||||
|
||||
const replay = (index?: number) => {
|
||||
if (index === undefined) {
|
||||
presets.forEach((_, presetIndex) => setPresets(presetIndex, "trigger", (trigger) => trigger + 1))
|
||||
setLastEvent("replayed all variants")
|
||||
return
|
||||
}
|
||||
setPresets(index, "trigger", (trigger) => trigger + 1)
|
||||
setLastEvent(`replayed ${presets[index]!.name.toLowerCase()}`)
|
||||
}
|
||||
const adjustDuration = (delta: number) => {
|
||||
const index = selected()
|
||||
setPresets(index, "duration", (duration) => Math.max(0.05, Math.round((duration + delta) * 100) / 100))
|
||||
replay(index)
|
||||
}
|
||||
const adjustIntensity = (delta: number) => {
|
||||
const index = selected()
|
||||
setPresets(index, "intensity", (intensity) => Math.max(0.1, Math.min(1, intensity + delta)))
|
||||
replay(index)
|
||||
}
|
||||
const reset = () => {
|
||||
PRESETS.forEach((preset, index) => setPresets(index, { ...preset, trigger: presets[index]!.trigger + 1 }))
|
||||
setSelected(1)
|
||||
setLastEvent("reset presets and replayed all variants")
|
||||
}
|
||||
|
||||
props.context.keymap.layer(() => ({
|
||||
commands: [
|
||||
{
|
||||
bind: "escape",
|
||||
title: "Back to storybook",
|
||||
group: "Storybook",
|
||||
run: () => props.context.ui.router.navigate({ type: "plugin", name: "storybook" }),
|
||||
},
|
||||
{ bind: "space", title: "Replay all variants", group: "Storybook", run: () => replay() },
|
||||
...PRESETS.map((preset, index) => ({
|
||||
bind: String(index + 1),
|
||||
title: `Replay ${preset.name}`,
|
||||
group: "Storybook",
|
||||
run: () => {
|
||||
setSelected(index)
|
||||
replay(index)
|
||||
},
|
||||
})),
|
||||
{
|
||||
bind: "up,k",
|
||||
title: "Select previous variant",
|
||||
group: "Storybook",
|
||||
run: () => setSelected((index) => (index + presets.length - 1) % presets.length),
|
||||
},
|
||||
{
|
||||
bind: "down,j",
|
||||
title: "Select next variant",
|
||||
group: "Storybook",
|
||||
run: () => setSelected((index) => (index + 1) % presets.length),
|
||||
},
|
||||
{ bind: "left,h", title: "Shorten fade", group: "Storybook", run: () => adjustDuration(-0.05) },
|
||||
{ bind: "right,l", title: "Lengthen fade", group: "Storybook", run: () => adjustDuration(0.05) },
|
||||
{ bind: "-", title: "Dim flash", group: "Storybook", run: () => adjustIntensity(-0.05) },
|
||||
{ bind: "+,=", title: "Brighten flash", group: "Storybook", run: () => adjustIntensity(0.05) },
|
||||
{ bind: "r", title: "Reset variants", group: "Storybook", run: reset },
|
||||
],
|
||||
}))
|
||||
|
||||
return (
|
||||
<box
|
||||
width={dimensions().width}
|
||||
height={dimensions().height}
|
||||
flexDirection="column"
|
||||
backgroundColor={theme.background.default}
|
||||
>
|
||||
<box paddingTop={2} paddingLeft={3} paddingRight={3} flexDirection="column" flexGrow={1}>
|
||||
<text fg={theme.text.default}>turn summary flash</text>
|
||||
<text fg={theme.text.subdued}>compare the production completion treatment across timing presets</text>
|
||||
<box height={2} />
|
||||
<For each={presets}>
|
||||
{(preset, index) => (
|
||||
<box flexDirection="column" marginBottom={1}>
|
||||
<text fg={index() === selected() ? theme.text.default : theme.text.subdued}>
|
||||
{index() === selected() ? "›" : " "} {index() + 1} {preset.name.padEnd(10)} {preset.duration.toFixed(2)}s
|
||||
{" "}{Math.round(preset.intensity * 100)}%
|
||||
</text>
|
||||
<box paddingLeft={4}>
|
||||
<AssistantSummary
|
||||
agent="Build"
|
||||
model="GPT-5.6 Sol Fast"
|
||||
duration="10.5s"
|
||||
agentColor={local.agent.color("build")}
|
||||
subduedColor={theme.text.subdued}
|
||||
flashColor={theme.text.default}
|
||||
animations
|
||||
flash={preset}
|
||||
/>
|
||||
</box>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
</box>
|
||||
<StoryFooter
|
||||
context={props.context}
|
||||
title="storybook / turn summary"
|
||||
status={`${presets[selected()]!.name} ${presets[selected()]!.duration.toFixed(2)}s ${Math.round(presets[selected()]!.intensity * 100)}%`}
|
||||
message={lastEvent()}
|
||||
controls={[
|
||||
{ shortcut: "space", label: "replay all" },
|
||||
{ shortcut: "1-4", label: "replay one" },
|
||||
{ shortcut: "↑/↓", label: "select" },
|
||||
{ shortcut: "←/→", label: "timing" },
|
||||
{ shortcut: "-/+", label: "brightness" },
|
||||
{ shortcut: "r", label: "reset" },
|
||||
{ shortcut: "esc", label: "back" },
|
||||
]}
|
||||
/>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
export const turnSummaryStory: Story = {
|
||||
id: "turn-summary",
|
||||
title: "Turn summary flash",
|
||||
render: (context) => <TurnSummaryStory context={context} />,
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/** @jsxImportSource @opentui/solid */
|
||||
import { RGBA } from "@opentui/core"
|
||||
import { testRender } from "@opentui/solid"
|
||||
import { expect, test } from "bun:test"
|
||||
import { AssistantSummary } from "../../src/component/assistant-summary"
|
||||
|
||||
const agent = RGBA.fromHex("#6699ff")
|
||||
const subdued = RGBA.fromHex("#667085")
|
||||
const text = RGBA.fromHex("#f0f2ff")
|
||||
|
||||
test("flashes a completed summary and settles to its semantic colors", async () => {
|
||||
const app = await testRender(
|
||||
() => (
|
||||
<AssistantSummary
|
||||
agent="Build"
|
||||
model="Simulated Model"
|
||||
duration="800ms"
|
||||
agentColor={agent}
|
||||
subduedColor={subdued}
|
||||
flashColor={text}
|
||||
animations
|
||||
flash={{ trigger: 1, duration: 0.8, intensity: 0.7 }}
|
||||
/>
|
||||
),
|
||||
{ width: 60, height: 1 },
|
||||
)
|
||||
|
||||
try {
|
||||
await app.renderOnce()
|
||||
const flashed = app.renderer.currentRenderBuffer.getSpanLines()[0]!.spans.filter((span) => span.text.trim())
|
||||
expect(flashed[0]!.fg.equals(agent)).toBeFalse()
|
||||
expect(flashed[1]!.fg.equals(subdued)).toBeFalse()
|
||||
|
||||
await Bun.sleep(900)
|
||||
await app.renderOnce()
|
||||
const settled = app.renderer.currentRenderBuffer.getSpanLines()[0]!.spans.filter((span) => span.text.trim())
|
||||
expect(settled[0]!.fg.equals(agent)).toBeTrue()
|
||||
expect(settled[1]!.fg.equals(subdued)).toBeTrue()
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
test("stays at rest when animations are disabled", async () => {
|
||||
const app = await testRender(
|
||||
() => (
|
||||
<AssistantSummary
|
||||
agent="Build"
|
||||
model="Simulated Model"
|
||||
agentColor={agent}
|
||||
subduedColor={subdued}
|
||||
flashColor={text}
|
||||
animations={false}
|
||||
flash={{ trigger: 1, duration: 0.8, intensity: 0.7 }}
|
||||
/>
|
||||
),
|
||||
{ width: 60, height: 1 },
|
||||
)
|
||||
|
||||
try {
|
||||
await app.renderOnce()
|
||||
const spans = app.renderer.currentRenderBuffer.getSpanLines()[0]!.spans.filter((span) => span.text.trim())
|
||||
expect(spans[0]!.fg.equals(agent)).toBeTrue()
|
||||
expect(spans[1]!.fg.equals(subdued)).toBeTrue()
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user