Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions cmd/mecatui/customization/defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,97 @@ func TestDefaultTitleTemplateElidesWideSessionTitle(t *testing.T) {
}
}

func TestCanonicalStatus_Scenario3_UnknownContextDoesNotClaimZeroPressure(t *testing.T) {
for _, tc := range []struct {
name string
footerCols int
want string
delegation Delegation
wantTeam bool
}{
{name: "full", footerCols: 120, want: "ctx ?/200K"},
{name: "compact", footerCols: 20, want: "ctx ?", delegation: Delegation{Team: LiveTeam{Working: 1, Total: 1}}, wantTeam: true},
{name: "minimal", footerCols: 11, want: "ctx ?", delegation: Delegation{Team: LiveTeam{Working: 1, Total: 1}}},
} {
t.Run(tc.name, func(t *testing.T) {
footer := stockFooter(t, Input{
Context: Context{Used: ContextAtom{Human: "?"}, Window: ContextAtom{Raw: 200_000, Human: "200K"}},
Delegation: tc.delegation,
Terminal: Terminal{FooterAvailCols: tc.footerCols},
})
got := statusSurfaceText(footer)
if !strings.Contains(got, tc.want) {
t.Fatalf("footer = %q, want unknown context containing %q", got, tc.want)
}
if tc.wantTeam {
if !strings.Contains(got, "⟳ 1/1") || strings.Contains(got, "working") {
t.Fatalf("footer = %q, want compact team cue without the full team ratio", got)
}
} else if strings.Contains(got, "⟳") {
t.Fatalf("footer = %q, retained compact delegation content instead of selecting minimal", got)
}
if strings.Contains(got, "ctx 0%") {
t.Fatalf("footer = %q, fabricated zero pressure for unknown context", statusSurfaceText(footer))
}
for _, span := range footer.Spans {
if span.Token == TokenSuccess || span.Token == TokenWarning {
t.Fatalf("footer = %#v, assigns a pressure token to unknown context", footer)
}
}
})
}
}

func TestCanonicalStatus_Scenario3_EstimatedContextMarkedAtEveryWidth(t *testing.T) {
for _, tc := range []struct {
name string
footerCols int
}{
{name: "full", footerCols: 120},
{name: "compact", footerCols: 20},
{name: "minimal", footerCols: 12},
} {
t.Run(tc.name, func(t *testing.T) {
estimated := stockFooter(t, Input{
Context: Context{Used: ContextAtom{Raw: 75_000, Human: "~75K"}, Window: ContextAtom{Raw: 100_000, Human: "100K"}, Percent: 75, Known: true, Estimated: true},
Terminal: Terminal{FooterAvailCols: tc.footerCols},
})
if text := statusSurfaceText(estimated); !strings.Contains(text, "~75%") {
t.Fatalf("estimated footer = %q, want visibly estimated percentage", text)
}

known := stockFooter(t, Input{
Context: Context{Used: ContextAtom{Raw: 90_000, Human: "90K"}, Window: ContextAtom{Raw: 100_000, Human: "100K"}, Percent: 90, Known: true},
Terminal: Terminal{FooterAvailCols: tc.footerCols},
})
if text := statusSurfaceText(known); strings.Contains(text, "~90%") || !strings.Contains(text, "90%") {
t.Fatalf("known footer = %q, want ordinary percentage", text)
}
if tc.name != "minimal" {
for _, span := range known.Spans {
if span.Token == TokenWarning {
return
}
}
t.Fatalf("known footer = %#v, want warning pressure token", known)
}
})
}
}

func stockFooter(t *testing.T, input Input) Surface {
t.Helper()
source := NewDefaultSource(0)
t.Cleanup(func() { _ = source.Close(context.Background()) })
source.Submit(input)
select {
case <-source.Changed():
case <-time.After(time.Second):
t.Fatal("stock source did not publish")
}
return source.Latest().Footer
}

