mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-13 19:20:06 +00:00
0313fa03ad
* fix(ui): show pasted image previews in input and transcript The half-block thumbnail preview added in #47 rendered but was clipped off the bottom of the screen, and submitted images showed only a text badge in the conversation history. - Mark the layout dirty when clipboardImageMsg / thumbnailReadyMsg reach the parent, so distributeHeight re-measures the now-taller input region instead of keeping a stale height that pushed the preview off-screen - Render thumbnail previews in the transcript after a user message, appended as a verbatim ScrollList item (raw ANSI half-blocks would be mangled if folded into the word-wrapped user text block) - Render transcript previews asynchronously via a tea.Cmd so decode + resample never blocks the Bubble Tea event loop - Add regression tests covering the input layout recompute and the transcript preview flow * fix(ui): anchor transcript image preview to its user message - Insert the async thumbnail preview directly after the originating user message (tracked via anchorID) instead of appending, so a streamed assistant reply that lands first no longer pushes the preview out of place - Make the layout regression test deterministic by forcing a truecolor profile, avoiding flakes on low-color CI terminals where the thumbnail would render empty - Add tests for anchored insertion and the unknown-anchor append fallback
86 lines
2.4 KiB
Go
86 lines
2.4 KiB
Go
package ui
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
tea "charm.land/bubbletea/v2"
|
|
uicore "github.com/mark3labs/kit/internal/ui/core"
|
|
)
|
|
|
|
// drainCmds runs a tea.Cmd chain back through m.Update like the BubbleTea
|
|
// event loop, expanding batches, until no further messages are produced.
|
|
func drainCmds(t *testing.T, m *AppModel, cmd tea.Cmd) *AppModel {
|
|
t.Helper()
|
|
queue := []tea.Cmd{cmd}
|
|
for i := 0; i < 50 && len(queue) > 0; i++ {
|
|
c := queue[0]
|
|
queue = queue[1:]
|
|
if c == nil {
|
|
continue
|
|
}
|
|
msg := c()
|
|
if msg == nil {
|
|
continue
|
|
}
|
|
if batch, ok := msg.(tea.BatchMsg); ok {
|
|
queue = append(queue, batch...)
|
|
continue
|
|
}
|
|
updated, nc := m.Update(msg)
|
|
m = updated.(*AppModel)
|
|
_ = m.View()
|
|
if nc != nil {
|
|
queue = append(queue, nc)
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
|
|
func measuredInputHeight(m *AppModel) int {
|
|
rendered := m.renderInput()
|
|
if rendered == "" {
|
|
return 0
|
|
}
|
|
return strings.Count(rendered, "\n") + 1
|
|
}
|
|
|
|
// TestPendingThumbnailTriggersLayoutRecompute is a regression test for the bug
|
|
// where a pasted image's async half-block preview rendered but was clipped off
|
|
// the bottom of the screen: the thumbnail arrives via thumbnailReadyMsg after
|
|
// distributeHeight already measured the input region without it. The parent
|
|
// must mark the layout dirty so the (now taller) input is re-measured.
|
|
func TestPendingThumbnailTriggersLayoutRecompute(t *testing.T) {
|
|
// Force a truecolor profile so imagepreview.Render deterministically
|
|
// produces a thumbnail regardless of the CI terminal's color support.
|
|
// Without this, a low-color test environment yields an empty preview and
|
|
// the glyph / height assertions below would flake.
|
|
t.Setenv("TERM", "xterm-256color")
|
|
t.Setenv("COLORTERM", "truecolor")
|
|
t.Setenv("NO_COLOR", "")
|
|
|
|
real := NewInputComponent(80, nil)
|
|
m, _, _ := newTestAppModel(nil)
|
|
m.input = real
|
|
m = sendMsg(m, tea.WindowSizeMsg{Width: 80, Height: 24})
|
|
|
|
heightBefore := measuredInputHeight(m)
|
|
|
|
updated, cmd := m.Update(clipboardImageMsg{image: &uicore.ImageAttachment{
|
|
Data: makeTestPNG(t, 16, 16),
|
|
MediaType: "image/png",
|
|
}})
|
|
m = updated.(*AppModel)
|
|
_ = m.View()
|
|
m = drainCmds(t, m, cmd)
|
|
|
|
heightAfter := measuredInputHeight(m)
|
|
if heightAfter <= heightBefore {
|
|
t.Errorf("input region should grow to fit the thumbnail (before=%d after=%d)", heightBefore, heightAfter)
|
|
}
|
|
|
|
if !strings.Contains(m.View().Content, "▀") {
|
|
t.Error("parent View should contain the half-block thumbnail (was clipped or not rendered)")
|
|
}
|
|
}
|