feat(tui): render structured artifacts as a summary, not raw JSON
Approval-card and preview JSON artifacts (the freestyle execution_plan, the analyst's analysis) rendered as a raw JSON dump. Add a JSON-aware preview: a scannable goal + numbered stage list for execution_plan, indented key/value lines for other objects, with light highlighting. Non-JSON previews fall back to the raw line view unchanged.
This commit is contained in:
@@ -94,19 +94,29 @@ func previewPlainLines(s string) []string {
|
||||
return strings.Split(strings.TrimRight(s, "\n"), "\n")
|
||||
}
|
||||
|
||||
// previewDisplayLines is the single source of truth for how a non-diff preview is laid out:
|
||||
// a scannable summary for structured (JSON) artifacts, raw lines otherwise. Both the renderer
|
||||
// and previewRowCount go through it so band sizing and scroll bounds match what's drawn.
|
||||
func previewDisplayLines(s string) []string {
|
||||
if lines, ok := artifactDisplayLines(s); ok {
|
||||
return lines
|
||||
}
|
||||
return previewPlainLines(s)
|
||||
}
|
||||
|
||||
// previewRowCount is the number of content rows a preview occupies — diff rows
|
||||
// for a real diff, plain lines otherwise. Drives band sizing and scroll bounds.
|
||||
func previewRowCount(s string) int {
|
||||
if isUnifiedDiff(s) {
|
||||
return len(parseUnifiedDiff(s))
|
||||
}
|
||||
return len(previewPlainLines(s))
|
||||
return len(previewDisplayLines(s))
|
||||
}
|
||||
|
||||
// renderPreviewLines renders non-diff preview text as dim plain lines (single
|
||||
// column), up to maxRows starting at off, each truncated to width.
|
||||
func (m Model) renderPreviewLines(content string, width, maxRows, off int) []string {
|
||||
lines := previewPlainLines(content)
|
||||
lines := previewDisplayLines(content)
|
||||
if off < 0 {
|
||||
off = 0
|
||||
}
|
||||
@@ -119,11 +129,34 @@ func (m Model) renderPreviewLines(content string, width, maxRows, off int) []str
|
||||
}
|
||||
out := make([]string, 0, end-off)
|
||||
for _, ln := range lines[off:end] {
|
||||
out = append(out, m.theme.span(truncate(ln, width), m.theme.P.Dim))
|
||||
out = append(out, m.colorizePreviewLine(ln, width))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// colorizePreviewLine highlights a summarized-artifact line: flush-left section headers (Goal,
|
||||
// Plan · N) in accent, the "→ produces" target picked out, everything else dim — so the structure
|
||||
// reads at a glance without a full JSON syntax highlighter. Plain (raw) previews stay uniformly dim.
|
||||
func (m Model) colorizePreviewLine(ln string, w int) string {
|
||||
t := m.theme
|
||||
ln = truncate(ln, w)
|
||||
if ln == "" {
|
||||
return ""
|
||||
}
|
||||
// Section header: no leading indent (e.g. "Goal", "Plan · 4 stages").
|
||||
if ln[0] != ' ' {
|
||||
return lipgloss.NewStyle().Foreground(t.P.Accent2).Background(t.P.Bg).Bold(true).Render(ln)
|
||||
}
|
||||
// Stage head: pick out the "→ produces" target after truncation so widths stay bounded.
|
||||
if i := strings.Index(ln, " → "); i >= 0 {
|
||||
head := t.span(ln[:i], t.P.FgStrong)
|
||||
arrow := t.span(" → ", t.P.Faint)
|
||||
prod := lipgloss.NewStyle().Foreground(t.P.OK).Background(t.P.Bg).Render(ln[i+len(" → "):])
|
||||
return head + arrow + prod
|
||||
}
|
||||
return t.span(ln, t.P.Dim)
|
||||
}
|
||||
|
||||
// hunkStarts reads the starting old/new line numbers from an @@ -a,b +c,d @@ marker.
|
||||
func hunkStarts(h string) (int, int) {
|
||||
old, newer := 1, 1
|
||||
|
||||
Reference in New Issue
Block a user