一键导入
rendering-tests
Use when debugging TUI layout issues, writing regression tests for rendering problems, or analyzing View() output structure with RenderingAnalyzer.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when debugging TUI layout issues, writing regression tests for rendering problems, or analyzing View() output structure with RenderingAnalyzer.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Capture external browser views (GitHub tickets, JIRA, Teams threads) using rrweb and Playwright, applying spotlights and captions, and embedding them into slidey decks.
Produce a deterministic, no-LLM demo / tour video of the kitsoki web UI (plus per-scene screenshots and a shareable MP4 / GIF / contact sheet) by driving a real `kitsoki web` server through Playwright. Use when asked to make, record, refresh, or author a tour demo video, feature-spotlight tour, walkthrough video, demo, or screen-capture of the kitsoki browser UI — whether a tour of one feature (golden example: agent-actions), the generic onboarding tour, or a full-product walkthrough. Also covers turning a REAL LLM-driven dogfood session into a deterministic demo: generating the no-LLM flow fixture + host cassette from a recorded trace via `kitsoki trace to-flow` (no hand-authoring, no LLM re-interpretation). Triggers on phrasings like "make a tour demo video", "record a demo of <feature>", "feature tour video", "walkthrough video", "turn this dogfood trace/session into a demo video".
Validate UI evidence (a screenshot for simple cases, a video for complex flows) against the bug or plan being verified plus usage scenarios — the inverse of kitsoki-ui-demo. Picks the evidence form by complexity, extracts deterministic frames, has a read-only `claude` vision agent judge each scenario against cited frames AND whether the evidence is complete for the stated bug/plan, adversarially re-checks every pass, and emits a gated qa-report.md + verdict.json. Use when asked to QA / review / validate / sign off on a demo, walkthrough, screenshot, or bug-fix proof, or to gate one in CI.
Run or refine the Kitsoki product-journey QA pipeline: generate 10-GitHub-repo natural-use matrices, create persona/scenario run bundles, drive or hand off visual/Kitsoki MCP evidence capture, run no-LLM replay/dogfood gates, review/validate bundles, and produce Slidey decks with playback evidence. Use when asked to dogfood onboarding, bugfix, PRD/design, feature implementation, product-discovery, product-bug, personas, or the reusable product-journey QA agent/story.
Drive a Kitsoki story as a skeptical practitioner and validate the exploratory QA path against local project corpora. Use when asked to inspect a product flow, run a skeptical walkthrough, or validate an exploratory QA pass against one or more open source projects with reproducible evidence and a written report.
Tune and validate a provider/model task in Kitsoki with a controlled, repeatable harness. Use when a model performs poorly on a story step, when a provider quota/rate behavior needs task hardening, or when the user asks to improve GLM/GPT/Claude performance with traceable artifacts. Produces an offline-scored benchmark report, a Slidey deck, and concrete story/prompt/tool changes without calling live LLMs from tests.
| name | rendering-tests |
| description | Use when debugging TUI layout issues, writing regression tests for rendering problems, or analyzing View() output structure with RenderingAnalyzer. |
Use this when: Debugging TUI layout issues, writing regression tests for rendering problems, or analyzing View() output structure.
For any rendering bug, use the test apparatus to:
// 1. Get the output
output := m.View()
// 2. Create an analyzer
analyzer := tui.NewRenderingAnalyzer(t, output)
// 3. Verify structure
analyzer.AssertLineSeparation("routing status", "queue indicator")
analyzer.AssertNoHorizontalConcat("status", "queue:")
analyzer.AssertContains("expected text")
// 4. Debug if needed
analyzer.Dump() // See line-by-line output
| Method | Use For |
|---|---|
AssertLineSeparation(text1, text2) | Prevent overlap bugs — verify items are on different lines |
AssertNoHorizontalConcat(text1, text2) | Detect horizontal concatenation when should be vertical |
AssertContains(text) | Verify expected content is in output |
AssertLineCount(n) | Verify exact line count |
AssertStructure(elements...) | Verify output contains expected elements |
Dump() | Print full output with line numbers for debugging |
StrippedLines() | Get output with ANSI codes removed |
Bug: Queue indicator appeared on same terminal line as routing status.
Test that catches it:
func TestQueueIndicatorNotOverlapped(t *testing.T) {
m := newTestModel()
m.mode = ModeAwaitingLLM
m.transcript.AppendLive("↳ resolved: view")
output := m.View()
analyzer := tui.NewRenderingAnalyzer(t, output)
// This test fails if the bug reoccurs
analyzer.AssertLineSeparation("resolved", "⏳")
analyzer.AssertNoHorizontalConcat("resolved", "queue:")
}
Symptom: "last content framework: awaiting" on one line
Test:
analyzer.AssertLineSeparation("content above", "framework: awaiting")
Root Cause: JoinVertical padding or double newlines causing cursor misalignment.
Symptom: Indicator and prompt on same line when should be separate
Test:
analyzer.AssertLineSeparation("⏳ queue", "↳ prompt")
analyzer.AssertLineCount(expectedLines)
Symptom: Weird characters in output or misaligned styling
Test:
analyzer.StrippedLines() // Analyze without ANSI codes
analyzer.AssertContains("visible text") // Works with ANSI
Example: "Queue indicator appears on same line as routing status"
func TestBugName(t *testing.T) {
m := newTestModel()
m.mode = ModeAwaitingLLM
m.transcript.AppendLive("↳ status")
m.inputQueue = append(m.inputQueue, "queued input")
output := m.View()
analyzer := tui.NewRenderingAnalyzer(t, output)
// These should FAIL if bug exists, PASS if fixed
analyzer.AssertLineSeparation("status", "queue:")
analyzer.AssertNoHorizontalConcat("status", "queue:")
}
analyzer.Dump()
Shows:
Example output:
=== Rendered Output ===
Line 0 (bytes= 35): "↳ resolved: deterministic · 1.00"
Line 1 (bytes=240): "─────────────────────────────…"
Line 2 (bytes= 60): "⏳ ⠋ thinking… · queue: 0"
Line 3 (bytes= 14): "↳ test input"
If bug only manifests at certain widths:
for _, width := range []int{80, 120, 200} {
t.Run(fmt.Sprintf("Width%d", width), func(t *testing.T) {
m.SetWidth(width)
output := m.View()
analyzer := tui.NewRenderingAnalyzer(t, output)
analyzer.AssertLineSeparation("text1", "text2")
})
}
rendering_test_utils.go — RenderingAnalyzer implementationrendering_regression_test.go — Example tests (copy these patterns)docs/tui/rendering-tests.md — Full documentation❌ Using string Contains for layout — use AssertLineSeparation() instead
❌ Forgetting about ANSI codes — analyzer handles them, but be aware of byte vs visible length
❌ Testing implementation, not behavior — test what users see
❌ Tests too specific — focus on critical structure, not exact positions
The mistake: Testing View() output in isolation and declaring bugs fixed based on test passes.
When this fails: Bugs involving:
User reported: "Queue indicator appears at end of log lines"
2026/05/29 05:40:41 INFO metamode.oracle.event ... ⏳ ⠏ running…
Isolated test (❌ can't catch it):
func TestQueueIndicator(t *testing.T) {
output := m.View()
analyzer := tui.NewRenderingAnalyzer(t, output)
analyzer.AssertLineSeparation("queue", "status") // ✅ Passes!
// But this doesn't test: slog writes to stderr while View() renders
}
Integration test (✅ catches it):
func TestSlogDoesNotMixWithQueueIndicator(t *testing.T) {
// Capture actual stderr output
var stderrBuf strings.Builder
oldDefault := slog.Default()
slog.SetDefault(slog.New(slog.NewTextHandler(&stderrBuf, nil)))
defer slog.SetDefault(oldDefault)
// Simulate concurrent logging + rendering (where the bug lives)
go func() {
for i := 0; i < 50; i++ {
slog.Info("metamode.oracle.event", "type", "system")
time.Sleep(time.Microsecond)
}
}()
for i := 0; i < 50; i++ {
m.View()
time.Sleep(time.Microsecond)
}
// Assert on ACTUAL output that reaches the terminal
stderrLines := strings.Split(stderrBuf.String(), "\n")
for _, line := range stderrLines {
if strings.Contains(line, "INFO") && strings.Contains(line, "⏳") {
t.Fatalf("queue indicator mixed into log: %q", line)
}
}
}
When to use isolated tests:
When you must add integration tests:
If the user's symptom involves:
Action: Don't just run the isolated test suite. Build a test that:
Before declaring a bug fixed:
This is non-negotiable. A passing test that would pass even without your fix is not testing the fix.
# All rendering tests
go test ./internal/tui -run Rendering -v
# Specific regression test
go test ./internal/tui -run TestQueueIndicatorNotOverlapped -v
# Show full output
go test ./internal/tui -run TestName -v
The bug was caused by lipgloss.JoinVertical() applying width-based padding that caused unintended horizontal alignment of multi-line parts.
Fix: Use simple string concatenation instead.
// ❌ Before (causes padding issues)
return lipgloss.JoinVertical(lipgloss.Left, parts...)
// ✅ After (preserves structure)
var output strings.Builder
for _, part := range parts {
trimmed := strings.TrimRight(part, "\n")
if trimmed != "" {
if output.Len() > 0 {
output.WriteString("\n")
}
output.WriteString(trimmed)
}
}
return output.String()
This testing apparatus prevents this class of bugs from recurring.