diff --git a/packages/app/src/pages/session/composer/session-revert-dock.stories.tsx b/packages/app/src/pages/session/composer/session-revert-dock.stories.tsx new file mode 100644 index 000000000000..dd030870a15b --- /dev/null +++ b/packages/app/src/pages/session/composer/session-revert-dock.stories.tsx @@ -0,0 +1,118 @@ +import { For } from "solid-js" +import { createStore } from "solid-js/store" +import { SessionRevertDock } from "@/pages/session/composer/session-revert-dock" +import { SettingsProvider, useSettings } from "@/context/settings" + +export default { + title: "Composer/Revert Dock", + id: "composer-revert-dock", + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: `### Overview +Real \`SessionRevertDock\` from app code, rendered above a mock composer card. + +### Source path +- \`packages/app/src/pages/session/composer/session-revert-dock.tsx\` + +### Why the mock composer +The live composer overlaps the dock's bottom by 18px (\`session-composer-region-controller.ts\` \`lift()\`). +The card below reproduces that overlap so the collapsed/expanded cutoff behavior can be verified in isolation. + +### Layout split +Use the **Layout** button to toggle \`newLayoutDesigns\` and preview both the v2 dock and the legacy (v1) \`DockTray\` fallback. + +### Notes +- \`onRestore\` only mutates local story state, so nothing in the real session is affected. +- Click the header to expand/collapse. Click "Restore message" to remove a row.`, + }, + }, + }, +} + +const messages = [ + "update current branch with latest changes from dev and fix conflicts if any", + "investigate why the chat input loses focus after sending a message", + "Debug why streaming responses sometimes duplicate the last token", + "suggest a better title for this PR based on the diff", + "add a storybook story for the revert dock", + "reduce re-renders in the timeline component", +] + +const btn = (accent?: boolean) => + ({ + padding: "6px 12px", + "border-radius": "6px", + border: "1px solid var(--v2-border-border-base, #0000001a)", + background: accent ? "var(--v2-background-bg-contrast, #242424)" : "var(--v2-background-bg-base, #fff)", + color: accent ? "var(--v2-text-text-contrast, #fafafa)" : "var(--v2-text-text-base, #161616)", + cursor: "pointer", + "font-size": "13px", + }) as const + +function Stage(props: { count: number }) { + const settings = useSettings() + const seed = () => messages.slice(0, props.count).map((text, index) => ({ id: `rolled-${index}`, text })) + const [store, setStore] = createStore({ items: seed() }) + + const v2 = () => settings.general.newLayoutDesigns() + const reset = () => setStore("items", seed()) + const restore = (id: string) => + setStore( + "items", + store.items.filter((item) => item.id !== id), + ) + + return ( +
+
+ + +
+ + {/* Reproduce the real composer stack: dock + card overlapping the dock's bottom by lift() = 18px */} +
+ +
+ Ask anything... +
+
+ +
+ Restored so far:{" "} + + {(item) => {store.items.some((current) => current.id === item.id) ? "" : `“${item.text}” `}} + +
+
+ ) +} + +const story = (count: number) => () => ( + + + +) + +export const OneMessage = { + name: "1 rolled back", + render: story(1), +} + +export const ThreeMessages = { + name: "3 rolled back", + render: story(3), +} + +export const ManyMessages = { + name: "6 rolled back (scrolls)", + render: story(6), +} diff --git a/packages/app/src/pages/session/composer/session-revert-dock.tsx b/packages/app/src/pages/session/composer/session-revert-dock.tsx index 5a5b8a9cd457..d8f022a53780 100644 --- a/packages/app/src/pages/session/composer/session-revert-dock.tsx +++ b/packages/app/src/pages/session/composer/session-revert-dock.tsx @@ -3,7 +3,11 @@ import { createStore } from "solid-js/store" import { Button } from "@opencode-ai/ui/button" import { DockTray } from "@opencode-ai/ui/dock-surface" import { IconButton } from "@opencode-ai/ui/icon-button" +import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" +import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" +import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" import { useLanguage } from "@/context/language" +import { useSettings } from "@/context/settings" export function SessionRevertDock(props: { items: { id: string; text: string }[] @@ -12,6 +16,7 @@ export function SessionRevertDock(props: { onRestore: (id: string) => void }) { const language = useLanguage() + const settings = useSettings() const [store, setStore] = createStore({ collapsed: true, }) @@ -31,69 +36,153 @@ export function SessionRevertDock(props: { ) const preview = createMemo(() => props.items[0]?.text ?? "") + const onHeaderKeyDown = (event: KeyboardEvent) => { + if (event.key !== "Enter" && event.key !== " ") return + event.preventDefault() + toggle() + } + return ( - + +
+ {label()} + + {preview()} + +
+ { + event.preventDefault() + event.stopPropagation() + }} + onClick={(event) => { + event.stopPropagation() + toggle() + }} + aria-label={ + store.collapsed ? language.t("session.revertDock.expand") : language.t("session.revertDock.collapse") + } + /> +
+
+ + +