Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions tui/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import { useTerminalSize } from "./hooks/useTerminalSize";
import { useCancelKey } from "./hooks/useCancelKey";
import { planLayout } from "./layout";
import { PaletteProvider, usePalette } from "./styles/palette";
// The canvas never dims — it is the background a dialog is dimmed *toward* —
// so it reads the lit palette directly rather than through usePalette().
import { colors as theme } from "./styles/theme";
import { ClockProvider } from "./hooks/useClock";
import { Scrollbar } from "./components/Scrollbar";
import { getModelDisplayName } from "../../providers/client";
Expand Down Expand Up @@ -76,7 +79,7 @@ export function App({ controller, onExit, homeScreen }: AppProps) {
flexDirection="column"
width={width}
height={height}
backgroundColor="#000000"
backgroundColor={theme.bgCanvas}
>
{/* One interval drives every animation below. See useClock for what the
four independent timers this replaced were costing. */}
Expand All @@ -86,11 +89,17 @@ export function App({ controller, onExit, homeScreen }: AppProps) {
<PaletteProvider dimmed={dialogOpen}>
{/* Header — pinned at top */}
<Box flexShrink={0} paddingX={1}>
<Header branch={homeScreen.branch} provider={homeScreen.providerName} />
<Header
branch={homeScreen.branch}
provider={homeScreen.providerName}
// The same condition HomeScreen draws the wordmark on, so the two
// cannot disagree about whether the name is already on screen.
wordmarkShowing={showHome && layout.wordmark !== "hidden" && !paletteOpen}
/>
</Box>

{/* Main content */}
<Box flexDirection="column" flexGrow={1} minHeight={0} paddingX={1} backgroundColor="#000000">
<Box flexDirection="column" flexGrow={1} minHeight={0} paddingX={1} backgroundColor={theme.bgCanvas}>
{showHome ? (
<HomeScreen
{...homeScreen}
Expand All @@ -111,7 +120,7 @@ export function App({ controller, onExit, homeScreen }: AppProps) {
flexGrow={1}
flexShrink={1}
minHeight={0}
backgroundColor="#000000"
backgroundColor={theme.bgCanvas}
>
<DiffPreview pendingEdit={state.pendingEdit!} />
</Box>
Expand Down
17 changes: 10 additions & 7 deletions tui/src/components/ApprovalFooter.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
import { Box, Text } from "ink";
import { usePalette } from "../styles/palette";

export function ApprovalFooter() {
const colors = usePalette();

return (
<Box
borderStyle="round"
borderColor="yellow"
borderColor={colors.warningBase}
paddingX={1}
marginTop={1}
flexDirection="row"
gap={2}
>
<Text color="green" bold>
<Text color={colors.successBase} bold>
[A]
</Text>
<Text dimColor>Apply</Text>
<Text color={colors.textFaint}>Apply</Text>

<Text color="red" bold>
<Text color={colors.dangerBase} bold>
[R]
</Text>
<Text dimColor>Reject</Text>
<Text color={colors.textFaint}>Reject</Text>

<Text color="gray" bold>
<Text color={colors.textMuted} bold>
[Esc]
</Text>
<Text dimColor>Cancel</Text>
<Text color={colors.textFaint}>Cancel</Text>
</Box>
);
}
12 changes: 7 additions & 5 deletions tui/src/components/ApprovalPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ export function ApprovalPicker({ mode }: ApprovalPickerProps) {
width={layout.dialogWidth}
paddingX={layout.dialogWidth < 40 ? 1 : 3}
paddingY={1}
backgroundColor="#101010"
backgroundColor={colors.bgElevated}
borderStyle={layout.showDialogBorder ? "round" : undefined}
borderColor={colors.borderElevated}
>
<Box justifyContent="space-between" marginBottom={layout.dialogRhythm}>
<Text bold color={colors.textStrong}>
Expand All @@ -83,22 +85,22 @@ export function ApprovalPicker({ mode }: ApprovalPickerProps) {

return (
<Box key={entry.mode} flexDirection="column">
<Box paddingX={1} backgroundColor={selected ? "#fb923c" : undefined}>
<Text color={selected ? "#000000" : colors.textMuted}>
<Box paddingX={1} backgroundColor={selected ? colors.selectionBg : undefined}>
<Text color={selected ? colors.selectionFg : colors.textMuted}>
{active ? "● " : " "}
</Text>
<Box flexShrink={1} minWidth={0}>
<Text
bold={selected}
color={selected ? "#000000" : colors.textBase}
color={selected ? colors.selectionFg : colors.textBase}
wrap="truncate-end"
>
{entry.label}
</Text>
</Box>
<Box flexGrow={1} />
{entry.unsafe && (
<Text color={selected ? "#7c2d12" : colors.warningBase}>unsafe</Text>
<Text color={selected ? colors.selectionFgWarn : colors.warningBase}>unsafe</Text>
)}
</Box>
{/* The description only for the highlighted row: four of them at
Expand Down
13 changes: 11 additions & 2 deletions tui/src/components/CapabilityRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,23 @@ export interface CapabilityRowProps {
capabilities: readonly string[];
}

/**
* What the agent does, under the wordmark.
*
* Separated by the same middle dot the header, the composer and the turn footer
* use. These were `#Build #Plan #Review` — hashtags, which say "tag" to a
* reader who has met them anywhere else, and which nothing on any other screen
* matched.
*/
export function CapabilityRow({ capabilities }: CapabilityRowProps) {
const colors = usePalette();

return (
<Box flexWrap="wrap" justifyContent="center">
{capabilities.map((capability, index) => (
<Box key={capability} marginRight={index === capabilities.length - 1 ? 0 : 2}>
<Text color={colors.borderStrong}>#{capability}</Text>
<Box key={capability}>
{index > 0 && <Text color={colors.borderStrong}>{" · "}</Text>}
<Text color={colors.textFaint}>{capability}</Text>
</Box>
))}
</Box>
Expand Down
6 changes: 4 additions & 2 deletions tui/src/components/CommandApproval.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ export function CommandApproval({ command }: { command: PendingCommand }) {
width={layout.dialogWidth}
paddingX={layout.dialogWidth < 40 ? 1 : 3}
paddingY={1}
backgroundColor="#101010"
backgroundColor={colors.bgElevated}
borderStyle={layout.showDialogBorder ? "round" : undefined}
borderColor={colors.borderElevated}
>
<Box justifyContent="space-between" marginBottom={layout.dialogRhythm}>
<Text bold color={colors.textStrong}>
Expand All @@ -51,7 +53,7 @@ export function CommandApproval({ command }: { command: PendingCommand }) {
</Box>

{/* The command itself, as it will be run. */}
<Box paddingX={1} backgroundColor="#1a1a1a" flexShrink={0}>
<Box paddingX={1} backgroundColor={colors.bgInset} flexShrink={0}>
<Text color={colors.textFaint}>{"$ "}</Text>
<Box flexShrink={1} minWidth={0}>
<Text color={colors.textBase} wrap="truncate-end">
Expand Down
10 changes: 5 additions & 5 deletions tui/src/components/CommandPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export function CommandPreview({
const { hiddenAbove, hiddenBelow, showIndicators: useIndicators } = visible;

return (
<Box flexDirection="column" flexShrink={0} paddingX={1} backgroundColor="#1a1a1a">
<Box flexDirection="column" flexShrink={0} paddingX={1} backgroundColor={colors.bgInset}>
{visible.showHeader && (
<Box marginBottom={1}>
<Text color={colors.textFaint} bold>
Expand All @@ -145,15 +145,15 @@ export function CommandPreview({
height={1}
flexShrink={0}
paddingX={1}
backgroundColor={isSelected ? "#fb923c" : undefined}
backgroundColor={isSelected ? colors.selectionBg : undefined}
>
<Box width={NAME_COLUMN_WIDTH} flexShrink={0}>
<Text color={isSelected ? "#000000" : colors.primary}>/</Text>
<Text bold color={isSelected ? "#000000" : colors.textBase}>
<Text color={isSelected ? colors.selectionFg : colors.primary}>/</Text>
<Text bold color={isSelected ? colors.selectionFg : colors.textBase}>
{cmd.name}
</Text>
</Box>
<Text color={isSelected ? "#431407" : colors.textMuted} wrap="truncate-end">
<Text color={isSelected ? colors.selectionFgMuted : colors.textMuted} wrap="truncate-end">
{cmd.description}
{aliases}
</Text>
Expand Down
4 changes: 3 additions & 1 deletion tui/src/components/ContinueTurn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ export function ContinueTurn({ continuation }: { continuation: PendingContinuati
width={layout.dialogWidth}
paddingX={layout.dialogWidth < 40 ? 1 : 3}
paddingY={1}
backgroundColor="#101010"
backgroundColor={colors.bgElevated}
borderStyle={layout.showDialogBorder ? "round" : undefined}
borderColor={colors.borderElevated}
>
<Box justifyContent="space-between" marginBottom={layout.dialogRhythm}>
<Text bold color={colors.textStrong}>
Expand Down
17 changes: 6 additions & 11 deletions tui/src/components/InlineCode.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
import { Text } from "ink";
import { useDimmed } from "../styles/palette";
import { dimHex } from "../styles/theme";
import { usePalette } from "../styles/palette";

interface InlineCodeProps {
text: string;
}

const CODE_COLOR = "#7fd88f";
const CODE_BACKGROUND = "#1e1e1e";

export function InlineCode({ text }: InlineCodeProps) {
// Its own colours rather than the theme's, so they need fading explicitly.
const dimmed = useDimmed();
// Read through the palette rather than from two module constants of its own,
// which is what removes the manual dimHex this used to need: behind a dialog
// the span now fades with everything around it.
const colors = usePalette();

return (
<Text
color={dimmed ? dimHex(CODE_COLOR) : CODE_COLOR}
backgroundColor={dimmed ? dimHex(CODE_BACKGROUND) : CODE_BACKGROUND}
>
<Text color={colors.accent} backgroundColor={colors.bgCode}>
{` ${text} `}
</Text>
);
Expand Down
2 changes: 1 addition & 1 deletion tui/src/components/LogoReveal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ function renderScanReveal(
</Text>
{/* Scan line character (if not at end) */}
{scanIndex < text.length && (
<Text bold={bold} color={color} backgroundColor="gray">
<Text bold={bold} color={color} backgroundColor={colors.borderBase}>
{text[scanIndex]}
</Text>
)}
Expand Down
15 changes: 9 additions & 6 deletions tui/src/components/ModelPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getConfig, saveConfig } from "../../../config/config";
import { allModels, isRunnable, providerLabel } from "../../../providers/modelCatalog";
import type { AgentController } from "../../../commands/agentController";
import { store } from "../store/ui-store";
import { colors } from "../styles/theme";
import { usePalette } from "../styles/palette";
import { planLayout, windowAround } from "../layout";
import { useTerminalSize } from "../hooks/useTerminalSize";
import { applyModelSelection } from "../model-selection";
Expand All @@ -16,6 +16,7 @@ interface ModelPickerProps {
}

export function ModelPicker({ controller, selectedModel }: ModelPickerProps) {
const colors = usePalette();
const [query, setQuery] = useState("");
const [showCursor, setShowCursor] = useState(true);
const [saving, setSaving] = useState(false);
Expand Down Expand Up @@ -113,7 +114,9 @@ export function ModelPicker({ controller, selectedModel }: ModelPickerProps) {
width={layout.dialogWidth}
paddingX={layout.dialogWidth < 40 ? 1 : 3}
paddingY={1}
backgroundColor="#101010"
backgroundColor={colors.bgElevated}
borderStyle={layout.showDialogBorder ? "round" : undefined}
borderColor={colors.borderElevated}
>
<Box justifyContent="space-between" marginBottom={layout.dialogRhythm}>
<Text bold color={colors.textStrong}>Select model</Text>
Expand Down Expand Up @@ -147,16 +150,16 @@ export function ModelPicker({ controller, selectedModel }: ModelPickerProps) {
const index = visible.start + offset;
const selected = index === selectedIndex;
return (
<Box key={model.id} paddingX={1} backgroundColor={selected ? "#fb923c" : undefined}>
<Text color={selected ? "#000000" : colors.textMuted}>{selected ? "● " : " "}</Text>
<Box key={model.id} paddingX={1} backgroundColor={selected ? colors.selectionBg : undefined}>
<Text color={selected ? colors.selectionFg : colors.textMuted}>{selected ? "● " : " "}</Text>
<Box flexShrink={1} minWidth={0}>
<Text bold={selected} color={selected ? "#000000" : colors.textBase} wrap="truncate-end">
<Text bold={selected} color={selected ? colors.selectionFg : colors.textBase} wrap="truncate-end">
{model.name}
</Text>
</Box>
<Box flexGrow={1} />
{layout.dialogWidth >= 40 && (
<Text color={selected ? "#000000" : colors.textFaint}>
<Text color={selected ? colors.selectionFg : colors.textFaint}>
{providerLabel(model.provider)}
</Text>
)}
Expand Down
13 changes: 9 additions & 4 deletions tui/src/components/QuestionDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ import TextInput from "ink-text-input";
import { useState } from "react";
import type { PendingQuestion } from "../types";
import { store } from "../store/ui-store";
import { colors } from "../styles/theme";
import { usePalette } from "../styles/palette";
import { planLayout } from "../layout";
import { useTerminalSize } from "../hooks/useTerminalSize";

export function QuestionDialog({ question }: { question: PendingQuestion }) {
const colors = usePalette();
const { width, height } = useTerminalSize();
const layout = planLayout(width, height);
const [index, setIndex] = useState(0);
const [value, setValue] = useState("");
const [answers, setAnswers] = useState<string[]>([]);
Expand All @@ -32,9 +37,9 @@ export function QuestionDialog({ question }: { question: PendingQuestion }) {
<Box
flexDirection="column"
width="80%"
borderStyle="single"
borderColor={colors.primary}
backgroundColor="#101010"
borderStyle={layout.showDialogBorder ? "round" : undefined}
borderColor={colors.borderElevated}
backgroundColor={colors.bgElevated}
paddingX={1}
>
<Text bold color={colors.primary}>Question {index + 1} of {question.questions.length}</Text>
Expand Down
15 changes: 9 additions & 6 deletions tui/src/components/SessionPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useEffect, useMemo, useState } from "react";
import { listSessions, type SessionSummary } from "../../../config/sessions";
import type { AgentController } from "../../../commands/agentController";
import { store } from "../store/ui-store";
import { colors } from "../styles/theme";
import { usePalette } from "../styles/palette";
import { planLayout, windowAround } from "../layout";
import { useTerminalSize } from "../hooks/useTerminalSize";
import { relativeTime } from "../relative-time";
Expand All @@ -14,6 +14,7 @@ interface SessionPickerProps {
}

export function SessionPicker({ controller }: SessionPickerProps) {
const colors = usePalette();
const [query, setQuery] = useState("");
const [showCursor, setShowCursor] = useState(true);
const [switching, setSwitching] = useState(false);
Expand Down Expand Up @@ -142,7 +143,9 @@ export function SessionPicker({ controller }: SessionPickerProps) {
width={layout.dialogWidth}
paddingX={layout.dialogWidth < 40 ? 1 : 3}
paddingY={1}
backgroundColor="#101010"
backgroundColor={colors.bgElevated}
borderStyle={layout.showDialogBorder ? "round" : undefined}
borderColor={colors.borderElevated}
>
<Box justifyContent="space-between" marginBottom={layout.dialogRhythm}>
<Text bold color={colors.textStrong}>Resume session</Text>
Expand Down Expand Up @@ -187,23 +190,23 @@ export function SessionPicker({ controller }: SessionPickerProps) {
<Box
key={session.id}
paddingX={1}
backgroundColor={selected ? "#fb923c" : undefined}
backgroundColor={selected ? colors.selectionBg : undefined}
>
<Text color={selected ? "#000000" : colors.textMuted}>
<Text color={selected ? colors.selectionFg : colors.textMuted}>
{session.id === activeId ? "● " : selected ? "› " : " "}
</Text>
<Box flexShrink={1} minWidth={0}>
<Text
bold={selected}
color={selected ? "#000000" : colors.textBase}
color={selected ? colors.selectionFg : colors.textBase}
wrap="truncate-end"
>
{label}
</Text>
</Box>
<Box flexGrow={1} />
{layout.dialogWidth >= 40 && (
<Text color={selected ? "#000000" : colors.textFaint}>
<Text color={selected ? colors.selectionFg : colors.textFaint}>
{relativeTime(session.updated)}
</Text>
)}
Expand Down
Loading