From c7a7900ff2cdd136ccb93268a4a7717cea12deac Mon Sep 17 00:00:00 2001 From: James Long Date: Fri, 17 Jul 2026 15:18:58 -0400 Subject: [PATCH] refactor(tui): migrate workflow views to V2 themes (#37526) --- packages/tui/src/app.tsx | 4 +- .../tui/src/component/devtools-sidebar.tsx | 26 +++++- .../tui/src/component/dialog-integration.tsx | 57 +++++++------ packages/tui/src/component/dialog-mcp.tsx | 82 +++++++++++++------ .../tui/src/component/dialog-move-session.tsx | 26 +++--- .../tui/src/component/dialog-retry-action.tsx | 27 +++--- .../dialog-session-delete-failed.tsx | 19 +++-- .../dialog-workspace-file-changes.tsx | 31 ++++--- .../tui/src/feature-plugins/system/scrap.tsx | 11 +-- packages/tui/src/theme/v2/v1-migrate.ts | 2 +- packages/tui/src/ui/dialog-export-options.tsx | 58 +++++++++---- packages/tui/src/ui/dialog-prompt.tsx | 22 ++--- packages/tui/src/ui/dialog.tsx | 4 +- packages/tui/src/ui/toast.tsx | 6 +- packages/tui/test/theme/v2/v1-migrate.test.ts | 6 +- 15 files changed, 243 insertions(+), 138 deletions(-) diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx index a0b2e3fc0be..0690d3fba75 100644 --- a/packages/tui/src/app.tsx +++ b/packages/tui/src/app.tsx @@ -420,7 +420,7 @@ function App(props: { pair?: DialogPairCredentials; started: number }) { const client = useClient() const toast = useToast() const themeState = useTheme() - const { theme, mode, setMode, locked, lock, unlock } = themeState + const { themeV2, mode, setMode, locked, lock, unlock } = themeState const data = useData() const location = useLocation() const exit = useExit() @@ -1087,7 +1087,7 @@ function App(props: { pair?: DialogPairCredentials; started: number }) { width={dimensions().width} height={dimensions().height} flexDirection="column" - backgroundColor={theme.background} + backgroundColor={themeV2.background()} onMouseDown={(evt) => { if (!Flag.OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT) return if (evt.button !== MouseButton.RIGHT) return diff --git a/packages/tui/src/component/devtools-sidebar.tsx b/packages/tui/src/component/devtools-sidebar.tsx index ae2aafec099..6fa3fdd645c 100644 --- a/packages/tui/src/component/devtools-sidebar.tsx +++ b/packages/tui/src/component/devtools-sidebar.tsx @@ -1,10 +1,11 @@ import { TextAttributes } from "@opentui/core" -import { For } from "solid-js" +import { createSignal, For } from "solid-js" import { useTheme } from "../context/theme" import { DevTools } from "../devtools" export function DevToolsSidebar() { - const { themeV2 } = useTheme().contextual("elevated") + const { themeV2, mode, setMode } = useTheme().contextual("elevated") + const [modeHovered, setModeHovered] = createSignal(false) return ( + + + + Theme + + + + Mode + + setModeHovered(true)} + onMouseOut={() => setModeHovered(false)} + onMouseUp={() => setMode(mode() === "dark" ? "light" : "dark")} + > + {mode()} + + + {(group) => ( diff --git a/packages/tui/src/component/dialog-integration.tsx b/packages/tui/src/component/dialog-integration.tsx index 2cc9c585845..0024bf34436 100644 --- a/packages/tui/src/component/dialog-integration.tsx +++ b/packages/tui/src/component/dialog-integration.tsx @@ -62,7 +62,7 @@ export function connectionSummary(integration: IntegrationInfo) { export function DialogIntegration(props: { onConnected?: OnIntegrationConnected } = {}) { const data = useData() const dialog = useDialog() - const { theme } = useTheme() + const { themeV2 } = useTheme().contextual("elevated") const options = createMemo(() => integrationOptions(data.location.integration.list() ?? []).map((integration) => { const methods = connectMethods(integration) @@ -74,7 +74,7 @@ export function DialogIntegration(props: { onConnected?: OnIntegrationConnected footer: connectionSummary(integration) || undefined, category: integration.id in INTEGRATION_PRIORITY ? "Popular" : "Services", disabled: methods.length === 0, - gutter: connected ? () => : undefined, + gutter: connected ? () => : undefined, onSelect: () => credentialConnections(integration).length ? manageConnections(integration, methods, dialog, props.onConnected) @@ -89,12 +89,12 @@ export function DialogIntegration(props: { onConnected?: OnIntegrationConnected options={options()} emptyView={ - No integrations available + No integrations available } noMatchView={ - No integrations found + No integrations found } /> @@ -289,23 +289,30 @@ function CommandPending(props: { function CommandView(props: { title: string; output: string; message: string }) { const dialog = useDialog() - const { theme } = useTheme() + const { themeV2 } = useTheme().contextual("elevated") + const { themeV2: overlayTheme } = useTheme().contextual("overlay") onMount(() => dialog.setSize("large")) return ( - + {props.title} - dialog.clear()}> + dialog.clear()}> esc close - - {props.output.trim()} + + {props.output.trim()} - {props.message} + {props.message} ) @@ -320,7 +327,7 @@ function KeyMethod(props: { const dialog = useDialog() const client = useClient() const toast = useToast() - const { theme } = useTheme() + const { themeV2 } = useTheme().contextual("elevated") const [error, setError] = createSignal() return ( @@ -338,7 +345,9 @@ function KeyMethod(props: { .then(() => connected(props.integration, data, dialog, toast, props.onConnected)) .catch((cause) => setError(message(cause))) }} - description={() => {(value) => {value()}}} + description={() => ( + {(value) => {value()}} + )} /> ) } @@ -493,7 +502,7 @@ function OAuthCode(props: { const dialog = useDialog() const client = useClient() const toast = useToast() - const { theme } = useTheme() + const { themeV2 } = useTheme().contextual("elevated") const [error, setError] = createSignal() let settled = false @@ -527,9 +536,9 @@ function OAuthCode(props: { }} description={() => ( - {props.attempt.instructions} - - {(value) => {value()}} + {props.attempt.instructions} + + {(value) => {value()}} )} /> @@ -538,31 +547,31 @@ function OAuthCode(props: { function OAuthView(props: { title: string; url?: string; instructions?: string; message: string; copy?: boolean }) { const dialog = useDialog() - const { theme } = useTheme() + const { themeV2 } = useTheme().contextual("elevated") return ( - + {props.title} - dialog.clear()}> + dialog.clear()}> esc {(url) => ( - + - {(instructions) => {instructions()}} + {(instructions) => {instructions()}} )} - {props.message} + {props.message} - - c copy + + c copy diff --git a/packages/tui/src/component/dialog-mcp.tsx b/packages/tui/src/component/dialog-mcp.tsx index 4f9a4d83d64..696ff468d86 100644 --- a/packages/tui/src/component/dialog-mcp.tsx +++ b/packages/tui/src/component/dialog-mcp.tsx @@ -4,7 +4,7 @@ import { Keymap } from "../context/keymap" import { pipe, sortBy } from "remeda" import { DialogSelect } from "../ui/dialog-select" import { useDialog } from "../ui/dialog" -import { useTheme, type Theme } from "../context/theme" +import { useTheme } from "../context/theme" import { TextAttributes, type ScrollBoxRenderable } from "@opentui/core" import type { McpServer } from "@opencode-ai/client" import { useClipboard } from "../context/clipboard" @@ -12,30 +12,59 @@ import { useToast } from "../ui/toast" import { useKeyboard, useTerminalDimensions } from "@opentui/solid" import { useConfig } from "../config" import { getScrollAcceleration } from "../util/scroll" +import type { ComponentTheme } from "../theme/v2/component" // Sort by how much attention a server needs: auth prompts first, then failures, // then healthy servers, and intentionally-off servers last. -function statusMeta(status: McpServer["status"], theme: Theme) { +function statusMeta(status: McpServer["status"], themeV2: ComponentTheme) { switch (status.status) { case "needs_auth": - return { rank: 0, icon: "!", label: "Needs authentication", color: theme.warning, error: undefined, bold: false } + return { + rank: 0, + icon: "!", + label: "Needs authentication", + color: themeV2.text.feedback.warning(), + error: undefined, + bold: false, + } case "needs_client_registration": - return { rank: 1, icon: "✗", label: "Needs registration", color: theme.error, error: status.error, bold: false } + return { + rank: 1, + icon: "✗", + label: "Needs registration", + color: themeV2.text.feedback.error(), + error: status.error, + bold: false, + } case "failed": - return { rank: 2, icon: "✗", label: "Failed", color: theme.error, error: status.error, bold: false } + return { + rank: 2, + icon: "✗", + label: "Failed", + color: themeV2.text.feedback.error(), + error: status.error, + bold: false, + } case "connected": - return { rank: 3, icon: "✓", label: "Connected", color: theme.success, error: undefined, bold: true } + return { + rank: 3, + icon: "✓", + label: "Connected", + color: themeV2.text.feedback.success(), + error: undefined, + bold: true, + } case "pending": - return { rank: 4, icon: "◌", label: "Pending", color: theme.textMuted, error: undefined, bold: false } + return { rank: 4, icon: "◌", label: "Pending", color: themeV2.text.subdued(), error: undefined, bold: false } default: - return { rank: 5, icon: "○", label: "Disabled", color: theme.textMuted, error: undefined, bold: false } + return { rank: 5, icon: "○", label: "Disabled", color: themeV2.text.subdued(), error: undefined, bold: false } } } export function DialogMcp() { const data = useData() const dialog = useDialog() - const { theme } = useTheme() + const { themeV2 } = useTheme().contextual("elevated") const [focused, setFocused] = createSignal() const [detail, setDetail] = createSignal() @@ -47,7 +76,7 @@ export function DialogMcp() { pipe( data.location.mcp.server.list() ?? [], sortBy( - (server) => statusMeta(server.status, theme).rank, + (server) => statusMeta(server.status, themeV2).rank, (server) => server.name, ), ), @@ -61,7 +90,7 @@ export function DialogMcp() { const options = createMemo(() => servers().map((server) => { - const meta = statusMeta(server.status, theme) + const meta = statusMeta(server.status, themeV2) return { value: server.name, title: server.name, @@ -77,12 +106,12 @@ export function DialogMcp() { const focusedError = createMemo(() => { const name = focused() const server = servers().find((entry) => entry.name === name) - return server ? statusMeta(server.status, theme).error : undefined + return server ? statusMeta(server.status, themeV2).error : undefined }) const open = (name: string | undefined) => { const server = servers().find((entry) => entry.name === name) - if (!server || !statusMeta(server.status, theme).error) return + if (!server || !statusMeta(server.status, themeV2).error) return setDetail(server) } @@ -100,7 +129,7 @@ export function DialogMcp() { onSelect={(option) => open(option.value as string)} footer={ - enter to view error + enter to view error } /> @@ -116,11 +145,12 @@ function DialogMcpError(props: { server: McpServer; onBack: () => void }) { const dialog = useDialog() const clipboard = useClipboard() const toast = useToast() - const { theme } = useTheme() + const { themeV2 } = useTheme().contextual("elevated") + const { themeV2: overlayTheme } = useTheme().contextual("overlay") const dimensions = useTerminalDimensions() const config = useConfig().data const [copied, setCopied] = createSignal(false) - const error = () => statusMeta(props.server.status, theme).error ?? "Unknown MCP connection error" + const error = () => statusMeta(props.server.status, themeV2).error ?? "Unknown MCP connection error" const height = createMemo(() => Math.max(3, Math.floor(dimensions().height / 2) - 5)) let scroll: ScrollBoxRenderable | undefined @@ -152,29 +182,35 @@ function DialogMcpError(props: { server: McpServer; onBack: () => void }) { return ( - + MCP server: {props.server.name} - + esc back - ✗ Failed - + ✗ Failed + (scroll = element)} height={height()} scrollbarOptions={{ visible: false }} scrollAcceleration={getScrollAcceleration(config)} > - + {error()} - ↑↓ scroll - + ↑↓ scroll + {copied() ? "✓ copied" : "c copy details"} diff --git a/packages/tui/src/component/dialog-move-session.tsx b/packages/tui/src/component/dialog-move-session.tsx index 9f6c165e176..3348b6c2c80 100644 --- a/packages/tui/src/component/dialog-move-session.tsx +++ b/packages/tui/src/component/dialog-move-session.tsx @@ -38,7 +38,7 @@ export function DialogMoveSession(props: DialogMoveSessionProps) { const dialog = useDialog() const client = useClient() const dimensions = useTerminalDimensions() - const { theme } = useTheme() + const { themeV2 } = useTheme().contextual("elevated") const sessionData = useData() const route = useRoute() const toast = useToast() @@ -172,16 +172,18 @@ export function DialogMoveSession(props: DialogMoveSessionProps) { return { title, titleView: isRemoving ? ( - Deleting {item.location} + Deleting {item.location} ) : deleting ? ( - Press {shortcuts.get("dialog.move_session.delete")} again to confirm + + Press {shortcuts.get("dialog.move_session.delete")} again to confirm + ) : suffix ? ( <> {visible.slice(0, split)} - {visible.slice(split)} + {visible.slice(split)} ) : undefined, - bg: deleting ? theme.error : undefined, + bg: deleting ? themeV2.background.action.destructive() : undefined, value: { type: "directory", directory: item.location, @@ -314,7 +316,7 @@ export function DialogMoveSession(props: DialogMoveSessionProps) { title="Move session" titleView={ - + Move session @@ -327,25 +329,25 @@ export function DialogMoveSession(props: DialogMoveSessionProps) { emptyView={ showError() ? ( - + Could not load project directories - {errorMessage(loadError())} - Close and reopen Move session to try again. + {errorMessage(loadError())} + Close and reopen Move session to try again. ) : directories.loading || loadedProject.loading ? ( - Loading project directories… + Loading project directories… ) : ( - No project directories available + No project directories available ) } noMatchView={ - No project directories found + No project directories found } locked={showError() || directories.loading || loadedProject.loading || Boolean(removing())} diff --git a/packages/tui/src/component/dialog-retry-action.tsx b/packages/tui/src/component/dialog-retry-action.tsx index 25befc571a7..01b5226fa19 100644 --- a/packages/tui/src/component/dialog-retry-action.tsx +++ b/packages/tui/src/component/dialog-retry-action.tsx @@ -2,7 +2,7 @@ import { RGBA, TextAttributes } from "@opentui/core" import open from "open" import { createSignal } from "solid-js" import { Keymap } from "../context/keymap" -import { selectedForeground, useTheme } from "../context/theme" +import { useTheme } from "../context/theme" import { useDialog, type DialogContext } from "../ui/dialog" import { Link } from "../ui/link" import { BgPulse } from "./bg-pulse" @@ -38,10 +38,9 @@ function panelOverlay(color: RGBA) { export function DialogRetryAction(props: DialogRetryActionProps) { const dialog = useDialog() - const { theme } = useTheme() - const fg = selectedForeground(theme) + const { themeV2 } = useTheme().contextual("elevated") const showGoTreatment = () => props.link === GO_URL - const textBg = () => (showGoTreatment() ? panelOverlay(theme.backgroundPanel) : undefined) + const textBg = () => (showGoTreatment() ? panelOverlay(themeV2.background()) : undefined) const [selected, setSelected] = createSignal<"dismiss" | "action">("action") Keymap.createLayer(() => ({ @@ -86,26 +85,26 @@ export function DialogRetryAction(props: DialogRetryActionProps) { ) : null} - + {props.title} - dialog.clear()}> + dialog.clear()}> esc - + {props.message} {props.link ? ( showGoTreatment() ? ( - + ) : ( - + ) ) : ( @@ -115,12 +114,14 @@ export function DialogRetryAction(props: DialogRetryActionProps) { setSelected("dismiss")} onMouseUp={() => dismiss(props, dialog)} > @@ -130,12 +131,12 @@ export function DialogRetryAction(props: DialogRetryActionProps) { setSelected("action")} onMouseUp={() => runAction(props, dialog)} > diff --git a/packages/tui/src/component/dialog-session-delete-failed.tsx b/packages/tui/src/component/dialog-session-delete-failed.tsx index 8754fd03dba..b70a17d44eb 100644 --- a/packages/tui/src/component/dialog-session-delete-failed.tsx +++ b/packages/tui/src/component/dialog-session-delete-failed.tsx @@ -13,7 +13,7 @@ export function DialogSessionDeleteFailed(props: { onDone?: () => void }) { const dialog = useDialog() - const { theme } = useTheme() + const { themeV2 } = useTheme().contextual("elevated") const [store, setStore] = createStore({ active: "delete" as "delete" | "restore", }) @@ -64,17 +64,17 @@ export function DialogSessionDeleteFailed(props: { return ( - + Failed to Delete Session - dialog.clear()}> + dialog.clear()}> esc - + {`The session "${props.session}" could not be deleted because the workspace "${props.workspace}" is not available.`} - + Choose how you want to recover this broken workspace session. @@ -86,7 +86,7 @@ export function DialogSessionDeleteFailed(props: { paddingRight={1} paddingTop={1} paddingBottom={1} - backgroundColor={item.id === store.active ? theme.primary : undefined} + backgroundColor={item.id === store.active ? themeV2.background.action("focused") : undefined} onMouseUp={() => { setStore("active", item.id) void confirm() @@ -94,11 +94,14 @@ export function DialogSessionDeleteFailed(props: { > {item.title} - + {item.description} diff --git a/packages/tui/src/component/dialog-workspace-file-changes.tsx b/packages/tui/src/component/dialog-workspace-file-changes.tsx index 415b70623a4..f2c344065c7 100644 --- a/packages/tui/src/component/dialog-workspace-file-changes.tsx +++ b/packages/tui/src/component/dialog-workspace-file-changes.tsx @@ -31,14 +31,15 @@ export function DialogWorkspaceFileChanges(props: { message?: string }) { const dialog = useDialog() - const { theme } = useTheme() + const { themeV2 } = useTheme().contextual("elevated") + const { themeV2: overlayTheme } = useTheme().contextual("overlay") const config = useConfig().data const dimensions = useTerminalDimensions() const scrollAcceleration = createMemo(() => getScrollAcceleration(config)) const [store, setStore] = createStore({ active: "yes" as WorkspaceFileChangesChoice }) const height = createMemo(() => Math.min(props.files.length, 8)) - const fileNameWidth = createMemo( - () => Math.max(2, Math.min(60, dimensions().width - 2) - 6 - Math.max(7, ...props.files.map(changeCountWidth))), + const fileNameWidth = createMemo(() => + Math.max(2, Math.min(60, dimensions().width - 2) - 6 - Math.max(7, ...props.files.map(changeCountWidth))), ) function confirm() { @@ -71,21 +72,21 @@ export function DialogWorkspaceFileChanges(props: { return ( - + {props.title ?? "File Changes Found"} - dialog.clear()}> + dialog.clear()}> esc - + {props.message ?? "Do you want to move these changes with the session?"} @@ -94,15 +95,19 @@ export function DialogWorkspaceFileChanges(props: { - {statusLabel(item.status)} + {statusLabel(item.status)} - + {" "} - {item.additions ? +{item.additions} : null} - {item.deletions ? -{item.deletions} : null} + {item.additions ? ( + +{item.additions} + ) : null} + {item.deletions ? ( + -{item.deletions} + ) : null} @@ -115,14 +120,14 @@ export function DialogWorkspaceFileChanges(props: { { setStore("active", item) props.onSelect(item) dialog.clear() }} > - {item} + {item} )} diff --git a/packages/tui/src/feature-plugins/system/scrap.tsx b/packages/tui/src/feature-plugins/system/scrap.tsx index 488944d632b..6118da5aacc 100644 --- a/packages/tui/src/feature-plugins/system/scrap.tsx +++ b/packages/tui/src/feature-plugins/system/scrap.tsx @@ -26,7 +26,8 @@ function Commands(props: { context: Plugin.Context }) { function Scrap(props: { context: Plugin.Context }) { const dimensions = useTerminalDimensions() - const { theme } = useTheme() + const { themeV2 } = useTheme() + const { themeV2: elevatedTheme } = useTheme().contextual("elevated") Keymap.createLayer(() => ({ commands: [ @@ -42,19 +43,19 @@ function Scrap(props: { context: Plugin.Context }) { })) return ( - + - ~/code/anomalyco/opencode + ~/code/anomalyco/opencode - esc home + esc home ) diff --git a/packages/tui/src/theme/v2/v1-migrate.ts b/packages/tui/src/theme/v2/v1-migrate.ts index 959a2bf48d6..fafc0ad1f42 100644 --- a/packages/tui/src/theme/v2/v1-migrate.ts +++ b/packages/tui/src/theme/v2/v1-migrate.ts @@ -267,7 +267,7 @@ function neutralAnchors(theme: Theme, mode: "light" | "dark") { const light: { step: HueStep; color: RGBA }[] = [ { step: 100, color: theme.background }, { step: 200, color: theme.backgroundPanel }, - { step: 300, color: theme.backgroundMenu }, + { step: 300, color: theme.backgroundElement || theme.backgroundMenu }, { step: 700, color: theme.textMuted }, { step: 900, color: theme.text }, ] diff --git a/packages/tui/src/ui/dialog-export-options.tsx b/packages/tui/src/ui/dialog-export-options.tsx index 6e5b9f472f9..4ccd87e9ce0 100644 --- a/packages/tui/src/ui/dialog-export-options.tsx +++ b/packages/tui/src/ui/dialog-export-options.tsx @@ -17,7 +17,8 @@ type Active = ExportFormat | "debug" | "thinking" | "copy" | "export" export function DialogExportOptions(props: DialogExportOptionsProps) { const dialog = useDialog() - const { theme } = useTheme() + const { themeV2 } = useTheme().contextual("elevated") + const { themeV2: overlayTheme } = useTheme().contextual("overlay") const [store, setStore] = createStore({ format: "markdown" as ExportFormat, debug: false, @@ -75,25 +76,33 @@ export function DialogExportOptions(props: DialogExportOptionsProps) { return ( - + Export session - dialog.clear()}> + dialog.clear()}> esc - Export as: + Export as: {(format) => ( selectFormat(format)} > - + {store.format === format ? "◉" : "○"} {format === "markdown" ? "Markdown" : "JSON"} @@ -105,43 +114,60 @@ export function DialogExportOptions(props: DialogExportOptionsProps) { { setStore("active", "thinking") setStore("thinking", !store.thinking) }} > - + {store.thinking ? "[x]" : "[ ]"} - Include thinking + + Include thinking + { setStore("active", "debug") setStore("debug", !store.debug) }} > - {store.debug ? "[x]" : "[ ]"} - Events (debug) + + {store.debug ? "[x]" : "[ ]"} + + + Events (debug) + confirm("copy")} > - Copy + Copy - confirm("export")}> - Export + confirm("export")} + > + Export diff --git a/packages/tui/src/ui/dialog-prompt.tsx b/packages/tui/src/ui/dialog-prompt.tsx index be89c9a8d46..b997fc8a93f 100644 --- a/packages/tui/src/ui/dialog-prompt.tsx +++ b/packages/tui/src/ui/dialog-prompt.tsx @@ -18,7 +18,7 @@ export type DialogPromptProps = { export function DialogPrompt(props: DialogPromptProps) { const dialog = useDialog() - const { theme } = useTheme() + const { themeV2 } = useTheme().contextual("elevated") const shortcuts = Keymap.useShortcuts() const [textareaTarget, setTextareaTarget] = createSignal() let textarea: TextareaRenderable @@ -74,10 +74,10 @@ export function DialogPrompt(props: DialogPromptProps) { return ( - + {props.title} - dialog.clear()}> + dialog.clear()}> esc @@ -91,20 +91,20 @@ export function DialogPrompt(props: DialogPromptProps) { }} initialValue={props.value} placeholder={props.placeholder ?? "Enter text"} - placeholderColor={theme.textMuted} - textColor={props.busy ? theme.textMuted : theme.text} - focusedTextColor={props.busy ? theme.textMuted : theme.text} - cursorColor={props.busy ? theme.backgroundElement : theme.text} + placeholderColor={themeV2.text.subdued()} + textColor={themeV2.text.formfield({ disabled: props.busy })} + focusedTextColor={themeV2.text.formfield({ disabled: props.busy })} + cursorColor={props.busy ? themeV2.background.formfield("disabled") : themeV2.text()} /> - {props.busyText ?? "Working..."} + {props.busyText ?? "Working..."} - processing...}> + processing...}> - - {shortcuts.get("dialog.prompt.submit")} submit + + {shortcuts.get("dialog.prompt.submit")} submit diff --git a/packages/tui/src/ui/dialog.tsx b/packages/tui/src/ui/dialog.tsx index f9e6358a6fa..99062e967ee 100644 --- a/packages/tui/src/ui/dialog.tsx +++ b/packages/tui/src/ui/dialog.tsx @@ -16,7 +16,7 @@ export function Dialog( }>, ) { const dimensions = useTerminalDimensions() - const { theme } = useTheme() + const { themeV2 } = useTheme().contextual("elevated") const renderer = useRenderer() let dismiss = false @@ -59,7 +59,7 @@ export function Dialog( }} width={width()} maxWidth={dimensions().width - 2} - backgroundColor={theme.backgroundPanel} + backgroundColor={themeV2.background()} paddingTop={1} > {props.children} diff --git a/packages/tui/src/ui/toast.tsx b/packages/tui/src/ui/toast.tsx index a72fd8f3135..c5edb8689dc 100644 --- a/packages/tui/src/ui/toast.tsx +++ b/packages/tui/src/ui/toast.tsx @@ -14,7 +14,7 @@ type ToastInput = Omit & { duration?: number } export function Toast() { const toast = useToast() - const { theme, themeV2 } = useTheme() + const { themeV2 } = useTheme().contextual("overlay") const dimensions = useTerminalDimensions() return ( @@ -31,8 +31,8 @@ export function Toast() { paddingRight={2} paddingTop={1} paddingBottom={1} - backgroundColor={theme.backgroundPanel} - borderColor={theme[current().variant]} + backgroundColor={themeV2.background()} + borderColor={themeV2.text.feedback[current().variant]()} border={["left", "right"]} customBorderChars={SplitBorder.customBorderChars} > diff --git a/packages/tui/test/theme/v2/v1-migrate.test.ts b/packages/tui/test/theme/v2/v1-migrate.test.ts index aaf9782a911..e7e704f7c01 100644 --- a/packages/tui/test/theme/v2/v1-migrate.test.ts +++ b/packages/tui/test/theme/v2/v1-migrate.test.ts @@ -85,7 +85,7 @@ test("infers chromatic hues, anchors light and dark colors, and aliases ambiguou expect(() => resolveThemeFile(migrated, "dark")).not.toThrow() }) -test("builds gray from V1 surfaces and text without using borders", () => { +test("builds gray from V1 surfaces and text without using menus or borders", () => { const source = structuredClone(DEFAULT_THEMES.opencode) source.theme.backgroundMenu = { light: "#ededed", dark: "#252525" } const light = resolveV1(source, "light") @@ -97,12 +97,12 @@ test("builds gray from V1 surfaces and text without using borders", () => { expect(lightGray[100]).toBe(hex(light.background)) expect(lightGray[200]).toBe(hex(light.backgroundPanel)) - expect(lightGray[300]).toBe(hex(light.backgroundMenu)) + expect(lightGray[300]).toBe(hex(light.backgroundElement)) expect(lightGray[700]).toBe(hex(light.textMuted)) expect(lightGray[900]).toBe(hex(light.text)) expect(darkGray[100]).toBe(hex(dark.text)) expect(darkGray[300]).toBe(hex(dark.textMuted)) - expect(darkGray[700]).toBe(hex(dark.backgroundMenu)) + expect(darkGray[700]).toBe(hex(dark.backgroundElement)) expect(darkGray[800]).toBe(hex(dark.backgroundPanel)) expect(darkGray[900]).toBe(hex(dark.background))