From 3eb778f31b18ba342491b12ce200047308701b35 Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Wed, 20 May 2026 18:12:18 -0400 Subject: [PATCH 1/3] =?UTF-8?q?fix(tui):=20clean=20up=20stream=20row=20den?= =?UTF-8?q?sity=20=E2=80=94=20drop=20ID,=20fix=20newline=20leak,=20align?= =?UTF-8?q?=20margins?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ID cluttered rows and caused wrapping on long entries. Body newlines leaked into stream rendering extra unindented lines. Cursor glyph shifted selected rows 1 col right of unselected. Remove ID from all row renderers (detail pane already shows it), collapse multiline body to first line, cap tags to 2 in stream, and reserve cursor column on unselected rows for consistent alignment. --- internal/tui/absorb.go | 10 ++++++---- internal/tui/cards.go | 10 ++++++---- internal/tui/list.go | 18 +++++++++--------- internal/tui/styles.go | 2 +- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/internal/tui/absorb.go b/internal/tui/absorb.go index eb51ce3..dab374d 100644 --- a/internal/tui/absorb.go +++ b/internal/tui/absorb.go @@ -108,12 +108,14 @@ func (a absorbModel) visibleCount() int { func renderAbsorbSource(e *db.Entity, maxWidth int) string { glyph := glyphStyle.Render(display.DisplayGlyph(e.Glyph, e.CardType)) - id := idStyle.Render("[" + display.FormatID(e.ID) + "]") body := e.Body if e.Title != nil { body = *e.Title } + if idx := strings.IndexByte(body, '\n'); idx >= 0 { + body = body[:idx] + } var tags string if len(e.Tags) > 0 { @@ -125,11 +127,11 @@ func renderAbsorbSource(e *db.Entity, maxWidth int) string { tags = " " + strings.Join(tagParts, " ") } - line := fmt.Sprintf("%s %s%s %s", glyph, body, tags, id) + line := fmt.Sprintf("%s %s%s", glyph, body, tags) if maxWidth > 0 && len(stripAnsi(line)) > maxWidth { - body = truncate(body, maxWidth-20) - line = fmt.Sprintf("%s %s%s %s", glyph, body, tags, id) + body = truncate(body, maxWidth-6) + line = fmt.Sprintf("%s %s%s", glyph, body, tags) } return line diff --git a/internal/tui/cards.go b/internal/tui/cards.go index 72416cc..ed38dfb 100644 --- a/internal/tui/cards.go +++ b/internal/tui/cards.go @@ -301,12 +301,14 @@ func (c cardsModel) visibleCount() int { func renderCard(e *db.Entity, maxWidth int) string { glyph := glyphStyle.Render(display.DisplayGlyph(e.Glyph, e.CardType)) - id := idStyle.Render("[" + display.FormatID(e.ID) + "]") body := e.Body if e.Title != nil { body = *e.Title } + if idx := strings.IndexByte(body, '\n'); idx >= 0 { + body = body[:idx] + } affordance := detectAffordance(e) affordStr := "" @@ -335,11 +337,11 @@ func renderCard(e *db.Entity, maxWidth int) string { useStr = " " + useCountStyle.Render(fmt.Sprintf("%d×", e.UseCount)) } - line := fmt.Sprintf("%s %s%s%s%s %s", glyph, body, affordStr, extraStr, useStr, id) + line := fmt.Sprintf("%s %s%s%s%s", glyph, body, affordStr, extraStr, useStr) if maxWidth > 0 && len(stripAnsi(line)) > maxWidth { - body = truncate(body, maxWidth-30) - line = fmt.Sprintf("%s %s%s%s%s %s", glyph, body, affordStr, extraStr, useStr, id) + body = truncate(body, maxWidth-8) + line = fmt.Sprintf("%s %s%s%s%s", glyph, body, affordStr, extraStr, useStr) } return line diff --git a/internal/tui/list.go b/internal/tui/list.go index 8c95c96..696912c 100644 --- a/internal/tui/list.go +++ b/internal/tui/list.go @@ -204,23 +204,23 @@ func renderEntity(e *db.Entity, maxWidth int) string { } glyph := style.Render(glyphStr) - id := idStyle.Render("[" + display.FormatID(e.ID) + "]") - body := e.Body if e.Title != nil { body = *e.Title } + if idx := strings.IndexByte(body, '\n'); idx >= 0 { + body = body[:idx] + } var extras []string if e.Pinned { extras = append(extras, pinnedStyle.Render("•")) } if len(e.Tags) > 0 { - tagParts := make([]string, len(e.Tags)) - for i, t := range e.Tags { - tagParts[i] = tagStyle.Render("#" + t) + limit := min(2, len(e.Tags)) + for _, t := range e.Tags[:limit] { + extras = append(extras, tagStyle.Render("#"+t)) } - extras = append(extras, strings.Join(tagParts, " ")) } extraStr := "" @@ -228,11 +228,11 @@ func renderEntity(e *db.Entity, maxWidth int) string { extraStr = " " + strings.Join(extras, " ") } - line := fmt.Sprintf("%s %s%s %s", glyph, body, extraStr, id) + line := fmt.Sprintf("%s %s%s", glyph, body, extraStr) if maxWidth > 0 && len(stripAnsi(line)) > maxWidth { - body = truncate(body, maxWidth-20) - line = fmt.Sprintf("%s %s%s %s", glyph, body, extraStr, id) + body = truncate(body, maxWidth-6) + line = fmt.Sprintf("%s %s%s", glyph, body, extraStr) } return line diff --git a/internal/tui/styles.go b/internal/tui/styles.go index 94e31ca..637e0f5 100644 --- a/internal/tui/styles.go +++ b/internal/tui/styles.go @@ -17,7 +17,7 @@ var ( PaddingLeft(1) listItemStyle = lipgloss.NewStyle(). - PaddingLeft(2) + PaddingLeft(4) selectedItemStyle = lipgloss.NewStyle(). PaddingLeft(1). From 989aa86679b491f048c29c5c1dbf773185aca66a Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Wed, 20 May 2026 18:49:38 -0400 Subject: [PATCH 2/3] fix(tui): compute truncation budget from actual overhead, not magic numbers Tags wrapped past pane edge when detail split narrowed the list. Truncation used fixed constants that didn't account for real tag width. Now measures everything-except-body and gives body exactly what remains. --- internal/tui/absorb.go | 3 ++- internal/tui/cards.go | 3 ++- internal/tui/list.go | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/tui/absorb.go b/internal/tui/absorb.go index dab374d..2a46b41 100644 --- a/internal/tui/absorb.go +++ b/internal/tui/absorb.go @@ -130,7 +130,8 @@ func renderAbsorbSource(e *db.Entity, maxWidth int) string { line := fmt.Sprintf("%s %s%s", glyph, body, tags) if maxWidth > 0 && len(stripAnsi(line)) > maxWidth { - body = truncate(body, maxWidth-6) + overhead := len(stripAnsi(line)) - len([]rune(body)) + body = truncate(body, maxWidth-overhead) line = fmt.Sprintf("%s %s%s", glyph, body, tags) } diff --git a/internal/tui/cards.go b/internal/tui/cards.go index ed38dfb..442d13d 100644 --- a/internal/tui/cards.go +++ b/internal/tui/cards.go @@ -340,7 +340,8 @@ func renderCard(e *db.Entity, maxWidth int) string { line := fmt.Sprintf("%s %s%s%s%s", glyph, body, affordStr, extraStr, useStr) if maxWidth > 0 && len(stripAnsi(line)) > maxWidth { - body = truncate(body, maxWidth-8) + overhead := len(stripAnsi(line)) - len([]rune(body)) + body = truncate(body, maxWidth-overhead) line = fmt.Sprintf("%s %s%s%s%s", glyph, body, affordStr, extraStr, useStr) } diff --git a/internal/tui/list.go b/internal/tui/list.go index 696912c..87192df 100644 --- a/internal/tui/list.go +++ b/internal/tui/list.go @@ -231,7 +231,8 @@ func renderEntity(e *db.Entity, maxWidth int) string { line := fmt.Sprintf("%s %s%s", glyph, body, extraStr) if maxWidth > 0 && len(stripAnsi(line)) > maxWidth { - body = truncate(body, maxWidth-6) + overhead := len(stripAnsi(line)) - len([]rune(body)) + body = truncate(body, maxWidth-overhead) line = fmt.Sprintf("%s %s%s", glyph, body, extraStr) } From 533e086ffb6bf00ee7aabbfb666590830f6bd560 Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Wed, 20 May 2026 18:57:15 -0400 Subject: [PATCH 3/3] fix(tui): esc closes detail split when list is focused Previously esc in split view with list focus fell through to capture focus. Now closes the detail pane and stays in stream browse mode. --- internal/tui/model.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/tui/model.go b/internal/tui/model.go index 84a6049..5e51c8e 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -455,6 +455,9 @@ func (m model) updateBrowse(msg tea.KeyMsg) (tea.Model, tea.Cmd) { m.focus = focusList return m, nil } + m.splitDetail = false + m.recalcSizes() + return m, nil } if m.focus == focusDetail {