fix(tui): render selected chat bubbles clearly

This commit is contained in:
Vincent Koc 2026-05-03 15:38:10 -07:00
parent 5a04347124
commit 8d641e7c30
No known key found for this signature in database
3 changed files with 23 additions and 6 deletions

View File

@ -34,5 +34,6 @@
- Bring shared TUI detail and sort behavior closer to `gitcrawl`: archives open newest-first, group count headers sort like `cnt*`, selected chat messages render before surrounding conversation context, document previews appear before metadata, and detail fields use `key: value` labels.
- Keep split-width member tables readable by rendering compact dates instead of truncated ISO timestamps.
- Prioritize gitcrawl-style footer muscle-memory controls in compact tmux panes before app-specific extras.
- Render selected chat message bodies with the same transcript marker as their speaker line so detail panes read more like chat.
- Force the Bubble Tea program to shut down on terminal signals so interrupted TUIs restore terminal modes and do not leave orphaned tmux panes.
- Rename the public package nouns to `config`, `store`, `snapshot`, `mirror`, `state`, `output`, `tui`, and `cache`.

View File

@ -3699,7 +3699,7 @@ func (m model) chatDetailLines(item Item, width int) []string {
lines = append(lines, dim(meta))
}
if message := chatBodyText(item); message != "" {
lines = append(lines, "", dim(tuiRule(width)), bold("Message"))
lines = append(lines, "", dim(tuiRule(width)), bold("Selected Message"))
lines = appendLimitedDetailLines(lines, chatBubbleLines(item, message, true, width), detailBodyLimit(m.compactDetail))
}
if title, thread := m.threadSection(item, width); len(thread) > 0 {
@ -3896,6 +3896,20 @@ func indentMarkdownLines(value string, indent, width int) []string {
return out
}
func prefixedMarkdownLines(value, prefix string, width int) []string {
prefix = strings.TrimRight(prefix, "\t")
raw := markdownLines(value, maxInt(8, width-lipgloss.Width(prefix)))
out := make([]string, 0, len(raw))
for _, line := range raw {
if line == "" {
out = append(out, strings.TrimRight(prefix, " "))
continue
}
out = append(out, prefix+line)
}
return out
}
func detailContextLines(item Item, includeTitle bool) []string {
var lines []string
fields := []string{
@ -4043,16 +4057,18 @@ func sortChatIndexesByTime(items []Item, indexes []int) {
func chatBubbleLines(item Item, text string, selected bool, width int) []string {
var lines []string
prefix := " "
bodyPrefix := " "
if selected {
prefix = "> "
bodyPrefix = "> "
}
header := joinNonEmpty([]string{itemAuthor(item), shortTimestamp(firstNonEmpty(item.CreatedAt, item.UpdatedAt))}, " ")
header := joinNonEmpty([]string{itemAuthor(item), shortTimestamp(firstNonEmpty(item.CreatedAt, item.UpdatedAt)), rowAge(item)}, " ")
if header != "" {
lines = append(lines, prefix+header)
}
body := indentMarkdownLines(text, lipgloss.Width(prefix)+2, width)
body := prefixedMarkdownLines(text, bodyPrefix, width)
if len(body) == 0 {
body = []string{strings.Repeat(" ", lipgloss.Width(prefix)+2) + "(empty)"}
body = []string{bodyPrefix + "(empty)"}
}
lines = append(lines, body...)
return lines

View File

@ -481,7 +481,7 @@ func TestChatDetailUsesTranscriptShapeBeforeMetadata(t *testing.T) {
}
lines := m.detailLines(item)
joined := strings.Join(lines, "\n")
for _, want := range []string{"general bob", "Thread 1-2/2", "alice", "root message", "> bob", "reply message", "Properties", "url: https://example.com/thread", "IDs", "parent: m1"} {
for _, want := range []string{"general bob", "Selected Message", "Thread 1-2/2", "alice", "root message", "> bob", "> reply message", "Properties", "url: https://example.com/thread", "IDs", "parent: m1"} {
if !strings.Contains(joined, want) {
t.Fatalf("chat detail missing %q:\n%s", want, joined)
}
@ -510,7 +510,7 @@ func TestChatDetailRendersMarkdownTranscriptLikeGitcrawl(t *testing.T) {
t.Fatal("missing selected item")
}
joined := stripANSI(strings.Join(m.detailLinesForWidth(item, 52), "\n"))
for _, want := range []string{"Plan", "- ship columns", "polish preview <https://example.com>", "> agreed", "done", "Properties", "IDs"} {
for _, want := range []string{"Plan", "- ship columns", "polish preview <https://example.com>", "> > agreed", "> done", "Properties", "IDs"} {
if !strings.Contains(joined, want) {
t.Fatalf("markdown chat detail missing %q:\n%s", want, joined)
}