Fix TUI redraw artifacts in row-based views (#325)

Co-authored-by: pr0ln <pr0ln@pr0lnui-Macmini.local>
This commit is contained in:
pr0ln
2026-04-04 18:19:31 +09:00
committed by GitHub
parent c3c60b7bab
commit 3c2e80a1ae
8 changed files with 55 additions and 31 deletions

View File

@@ -9,6 +9,7 @@ import { formatDuration, formatNumber } from '../../utils/format.js';
import { toInkColor } from '../../utils/ink.js';
import type { Theme } from '../../utils/theme.js';
import { Byline } from '../design-system/Byline.js';
import FullWidthRow from '../design-system/FullWidthRow.js';
import { GlimmerMessage } from './GlimmerMessage.js';
import { SpinnerGlyph } from './SpinnerGlyph.js';
import type { SpinnerMode } from './types.js';
@@ -223,11 +224,13 @@ export function SpinnerAnimationRow({
<Byline>{parts}</Byline>
<Text dimColor>)</Text>
</> : null;
return <Box ref={viewportRef} flexDirection="row" flexWrap="wrap" marginTop={1} width="100%">
<SpinnerGlyph frame={frame} messageColor={messageColor} stalledIntensity={overrideColor ? 0 : stalledIntensity} reducedMotion={reducedMotion} time={time} />
<GlimmerMessage message={message} mode={mode} messageColor={messageColor} glimmerIndex={glimmerIndex} flashOpacity={flashOpacity} shimmerColor={shimmerColor} stalledIntensity={overrideColor ? 0 : stalledIntensity} />
{status}
</Box>;
return <FullWidthRow>
<Box ref={viewportRef} flexDirection="row" flexWrap="wrap" marginTop={1}>
<SpinnerGlyph frame={frame} messageColor={messageColor} stalledIntensity={overrideColor ? 0 : stalledIntensity} reducedMotion={reducedMotion} time={time} />
<GlimmerMessage message={message} mode={mode} messageColor={messageColor} glimmerIndex={glimmerIndex} flashOpacity={flashOpacity} shimmerColor={shimmerColor} stalledIntensity={overrideColor ? 0 : stalledIntensity} />
{status}
</Box>
</FullWidthRow>;
}
function SpinnerModeGlyph(t0) {
const $ = _c(2);

View File

@@ -13,6 +13,7 @@ import { summarizeRecentActivities } from '../utils/collapseReadSearch.js';
import { truncateToWidth } from '../utils/format.js';
import { isTodoV2Enabled, type Task } from '../utils/tasks.js';
import type { Theme } from '../utils/theme.js';
import FullWidthRow from './design-system/FullWidthRow.js';
import ThemedText from './design-system/ThemedText.js';
type Props = {
tasks: Task[];
@@ -186,11 +187,11 @@ export function TaskListV2({
}
const content = <>
{visibleTasks.map(task_0 => <TaskItem key={task_0.id} task={task_0} ownerColor={task_0.owner ? teammateColors[task_0.owner] : undefined} openBlockers={task_0.blockedBy.filter(id_3 => unresolvedTaskIds.has(id_3))} activity={task_0.owner ? teammateActivity[task_0.owner] : undefined} ownerActive={task_0.owner ? activeTeammates.has(task_0.owner) : false} columns={columns} />)}
{maxDisplay > 0 && hiddenSummary && <Text dimColor>{hiddenSummary}</Text>}
{maxDisplay > 0 && hiddenSummary && <FullWidthRow><Text dimColor>{hiddenSummary}</Text></FullWidthRow>}
</>;
if (isStandalone) {
return <Box flexDirection="column" marginTop={1} marginLeft={2}>
<Box>
return <Box flexDirection="column" marginTop={1} marginLeft={2} width="100%">
<Box width="100%">
<Text dimColor>
<Text bold>{tasks.length}</Text>
{' tasks ('}
@@ -207,7 +208,7 @@ export function TaskListV2({
{content}
</Box>;
}
return <Box flexDirection="column">{content}</Box>;
return <Box flexDirection="column" width="100%">{content}</Box>;
}
type TaskItemProps = {
task: Task;
@@ -340,7 +341,7 @@ function TaskItem(t0) {
}
let t10;
if ($[26] !== t5 || $[27] !== t7 || $[28] !== t8 || $[29] !== t9) {
t10 = <Box>{t5}{t7}{t8}{t9}</Box>;
t10 = <FullWidthRow>{t5}{t7}{t8}{t9}</FullWidthRow>;
$[26] = t5;
$[27] = t7;
$[28] = t8;
@@ -351,7 +352,7 @@ function TaskItem(t0) {
}
let t11;
if ($[31] !== displayActivity || $[32] !== showActivity) {
t11 = showActivity && displayActivity && <Box><Text dimColor={true}>{" "}{displayActivity}{figures.ellipsis}</Text></Box>;
t11 = showActivity && displayActivity && <FullWidthRow><Text dimColor={true}>{" "}{displayActivity}{figures.ellipsis}</Text></FullWidthRow>;
$[31] = displayActivity;
$[32] = showActivity;
$[33] = t11;
@@ -360,7 +361,7 @@ function TaskItem(t0) {
}
let t12;
if ($[34] !== t10 || $[35] !== t11) {
t12 = <Box flexDirection="column">{t10}{t11}</Box>;
t12 = <Box flexDirection="column" width="100%">{t10}{t11}</Box>;
$[34] = t10;
$[35] = t11;
$[36] = t12;

View File

@@ -6,6 +6,7 @@ import { useKeybinding } from '../../keybindings/useKeybinding.js';
import type { Theme } from '../../utils/theme.js';
import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js';
import { Byline } from './Byline.js';
import FullWidthRow from './FullWidthRow.js';
import { KeyboardShortcutHint } from './KeyboardShortcutHint.js';
import { Pane } from './Pane.js';
type DialogProps = {
@@ -102,7 +103,7 @@ export function Dialog(t0) {
}
let t9;
if ($[16] !== defaultInputGuide || $[17] !== exitState || $[18] !== hideInputGuide || $[19] !== inputGuide) {
t9 = !hideInputGuide && <Box marginTop={1}><Text dimColor={true} italic={true}>{inputGuide ? inputGuide(exitState) : defaultInputGuide}</Text></Box>;
t9 = !hideInputGuide && <Box marginTop={1}><FullWidthRow><Text dimColor={true} italic={true}>{inputGuide ? inputGuide(exitState) : defaultInputGuide}</Text></FullWidthRow></Box>;
$[16] = defaultInputGuide;
$[17] = exitState;
$[18] = hideInputGuide;

View File

@@ -0,0 +1,15 @@
import * as React from 'react';
import { Box } from '../../ink.js';
type Props = {
children: React.ReactNode;
};
export default function FullWidthRow({
children
}: Props): React.ReactNode {
return <Box flexDirection="row" width="100%">
{children}
<Box flexGrow={1} />
</Box>;
}

View File

@@ -24,6 +24,7 @@ import { BLACK_CIRCLE } from '../../constants/figures.js';
import { TeammateMessageContent } from './UserTeammateMessage.js';
import { isShutdownApproved } from '../../utils/teammateMailbox.js';
import { CtrlOToExpand } from '../CtrlOToExpand.js';
import FullWidthRow from '../design-system/FullWidthRow.js';
import { FilePathLink } from '../FilePathLink.js';
import { feature } from 'bun:bundle';
import { useSelectedMessageBg } from '../messageActions.js';
@@ -514,7 +515,7 @@ function Line(t0) {
const bg = useSelectedMessageBg();
let t2;
if ($[0] !== children || $[1] !== color || $[2] !== dimColor) {
t2 = <MessageResponse><Text color={color} dimColor={dimColor} wrap="wrap">{children}</Text></MessageResponse>;
t2 = <MessageResponse><FullWidthRow><Text color={color} dimColor={dimColor} wrap="wrap">{children}</Text></FullWidthRow></MessageResponse>;
$[0] = children;
$[1] = color;
$[2] = dimColor;

View File

@@ -5,6 +5,7 @@ import { NO_CONTENT_MESSAGE } from '../../constants/messages.js';
import { Box, Text } from '../../ink.js';
import { extractTag } from '../../utils/messages.js';
import { Markdown } from '../Markdown.js';
import FullWidthRow from '../design-system/FullWidthRow.js';
import { MessageResponse } from '../MessageResponse.js';
type Props = {
content: string;
@@ -77,7 +78,7 @@ function IndentedContent(t0) {
}
let t2;
if ($[3] !== children) {
t2 = <Box flexDirection="row">{t1}<Box flexDirection="column" flexGrow={1}><Markdown>{children}</Markdown></Box></Box>;
t2 = <FullWidthRow>{t1}<Box flexDirection="column" flexGrow={1}><Markdown>{children}</Markdown></Box></FullWidthRow>;
$[3] = children;
$[4] = t2;
} else {
@@ -147,7 +148,7 @@ function CloudLaunchContent(t0) {
}
let t6;
if ($[14] !== rest) {
t6 = rest && <Box flexDirection="row"><Text dimColor={true}>{" \u23BF "}</Text><Text dimColor={true}>{rest}</Text></Box>;
t6 = rest && <FullWidthRow><Text dimColor={true}>{" \u23BF "}</Text><Text dimColor={true}>{rest}</Text></FullWidthRow>;
$[14] = rest;
$[15] = t6;
} else {

View File

@@ -11,6 +11,7 @@ import { getSettingSourceName, type SettingSource } from '../../utils/settings/c
import { plural } from '../../utils/stringUtils.js';
import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js';
import { Dialog } from '../design-system/Dialog.js';
import FullWidthRow from '../design-system/FullWidthRow.js';
// Skills are always PromptCommands with CommandBase properties
type SkillCommand = CommandBase & PromptCommand;
@@ -105,14 +106,14 @@ export function SkillsMenu(t0) {
if (skills.length === 0) {
let t3;
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
t3 = <Text dimColor={true}>Create skills in .claude/skills/ or ~/.claude/skills/</Text>;
t3 = <FullWidthRow><Text dimColor={true}>Create skills in .claude/skills/ or ~/.claude/skills/</Text></FullWidthRow>;
$[6] = t3;
} else {
t3 = $[6];
}
let t4;
if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
t4 = <Text dimColor={true} italic={true}><ConfigurableShortcutHint action="confirm:no" context="Confirmation" fallback="Esc" description="close" /></Text>;
t4 = <FullWidthRow><Text dimColor={true} italic={true}><ConfigurableShortcutHint action="confirm:no" context="Confirmation" fallback="Esc" description="close" /></Text></FullWidthRow>;
$[7] = t4;
} else {
t4 = $[7];
@@ -137,7 +138,7 @@ export function SkillsMenu(t0) {
}
const title = getSourceTitle(source_0);
const subtitle = getSourceSubtitle(source_0, groupSkills);
return <Box flexDirection="column" key={source_0}><Box><Text bold={true} dimColor={true}>{title}</Text>{subtitle && <Text dimColor={true}> ({subtitle})</Text>}</Box>{groupSkills.map(skill_1 => renderSkill(skill_1))}</Box>;
return <Box flexDirection="column" key={source_0}><FullWidthRow><Text bold={true} dimColor={true}>{title}</Text>{subtitle && <Text dimColor={true}> ({subtitle})</Text>}</FullWidthRow>{groupSkills.map(skill_1 => renderSkill(skill_1))}</Box>;
};
$[10] = skillsBySource;
$[11] = t3;
@@ -209,7 +210,7 @@ export function SkillsMenu(t0) {
}
let t13;
if ($[30] === Symbol.for("react.memo_cache_sentinel")) {
t13 = <Text dimColor={true} italic={true}><ConfigurableShortcutHint action="confirm:no" context="Confirmation" fallback="Esc" description="close" /></Text>;
t13 = <FullWidthRow><Text dimColor={true} italic={true}><ConfigurableShortcutHint action="confirm:no" context="Confirmation" fallback="Esc" description="close" /></Text></FullWidthRow>;
$[30] = t13;
} else {
t13 = $[30];
@@ -230,7 +231,7 @@ function _temp3(skill_0) {
const estimatedTokens = estimateSkillFrontmatterTokens(skill_0);
const tokenDisplay = `~${formatTokens(estimatedTokens)}`;
const pluginName = skill_0.source === "plugin" ? skill_0.pluginInfo?.pluginManifest.name : undefined;
return <Box key={`${skill_0.name}-${skill_0.source}`}><Text>{getSkillListLabel(skill_0)}</Text><Text dimColor={true}>{pluginName ? ` · ${pluginName}` : ""} · {tokenDisplay} description tokens</Text></Box>;
return <FullWidthRow key={`${skill_0.name}-${skill_0.source}`}><Text>{getSkillListLabel(skill_0)}</Text><Text dimColor={true}>{pluginName ? ` · ${pluginName}` : ""} · {tokenDisplay} description tokens</Text></FullWidthRow>;
}
function _temp2(a, b) {
return a.name.localeCompare(b.name);

View File

@@ -1,6 +1,7 @@
import { c as _c } from "react-compiler-runtime";
import React from 'react';
import { removeSandboxViolationTags } from 'src/utils/sandbox/sandbox-ui-utils.js';
import FullWidthRow from '../../components/design-system/FullWidthRow.js';
import { KeyboardShortcutHint } from '../../components/design-system/KeyboardShortcutHint.js';
import { MessageResponse } from '../../components/MessageResponse.js';
import { OutputLine } from '../../components/shell/OutputLine.js';
@@ -100,7 +101,7 @@ export default function BashToolResultMessage(t0) {
if (isImage) {
let t8;
if ($[11] === Symbol.for("react.memo_cache_sentinel")) {
t8 = <MessageResponse height={1}><Text dimColor={true}>[Image data detected and sent to Claude]</Text></MessageResponse>;
t8 = <MessageResponse height={1}><FullWidthRow><Text dimColor={true}>[Image data detected and sent to Claude]</Text></FullWidthRow></MessageResponse>;
$[11] = t8;
} else {
t8 = $[11];
@@ -145,7 +146,7 @@ export default function BashToolResultMessage(t0) {
}
let t8;
if ($[15] !== cwdResetWarning) {
t8 = cwdResetWarning ? <MessageResponse><Text dimColor={true}>{cwdResetWarning}</Text></MessageResponse> : null;
t8 = cwdResetWarning ? <MessageResponse><FullWidthRow><Text dimColor={true}>{cwdResetWarning}</Text></FullWidthRow></MessageResponse> : null;
$[15] = cwdResetWarning;
$[16] = t8;
} else {
@@ -153,7 +154,7 @@ export default function BashToolResultMessage(t0) {
}
let t9;
if ($[17] !== backgroundTaskId || $[18] !== cwdResetWarning || $[19] !== noOutputExpected || $[20] !== returnCodeInterpretation || $[21] !== stderr || $[22] !== stdout) {
t9 = stdout === "" && stderr.trim() === "" && !cwdResetWarning ? <MessageResponse height={1}><Text dimColor={true}>{backgroundTaskId ? <>Running in the background{" "}<KeyboardShortcutHint shortcut={"\u2193"} action="manage" parens={true} /></> : returnCodeInterpretation || (noOutputExpected ? "Done" : "(No output)")}</Text></MessageResponse> : null;
t9 = stdout === "" && stderr.trim() === "" && !cwdResetWarning ? <MessageResponse height={1}><FullWidthRow><Text dimColor={true}>{backgroundTaskId ? <>Running in the background{" "}<KeyboardShortcutHint shortcut={"\u2193"} action="manage" parens={true} /></> : returnCodeInterpretation || (noOutputExpected ? "Done" : "(No output)")}</Text></FullWidthRow></MessageResponse> : null;
$[17] = backgroundTaskId;
$[18] = cwdResetWarning;
$[19] = noOutputExpected;
@@ -174,7 +175,7 @@ export default function BashToolResultMessage(t0) {
}
let t11;
if ($[26] !== T0 || $[27] !== t10 || $[28] !== t4 || $[29] !== t5 || $[30] !== t6 || $[31] !== t8 || $[32] !== t9) {
t11 = <T0 flexDirection={t4}>{t5}{t6}{t8}{t9}{t10}</T0>;
t11 = <T0 flexDirection={t4} width="100%">{t5}{t6}{t8}{t9}{t10}</T0>;
$[26] = T0;
$[27] = t10;
$[28] = t4;