Skip to content

Commit 99a34fa

Browse files
committed
feat(tui): changed prompt focus setting to allow interactive / popup during streaming
1 parent faa893f commit 99a34fa

4 files changed

Lines changed: 156 additions & 31 deletions

File tree

src-rust/crates/core/src/share_export/template.css

Lines changed: 116 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -212,48 +212,139 @@ nav {
212212

213213
/* Tool calls / tool results / thinking — collapsible details */
214214
details.tool-call, details.tool-result, details.thinking {
215-
margin: 0.7em 0;
215+
margin: 0.8em 0;
216216
border: 1px solid var(--surface-border);
217-
border-radius: 6px;
218-
background: rgba(0, 0, 0, 0.35);
219-
font-size: 0.82rem;
217+
border-radius: 8px;
218+
background: rgba(10, 10, 10, 0.5);
219+
font-size: 0.85rem;
220+
overflow: hidden;
221+
transition: border-color 0.2s, box-shadow 0.2s;
222+
}
223+
details.tool-call {
224+
border-color: rgba(233, 30, 99, 0.2);
225+
background: linear-gradient(to right, rgba(233, 30, 99, 0.02), rgba(0, 0, 0, 0.2));
226+
}
227+
details.tool-call:hover {
228+
border-color: rgba(233, 30, 99, 0.4);
229+
box-shadow: 0 0 12px rgba(233, 30, 99, 0.05);
230+
}
231+
details.tool-result {
232+
border-color: rgba(255, 255, 255, 0.08);
233+
background: rgba(255, 255, 255, 0.01);
234+
}
235+
details.tool-result:hover {
236+
border-color: rgba(255, 255, 255, 0.15);
237+
}
238+
details.tool-result.error {
239+
border-color: rgba(255, 95, 86, 0.3);
240+
background: rgba(255, 95, 86, 0.02);
241+
}
242+
details.tool-result.error:hover {
243+
border-color: rgba(255, 95, 86, 0.5);
244+
box-shadow: 0 0 12px rgba(255, 95, 86, 0.05);
245+
}
246+
details.thinking {
247+
border-color: rgba(255, 255, 255, 0.06);
248+
border-style: dashed;
249+
background: transparent;
220250
}
251+
details.thinking:hover {
252+
border-color: rgba(255, 255, 255, 0.12);
253+
}
254+
221255
details > summary {
222256
cursor: pointer;
223-
padding: 0.55rem 0.9rem;
224-
color: var(--text-secondary);
257+
padding: 0.6rem 0.95rem;
258+
color: var(--text-primary);
225259
user-select: none;
226260
list-style: none;
227261
font-family: var(--font-mono);
228262
display: flex;
229263
align-items: center;
230-
gap: 0.5rem;
264+
gap: 0.6rem;
265+
transition: background 0.2s;
266+
}
267+
details > summary:hover {
268+
background: rgba(255, 255, 255, 0.02);
231269
}
232270
details > summary::-webkit-details-marker { display: none; }
233-
details[open] > summary { border-bottom: 1px solid var(--surface-border); }
234-
details.tool-call > summary::before {
235-
content: '▸'; color: var(--accent); margin-right: 0.1rem;
236-
transition: transform 0.15s;
271+
details[open] > summary {
272+
border-bottom: 1px solid var(--surface-border);
273+
background: rgba(255, 255, 255, 0.01);
237274
}
238-
details.tool-call[open] > summary::before { transform: rotate(90deg); }
239-
details.tool-call > summary .tool-name {
275+
276+
/* Rotation arrow */
277+
details > summary::before {
278+
content: '▸';
279+
display: inline-block;
280+
color: var(--text-muted);
281+
transition: transform 0.15s ease, color 0.15s ease;
282+
font-size: 0.75rem;
283+
margin-right: 0.1rem;
284+
}
285+
details[open] > summary::before {
286+
transform: rotate(90deg);
287+
color: var(--text-secondary);
288+
}
289+
details.tool-call[open] > summary::before {
240290
color: var(--accent);
241-
font-weight: 500;
242291
}
243-
details.tool-result > summary::before {
244-
content: '↳'; color: var(--text-muted); margin-right: 0.1rem;
292+
details.tool-result.error[open] > summary::before {
293+
color: var(--error);
294+
}
295+
296+
/* Badges styling */
297+
.badge {
298+
font-size: 0.65rem;
299+
text-transform: uppercase;
300+
font-weight: 700;
301+
letter-spacing: 0.05em;
302+
padding: 2px 6px;
303+
border-radius: 4px;
304+
display: inline-flex;
305+
align-items: center;
306+
line-height: 1;
307+
user-select: none;
308+
}
309+
.badge.tool-badge {
310+
background: rgba(233, 30, 99, 0.1);
311+
color: var(--accent);
312+
border: 1px solid rgba(233, 30, 99, 0.25);
313+
}
314+
.badge.result-badge {
315+
background: rgba(255, 255, 255, 0.05);
316+
color: var(--text-secondary);
317+
border: 1px solid rgba(255, 255, 255, 0.1);
318+
}
319+
.badge.result-badge.error {
320+
background: rgba(255, 95, 86, 0.1);
321+
color: var(--error);
322+
border: 1px solid rgba(255, 95, 86, 0.25);
245323
}
246-
details.tool-result.error { border-color: var(--error); }
247-
details.tool-result.error > summary { color: var(--error); }
248-
details.thinking { background: rgba(255, 255, 255, 0.015); font-style: italic; }
249-
details.thinking > summary::before {
250-
content: '◈'; color: var(--text-muted); margin-right: 0.1rem;
324+
.badge.thinking-badge {
325+
background: rgba(255, 255, 255, 0.03);
326+
color: var(--text-muted);
327+
border: 1px solid rgba(255, 255, 255, 0.06);
328+
}
329+
330+
/* Label details */
331+
details.tool-call > summary .tool-name {
332+
color: var(--text-primary);
333+
font-weight: 600;
334+
}
335+
details.tool-result > summary .result-label {
336+
color: var(--text-secondary);
337+
font-weight: 400;
338+
}
339+
details.tool-result.error > summary .result-label {
340+
color: var(--error);
251341
}
252342

253343
details > .body {
254-
padding: 0.7rem 0.9rem;
344+
padding: 0.8rem 1rem;
255345
font-family: var(--font-mono);
256-
font-size: 0.78rem;
346+
font-size: 0.8rem;
347+
background: rgba(0, 0, 0, 0.2);
257348
}
258349
details > .body pre {
259350
white-space: pre-wrap;
@@ -265,7 +356,8 @@ details > .body pre {
265356
border: none;
266357
padding: 0;
267358
color: var(--text-secondary);
268-
font-size: 0.78rem;
359+
font-size: 0.8rem;
360+
line-height: 1.5;
269361
}
270362

271363
/* Local shell command output (! command) — terminal style */

src-rust/crates/core/src/share_export/template.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,17 @@
194194
var d = document.createElement('details');
195195
d.className = 'tool-call';
196196
var s = document.createElement('summary');
197-
s.innerHTML = '▸ <span class="tool-name">' + escapeHtml(block.name || 'tool') + '</span>';
197+
198+
var badge = document.createElement('span');
199+
badge.className = 'badge tool-badge';
200+
badge.textContent = 'tool call';
201+
s.appendChild(badge);
202+
203+
var nameSpan = document.createElement('span');
204+
nameSpan.className = 'tool-name';
205+
nameSpan.textContent = block.name || 'tool';
206+
s.appendChild(nameSpan);
207+
198208
d.appendChild(s);
199209
var b = document.createElement('div'); b.className = 'body';
200210
var pre = document.createElement('pre');
@@ -211,7 +221,17 @@
211221
d.className = 'tool-result' + (isError ? ' error' : '');
212222
d.open = isError;
213223
var s = document.createElement('summary');
214-
s.textContent = isError ? '✗ tool result (error)' : '↳ tool result';
224+
225+
var badge = document.createElement('span');
226+
badge.className = 'badge result-badge' + (isError ? ' error' : '');
227+
badge.textContent = isError ? 'error' : 'result';
228+
s.appendChild(badge);
229+
230+
var labelSpan = document.createElement('span');
231+
labelSpan.className = 'result-label';
232+
labelSpan.textContent = 'tool output';
233+
s.appendChild(labelSpan);
234+
215235
d.appendChild(s);
216236
var b = document.createElement('div'); b.className = 'body';
217237
var text;
@@ -234,7 +254,12 @@
234254
var d = document.createElement('details');
235255
d.className = 'thinking';
236256
var s = document.createElement('summary');
237-
s.textContent = '💭 thinking';
257+
258+
var badge = document.createElement('span');
259+
badge.className = 'badge thinking-badge';
260+
badge.textContent = 'thinking';
261+
s.appendChild(badge);
262+
238263
d.appendChild(s);
239264
var b = document.createElement('div'); b.className = 'body';
240265
var pre = document.createElement('pre');

src-rust/crates/tui/src/app.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4114,15 +4114,17 @@ impl App {
41144114
self.prompt_input.cursor = self.prompt_input.text.len();
41154115
self.sync_legacy_prompt_fields();
41164116
}
4117-
KeyCode::Tab if !self.is_streaming => {
4117+
KeyCode::Tab => {
41184118
if !self.prompt_input.suggestions.is_empty() {
4119-
// Accept slash-command suggestion
4119+
// Accept slash-command suggestion. Allowed while streaming
4120+
// so the typeahead popup is interactive even when a turn
4121+
// is in flight — Enter then queues the completed command.
41204122
if self.prompt_input.suggestion_index.is_none() {
41214123
self.prompt_input.suggestion_index = Some(0);
41224124
}
41234125
self.prompt_input.accept_suggestion();
41244126
self.refresh_prompt_input();
4125-
} else if self.prompt_input.is_empty() {
4127+
} else if !self.is_streaming && self.prompt_input.is_empty() {
41264128
// Cycle agent mode: build → plan → explore → build
41274129
self.cycle_agent_mode();
41284130
self.rustle_look_down();

src-rust/crates/tui/src/render.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,12 @@ pub fn render_app(frame: &mut Frame, app: &App) {
383383

384384
let prompt_focused =
385385
!app.is_streaming && app.permission_request.is_none() && !app.history_search_overlay.visible;
386+
// Suggestions popup tracks whether the prompt accepts input, not whether
387+
// it is the focused widget. Text entry is allowed during streaming so the
388+
// user can queue the next message, so the typeahead popup must follow
389+
// that same affordance.
390+
let suggestions_visible =
391+
app.permission_request.is_none() && !app.history_search_overlay.visible;
386392
let status_visible = should_render_status_row(app);
387393
// One blank separator row above the status/input area when status is active,
388394
// matching the visual breathing room in the TS layout.
@@ -404,7 +410,7 @@ pub fn render_app(frame: &mut Frame, app: &App) {
404410
} else {
405411
0
406412
};
407-
let suggestions_height = if prompt_focused && !app.prompt_input.suggestions.is_empty() {
413+
let suggestions_height = if suggestions_visible && !app.prompt_input.suggestions.is_empty() {
408414
app.prompt_input.suggestions.len().min(5) as u16
409415
} else {
410416
0

0 commit comments

Comments
 (0)