Skip to content

Commit 69d302b

Browse files
aksOpsclaude
andcommitted
refactor(statusline): drop token line, simplify context segment to ctx %
Statusline shrinks from 3 lines to 2 and the context segment renders just `ctx <pct>%` (no parenthesised token sum). Removes: - Line 3 cumulative session ↑/↓ token totals + trailing effort tail - buildTokenLine, readEffortLevel, unused color constants (cTokIn, cTokOut, cDimGray) - claude package import (no longer needed for effort lookup) - Obsolete tests for the dropped token-count behaviour Token-count helpers (contextTokens, fmtTokens) and their unit tests remain — no production caller, but the formatting behaviour stays verified for any future reuse. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f56fff1 commit 69d302b

2 files changed

Lines changed: 16 additions & 123 deletions

File tree

cmd/statusline.go

Lines changed: 8 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import (
1111
"strings"
1212

1313
"github.com/spf13/cobra"
14-
15-
"github.com/RandomCodeSpace/ctm/internal/claude"
1614
)
1715

1816
func init() {
@@ -24,17 +22,15 @@ func init() {
2422
// we print a three-line display on stdout. Hidden because it's an
2523
// internal hook, not a user-facing command.
2624
//
27-
// Output layout (3 lines):
25+
// Output layout (2 lines):
2826
//
2927
// Line 1: <model> · <project> (project shown as a plain path)
30-
// Line 2: c 25% (437k) w 40% h 10% (context % + tokens + rate limits)
31-
// Line 3: ↑ 117k ↓ 434k (cumulative session input / output)
28+
// Line 2: ctx 25% w 40% h 10% (context % + rate limits)
3229
//
33-
// Cache_read (⚡) was dropped from the status because its magnitude is
34-
// already captured in the context-tokens parenthesis and Claude Code's
35-
// own focus-mode overlay duplicates the information. Weekly / 5-hour
36-
// rate limits share line 2 with context because they're all
37-
// percentages; tokens share line 3 because both are cumulative ints.
30+
// Cache_read (⚡) was dropped because its magnitude is already captured
31+
// in the context-tokens parenthesis and Claude Code's own focus-mode
32+
// overlay duplicates the information. Weekly / 5-hour rate limits
33+
// share line 2 with context because they're all percentages.
3834
var statuslineCmd = &cobra.Command{
3935
Use: "statusline",
4036
Short: "Internal statusLine renderer — reads JSON on stdin (hidden)",
@@ -138,9 +134,6 @@ const (
138134
cMagenta = "\x1b[1;38;5;220m" // weekly bar
139135
cYellow = "\x1b[1;38;5;208m" // 5-hour bar
140136
cHdrModel = "\x1b[1;97m"
141-
cTokIn = "\x1b[1;38;5;33m"
142-
cTokOut = "\x1b[1;38;5;37m"
143-
cDimGray = "\x1b[90m"
144137
)
145138

146139
func renderStatusline(in *statuslineInput) string {
@@ -153,9 +146,6 @@ func renderStatusline(in *statuslineInput) string {
153146
if mid != "" {
154147
lines = append(lines, mid)
155148
}
156-
if s := buildTokenLine(in); s != "" {
157-
lines = append(lines, s)
158-
}
159149
return strings.Join(lines, "\n")
160150
}
161151

@@ -201,66 +191,15 @@ func buildHeader(in *statuslineInput) string {
201191
}
202192
}
203193

204-
// readEffortLevel is a cmd-package-local wrapper so other renderers can
205-
// pick up the current effort level without duplicating the path dance.
206-
// Silent on every error path — effort is a nice-to-have, not critical.
207-
func readEffortLevel() string {
208-
p, err := claude.SettingsJSONPath()
209-
if err != nil {
210-
return ""
211-
}
212-
return claude.ReadEffortLevel(p)
213-
}
214-
215-
// buildContextLine builds the `c <pct>% (<tokens>)` segment of line 2.
216-
// The context-window-used percentage is the primary signal; the
217-
// parenthesised token sum (input + cache_creation + cache_read, per
218-
// Claude Code's input-only formula) is a secondary concrete number.
194+
// buildContextLine builds the `ctx <pct>%` segment of line 2.
219195
// Returns "" when used_percentage is absent.
220196
func buildContextLine(in *statuslineInput) string {
221197
used := in.ContextWindow.UsedPercentage
222198
if used == nil {
223199
return ""
224200
}
225201
usedPct := int(math.Round(*used))
226-
entry := fmt.Sprintf("%sc %d%%%s", cCyan, usedPct, cReset)
227-
if ctx := contextTokens(in); ctx > 0 {
228-
entry += fmt.Sprintf(" %s(%s)%s", cDimGray, fmtTokens(ctx), cReset)
229-
}
230-
return entry
231-
}
232-
233-
// buildTokenLine renders the cumulative session token totals: `↑ <input>`
234-
// and `↓ <output>`. cache_read (⚡) used to live here too; it was dropped
235-
// from the statusline — the token magnitude is already visible as the
236-
// parenthesised number on the context line and Claude Code's focus-mode
237-
// overlay renders its own cache indicator.
238-
func buildTokenLine(in *statuslineInput) string {
239-
var parts []string
240-
add := func(glyph rune, color string, n *int64) {
241-
if n == nil || *n <= 0 {
242-
return
243-
}
244-
parts = append(parts, fmt.Sprintf("%s%c%s %s%s%s",
245-
color, glyph, cReset, cDimGray, fmtTokens(*n), cReset))
246-
}
247-
add('↑', cTokIn, in.ContextWindow.TotalInputTokens)
248-
add('↓', cTokOut, in.ContextWindow.TotalOutputTokens)
249-
line := strings.Join(parts, " ")
250-
251-
// Tack the current effort level onto the last line. Sourced from
252-
// ~/.claude/settings.json via readEffortLevel — not in Claude
253-
// Code's statusLine payload. Dim-gray so it reads as secondary
254-
// info next to the token counts. Only appended when at least one
255-
// token is present so a truly empty payload doesn't render a
256-
// lone "· xhigh" orphan.
257-
if line == "" {
258-
return ""
259-
}
260-
if effort := readEffortLevel(); effort != "" {
261-
line += fmt.Sprintf(" %s%s%s", cDimGray, effort, cReset)
262-
}
263-
return line
202+
return fmt.Sprintf("%sctx %d%%%s", cCyan, usedPct, cReset)
264203
}
265204

266205
// buildRateLimitLine renders `w <pct>%` and `h <pct>%` for weekly and

cmd/statusline_test.go

Lines changed: 8 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -22,36 +22,28 @@ func TestRenderStatuslineFullPayload(t *testing.T) {
2222

2323
out := renderStatusline(in)
2424
lines := strings.Split(out, "\n")
25-
if len(lines) != 3 {
26-
t.Fatalf("expected 3 lines, got %d:\n%s", len(lines), out)
25+
if len(lines) != 2 {
26+
t.Fatalf("expected 2 lines, got %d:\n%s", len(lines), out)
2727
}
28-
// Line 0 — Header: full model name (minus the redundant "Claude "
29-
// prefix) plus the project tail.
3028
if !strings.Contains(lines[0], "Sonnet 4.5 (1M)") {
3129
t.Errorf("header missing full model name: %q", lines[0])
3230
}
3331
if !strings.Contains(lines[0], "ctm-statusline-fake") {
3432
t.Errorf("header missing project tail: %q", lines[0])
3533
}
36-
// Line 1 — Context + rate limits share one line now: c / w / h.
37-
for _, want := range []string{"c", "25%", "w", "40%", "h", "10%"} {
34+
for _, want := range []string{"ctx", "25%", "w", "40%", "h", "10%"} {
3835
if !strings.Contains(lines[1], want) {
3936
t.Errorf("line 2 missing %q: %q", want, lines[1])
4037
}
4138
}
42-
// ⚡ should NOT appear anywhere — cache was dropped as a separate
43-
// statusline entry. (The cache_read value may still contribute to
44-
// the parenthesised context-tokens sum, which is expected.)
45-
if strings.Contains(out, "⚡") {
46-
t.Errorf("cache glyph ⚡ should have been removed:\n%s", out)
39+
if strings.Contains(lines[1], "(") || strings.Contains(lines[1], ")") {
40+
t.Errorf("line 2 should not contain token-count parens: %q", lines[1])
4741
}
48-
// Line 2 — Tokens: input + output only.
49-
for _, want := range []string{"↑", "↓", "12.3k", "6.8k"} {
50-
if !strings.Contains(lines[2], want) {
51-
t.Errorf("token line missing %q: %q", want, lines[2])
42+
for _, banned := range []string{"⚡", "↑", "↓"} {
43+
if strings.Contains(out, banned) {
44+
t.Errorf("dropped glyph %q should not appear:\n%s", banned, out)
5245
}
5346
}
54-
// No bar runes should appear anywhere.
5547
for _, bar := range []string{"━", "─"} {
5648
if strings.Contains(out, bar) {
5749
t.Errorf("unexpected bar rune %q in output:\n%s", bar, out)
@@ -122,44 +114,6 @@ func TestContextTokens_AllNilReturnsZero(t *testing.T) {
122114
}
123115
}
124116

125-
func TestRenderStatuslineShowsContextTokens(t *testing.T) {
126-
in := &statuslineInput{}
127-
in.Model.DisplayName = "Claude Sonnet 4.5"
128-
in.ContextWindow.UsedPercentage = floatPtr(42)
129-
in.ContextWindow.CurrentUsage.InputTokens = intPtr(12000)
130-
in.ContextWindow.CurrentUsage.CacheCreationInputTokens = intPtr(8000)
131-
in.ContextWindow.CurrentUsage.CacheReadInputTokens = intPtr(417270)
132-
// Sum = 437 270 → formats as "437.3k"
133-
134-
out := renderStatusline(in)
135-
lines := strings.Split(out, "\n")
136-
if len(lines) < 2 {
137-
t.Fatalf("expected at least 2 lines, got:\n%s", out)
138-
}
139-
if !strings.Contains(lines[1], "437.3k") {
140-
t.Errorf("line 2 missing context token count: %q", lines[1])
141-
}
142-
if !strings.Contains(lines[1], "42%") {
143-
t.Errorf("line 2 missing context %%: %q", lines[1])
144-
}
145-
}
146-
147-
func TestRenderStatuslineOmitsContextTokensWhenZero(t *testing.T) {
148-
in := &statuslineInput{}
149-
in.Model.DisplayName = "Claude Sonnet 4.5"
150-
in.ContextWindow.UsedPercentage = floatPtr(0)
151-
// current_usage absent — contextTokens = 0
152-
153-
out := renderStatusline(in)
154-
lines := strings.Split(out, "\n")
155-
if len(lines) < 2 {
156-
t.Fatalf("expected at least 2 lines, got:\n%s", out)
157-
}
158-
if strings.Contains(lines[1], "(") {
159-
t.Errorf("line 2 unexpectedly includes a token-count paren: %q", lines[1])
160-
}
161-
}
162-
163117
func TestFmtTokens(t *testing.T) {
164118
cases := map[int64]string{
165119
0: "0",

0 commit comments

Comments
 (0)