func TestDefaultStatusHeadersElideSessionTitleByVariant(t *testing.T) {
const title = "界界界界界界界界界界界界界界界界界界界界"
for _, tc := range []struct {
Expand Down
4 changes: 2 additions & 2 deletions cmd/mecatui/customization/source_correction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestStatusLine_DefaultTemplatesExposeLegacyDisplayAtoms(t *testing.T) {
Model: Model{ProviderID: "openai", DisplayName: "GPT-5", Route: "azure"},
Server: ServerTarget{DisplayTarget: "server.example", ConnectionMode: "connect"},
Usage: Usage{Input: UsageAtom{Raw: 4_000, Human: "4K"}, Output: UsageAtom{Raw: 1_000, Human: "1K"}, CacheWrite: UsageAtom{Raw: 500, Human: "500"}, CacheReadPercent: 75},
Context: Context{Used: ContextAtom{Raw: 7_000, Human: "7K"}, Window: ContextAtom{Raw: 10_000, Human: "10K"}, Percent: 70},
Context: Context{Used: ContextAtom{Raw: 7_000, Human: "7K"}, Window: ContextAtom{Raw: 10_000, Human: "10K"}, Percent: 70, Known: true},
Delegation: Delegation{
Parallel: DelegationSummary{Running: 1, Finished: 2},
Subagents: DelegationSummary{Running: 3, Finished: 4},
Expand Down Expand Up @@ -87,7 +87,7 @@ func TestStatusLine_DefaultHeaderOmitsEmbeddedServerTarget(t *testing.T) {
func TestStatusLine_DefaultFooterSheddingPreservesContextPriority(t *testing.T) {
input := Input{
Usage: Usage{Input: UsageAtom{Raw: 4_000, Human: "4K"}, Output: UsageAtom{Raw: 1_000, Human: "1K"}, CacheReadPercent: 75},
Context: Context{Used: ContextAtom{Raw: 7_000, Human: "7K"}, Window: ContextAtom{Raw: 10_000, Human: "10K"}, Percent: 70},
Context: Context{Used: ContextAtom{Raw: 7_000, Human: "7K"}, Window: ContextAtom{Raw: 10_000, Human: "10K"}, Percent: 70, Known: true},
Delegation: Delegation{Parallel: DelegationSummary{Running: 1, Finished: 2}, Subagents: DelegationSummary{Running: 3, Finished: 4}, Team: LiveTeam{ID: "abc", Working: 1, Total: 2}},
}
cases := []struct {
Expand Down
34 changes: 24 additions & 10 deletions cmd/mecatui/customization/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,16 +179,16 @@ type templateContext struct {
// It emits only trusted StatusML markup; user-derived values remain escaped in
// templateContext. The UI resolves its semantic token through the active theme.
func contextMeter(input templateContext) templateText {
if !input.Known {
if input.Window.Raw > 0 {
return templateText("<text>ctx ?/" + string(input.Window.Human) + "</text>")
}
return templateText("<text>ctx ?</text>")
}
if input.Window.Raw <= 0 {
return templateText("<text>ctx " + string(input.Used.Human) + "</text>")
}
percent := input.Percent
if percent < 0 {
percent = 0
}
if percent > 100 {
percent = 100
}
percent := clampedContextPercent(input.Percent)
var token, fill string
switch {
case percent >= 85:
Expand All @@ -203,35 +203,41 @@ func contextMeter(input templateContext) templateText {
filled = 8
}
bar := strings.Repeat(fill, filled) + strings.Repeat("░", 8-filled)
label := "ctx " + bar + " " + strconv.Itoa(percent) + "%"
label := "ctx " + bar + " " + contextPercentLabel(input, percent)
if percent >= 85 {
label += " ⚠"
}
return templateText("<" + token + ">" + label + "</" + token + "><text> · " + string(input.Used.Human) + "/" + string(input.Window.Human) + "</text>")
}

func contextMeterCompact(input templateContext) templateText {
if !input.Known {
return templateText("<text>ctx ?</text>")
}
if input.Window.Raw <= 0 {
return templateText("<text>ctx " + string(input.Used.Human) + "</text>")
}
percent := clampedContextPercent(input.Percent)
token, fill := contextPressure(percent)
filled := (percent*8 + 50) / 100
bar := strings.Repeat(fill, filled) + strings.Repeat("░", 8-filled)
label := "ctx " + bar + " " + strconv.Itoa(percent) + "%"
label := "ctx " + bar + " " + contextPercentLabel(input, percent)
if percent >= 85 {
label += " ⚠"
}
return templateText("<" + token + ">" + label + "</" + token + ">")
}

func contextMeterMinimal(input templateContext) templateText {
if !input.Known {
return templateText("<text>ctx ?</text>")
}
if input.Window.Raw <= 0 {
return templateText("<text>ctx " + string(input.Used.Human) + "</text>")
}
percent := clampedContextPercent(input.Percent)
token, _ := contextPressure(percent)
label := "ctx " + strconv.Itoa(percent) + "%"
label := "ctx " + contextPercentLabel(input, percent)
if percent >= 85 {
label += " ⚠"
}
Expand All @@ -248,6 +254,14 @@ func clampedContextPercent(percent int) int {
return percent
}

func contextPercentLabel(input templateContext, percent int) string {
prefix := ""
if input.Estimated {
prefix = "~"
}
return prefix + strconv.Itoa(percent) + "%"
}

func contextPressure(percent int) (token, fill string) {
switch {
case percent >= 85:
Expand Down
4 changes: 4 additions & 0 deletions cmd/mecatui/ui/agents_inventory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ func TestAgentsInvPanelGolden(t *testing.T) {
if m.agentsInv.view != agentsInvPanel {
t.Fatalf("view = %v, want agentsInvPanel", m.agentsInv.view)
}
m = goldenStatusFrame(t, m)
got := stripANSI([]byte(m.View().Content))
compareGolden(t, "agents_inventory.golden", got)
}
Expand All @@ -376,6 +377,7 @@ func TestAgentsInvPanelEmptyNotEnabledGolden(t *testing.T) {
m := newAgentsInvModel(t, &fakeAgents{}, client.Capabilities{Agents: false})
mm, cmd := m.runAgentsInv()
m = feedCmd(t, mm.(Model), cmd)
m = goldenStatusFrame(t, m)
got := stripANSI([]byte(m.View().Content))
compareGolden(t, "agents_inventory_empty_disabled.golden", got)
}
Expand All @@ -386,6 +388,7 @@ func TestAgentsInvPanelEmptyEnabledGolden(t *testing.T) {
m := newAgentsInvModel(t, &fakeAgents{}, client.Capabilities{Agents: true})
mm, cmd := m.runAgentsInv()
m = feedCmd(t, mm.(Model), cmd)
m = goldenStatusFrame(t, m)
got := stripANSI([]byte(m.View().Content))
compareGolden(t, "agents_inventory_empty_enabled.golden", got)
}
Expand Down Expand Up @@ -544,6 +547,7 @@ func TestAgentsInvPanelScrollGolden(t *testing.T) {
}
mm, _, _ = m.onAgentsInvKey(tea.KeyPressMsg{Code: tea.KeyPgDown})
m = mm.(Model)
m = goldenStatusFrame(t, m)
got := stripANSI([]byte(m.View().Content))
compareGolden(t, "agents_inventory_scroll.golden", got)
}
13 changes: 7 additions & 6 deletions cmd/mecatui/ui/agents_overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/stacklok/mecatl/cmd/mecatui/internal/terminaltext"
"github.com/stacklok/mecatl/cmd/mecatui/theme"
"github.com/stacklok/mecatl/cmd/mecatui/ui/internal/bounded"
"github.com/stacklok/mecatl/cmd/mecatui/ui/internal/renderfmt"
"github.com/stacklok/mecatl/cmd/mecatui/ui/internal/scrollback"
)

Expand Down Expand Up @@ -1311,7 +1312,7 @@ func subagentRosterDetails(ln *subagentLane) string {
details = append(details,
subagentLaneState(ln),
plural(ln.toolCount, "tool"),
"↑"+humanizeTokens(ln.usage.InputTokens)+" ↓"+humanizeTokens(ln.usage.OutputTokens))
"↑"+renderfmt.HumanizeTokens(ln.usage.InputTokens)+" ↓"+renderfmt.HumanizeTokens(ln.usage.OutputTokens))
return strings.Join(details, " · ")
}

Expand Down Expand Up @@ -1763,7 +1764,7 @@ func parallelBranchDetails(br *parallelBranch) string {
if routed := delegationModelLabel(br.routedCategory, br.routedModel, br.routingReason, br.model, br.routingDecision); routed != "" {
parts = append(parts, routed)
}
parts = append(parts, parallelBranchState(br), plural(br.toolCount, "tool"), "↑"+humanizeTokens(br.usage.InputTokens)+" ↓"+humanizeTokens(br.usage.OutputTokens))
parts = append(parts, parallelBranchState(br), plural(br.toolCount, "tool"), "↑"+renderfmt.HumanizeTokens(br.usage.InputTokens)+" ↓"+renderfmt.HumanizeTokens(br.usage.OutputTokens))
return strings.Join(parts, " · ")
}

Expand All @@ -1781,7 +1782,7 @@ func parallelBranchState(br *parallelBranch) string {
if br.done {
state := parallelBranchStopLabel(br)
if br.durationMs > 0 {
state += " · " + humanizeDuration(br.durationMs)
state += " · " + renderfmt.HumanizeDuration(br.durationMs)
}
return state
}
Expand Down Expand Up @@ -1818,7 +1819,7 @@ func parallelBranchLine(br *parallelBranch) string {
if br.done {
state = parallelBranchStopLabel(br)
if br.durationMs > 0 {
state += " · " + humanizeDuration(br.durationMs)
state += " · " + renderfmt.HumanizeDuration(br.durationMs)
}
} else if br.current != "" {
state = truncate(terminaltext.Sanitize(br.current), maxTraceToolNameLen) + "…"
Expand All @@ -1830,8 +1831,8 @@ func parallelBranchLine(br *parallelBranch) string {
return fmt.Sprintf("%s %s · %s%s · %s · %s · ↑%s ↓%s",
glyph, terminaltext.Sanitize(label), goal, routed, state,
plural(br.toolCount, "tool"),
humanizeTokens(br.usage.InputTokens),
humanizeTokens(br.usage.OutputTokens))
renderfmt.HumanizeTokens(br.usage.InputTokens),
renderfmt.HumanizeTokens(br.usage.OutputTokens))
}

// parallelBranchStopLabel labels a finished branch: "failed" (with the stop reason when
Expand Down
Loading
Loading