diff --git a/ui-tui/src/app.tsx b/ui-tui/src/app.tsx
index 06399ba7ad..88dcf84e64 100644
--- a/ui-tui/src/app.tsx
+++ b/ui-tui/src/app.tsx
@@ -709,7 +709,10 @@ export function App({ gw }: { gw: GatewayClient }) {
historyDraftRef.current = ''
}
- if (full.startsWith('/') && slashRef.current(full)) {
+ if (full.startsWith('/')) {
+ appendMessage({ role: 'system', text: full, kind: 'slash' })
+ pushHistory(full)
+ slashRef.current(full)
clearInput()
return
@@ -793,7 +796,7 @@ export function App({ gw }: { gw: GatewayClient }) {
send(full)
},
// eslint-disable-next-line react-hooks/exhaustive-deps
- [busy, enqueue, gw, listPasteIds, pastes, resolvePasteTokens, sid]
+ [appendMessage, busy, enqueue, gw, listPasteIds, pastes, pushHistory, resolvePasteTokens, sid]
)
// ── Input handling ───────────────────────────────────────────────
diff --git a/ui-tui/src/components/messageLine.tsx b/ui-tui/src/components/messageLine.tsx
index 91d1fe8c33..8b8b30894b 100644
--- a/ui-tui/src/components/messageLine.tsx
+++ b/ui-tui/src/components/messageLine.tsx
@@ -31,6 +31,10 @@ export const MessageLine = memo(function MessageLine({
const { body, glyph, prefix } = ROLE[msg.role](t)
const content = (() => {
+ if (msg.kind === 'slash') {
+ return {msg.text}
+ }
+
if (msg.role === 'assistant') {
return hasAnsi(msg.text) ? {msg.text} :
}
@@ -41,9 +45,11 @@ export const MessageLine = memo(function MessageLine({
return (
{head}
+
[long message]
+
{rest.join('')}
)
@@ -53,7 +59,7 @@ export const MessageLine = memo(function MessageLine({
})()
return (
-
+
{msg.thinking && (
💭 {msg.thinking.replace(/\n/g, ' ').slice(0, 200)}
diff --git a/ui-tui/src/components/thinking.tsx b/ui-tui/src/components/thinking.tsx
index 5dbcfdab47..f4f5130eec 100644
--- a/ui-tui/src/components/thinking.tsx
+++ b/ui-tui/src/components/thinking.tsx
@@ -3,15 +3,21 @@ import { memo, useEffect, useState } from 'react'
import spinners, { type BrailleSpinnerName } from 'unicode-animations'
import { FACES, TOOL_VERBS, VERBS } from '../constants.js'
-import { isToolTrailResultLine, lastCotTrailIndex } from '../lib/text.js'
+import {
+ isToolTrailResultLine,
+ lastCotTrailIndex,
+ pick,
+ scaleHex,
+ THINKING_COT_FADE,
+ THINKING_COT_MAX,
+ thinkingCotTail
+} from '../lib/text.js'
import type { Theme } from '../theme.js'
import type { ActiveTool, ActivityItem } from '../types.js'
const THINK: BrailleSpinnerName[] = ['helix', 'breathe', 'orbit', 'dna', 'waverows', 'snake', 'pulse']
const TOOL: BrailleSpinnerName[] = ['cascade', 'scan', 'diagswipe', 'fillsweep', 'rain', 'columns', 'sparkle']
-const pick = (a: T[]) => a[Math.floor(Math.random() * a.length)]!
-
const tone = (item: ActivityItem, t: Theme) =>
item.tone === 'error' ? t.color.error : item.tone === 'warn' ? t.color.warn : t.color.dim
@@ -128,7 +134,8 @@ export const Thinking = memo(function Thinking({ reasoning, t }: { reasoning: st
return () => clearInterval(id)
}, [])
- const tail = reasoning.slice(-160).replace(/\n/g, ' ')
+ const tail = thinkingCotTail(reasoning)
+ const clipped = reasoning.length > THINKING_COT_MAX
return (
<>
@@ -138,8 +145,17 @@ export const Thinking = memo(function Thinking({ reasoning, t }: { reasoning: st
{tail ? (
-
- 💭 {tail}
+
+ {clipped &&
+ Array.from({ length: Math.min(THINKING_COT_FADE, tail.length) }, (_, i) => (
+
+ {tail[i]}
+
+ ))}
+
+
+ {clipped ? tail.slice(THINKING_COT_FADE) : tail}
+
) : null}
>
diff --git a/ui-tui/src/lib/text.ts b/ui-tui/src/lib/text.ts
index ddb6f9fdd4..7f835c0cd4 100644
--- a/ui-tui/src/lib/text.ts
+++ b/ui-tui/src/lib/text.ts
@@ -53,6 +53,23 @@ export const lastCotTrailIndex = (trail: readonly string[]) => {
return -1
}
+export const THINKING_COT_MAX = 160
+export const THINKING_COT_FADE = 5
+
+export const thinkingCotTail = (reasoning: string) => reasoning.replace(/\n/g, ' ').slice(-THINKING_COT_MAX)
+
+/** Scale #RRGGBB by k ∈ [0,1] — used for left-edge fade toward terminal bg. */
+export const scaleHex = (hex: string, k: number) => {
+ const h = hex.replace('#', '')
+
+ const ch = (o: number) =>
+ Math.round(parseInt(h.slice(o, o + 2), 16) * k)
+ .toString(16)
+ .padStart(2, '0')
+
+ return `#${ch(0)}${ch(2)}${ch(4)}`
+}
+
export const estimateRows = (text: string, w: number, compact = false) => {
let inCode = false
let rows = 0
diff --git a/ui-tui/src/types.ts b/ui-tui/src/types.ts
index 3254c2674a..1cfa035403 100644
--- a/ui-tui/src/types.ts
+++ b/ui-tui/src/types.ts
@@ -24,7 +24,7 @@ export interface ClarifyReq {
export interface Msg {
role: Role
text: string
- kind?: 'intro'
+ kind?: 'intro' | 'slash'
info?: SessionInfo
thinking?: string
tools?: string[]