Skip to content

refactor(widgets): share one base class across the Git count widgets - #654

Merged
sirmalloc merged 4 commits into
sirmalloc:mainfrom
eric-engberg:refactor/git-count-widget-duplication
Oct 6, 2026
Merged

sirmalloc merged 4 commits into
sirmalloc:mainfrom
eric-engberg:refactor/git-count-widget-duplication

Conversation

@eric-engberg

@eric-engberg eric-engberg commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

#601 and #499 also touch these files; happy to rebase either way. A sibling PR, #651, does the same for the Git status indicator widgets; the two touch different files, so either can go first.

Demo

After the refactor: in the line editor, r switches Git Staged Files to its raw value and h hides it at zero, then g sets Git Insertions' glyph and both of Git Changes' glyphs. Every case also renders byte-for-byte the same as before; see Testing.

Powerline

Raw value, zero hiding and glyphs on the Git count widgets in Powerline mode

Plain

Raw value, zero hiding and glyphs on the Git count widgets in plain mode

The same six widgets piped from a real repository with staged, unstaged and untracked changes, a clean one, and a directory outside any repository:

Powerline

The Git count widgets reading a dirty repository, a clean one and a non-repository in Powerline mode

Plain

The Git count widgets reading a dirty repository, a clean one and a non-repository in plain mode

Summary

Git Staged, Unstaged and Untracked Files, Insertions, Deletions and Changes each carried their own copy of:

  • the editor label, and the "when not in a git repo" and "zero" hide states;
  • render: the preview sample, then (no git) outside a work tree, or nothing when that state is hidden, then nothing for a zero count when the zero state is hidden.

The three file-count widgets were also one class written three times, differing only in the count they show, its label, name and color. Insertions and Deletions were nearly the same class too.

GitCountWidgetBase (widgets/shared/git-count-widget.ts) now holds that render and the hide states. Each widget says when its counts are zero and how they show; the preview sample goes through the same formatting. On top of it:

  • GitFileCountWidget holds the file counts' git query, the raw-or-labeled format and the numeric value, so Staged, Unstaged and Untracked Files only name their count and label.
  • GitLineCountWidget holds the line counts' git query, the preview sample, and the glyph key and editor. Insertions, Deletions and Changes each keep their own zero rule and format.

7 source files, 173 lines added and 330 removed, plus a test file. No visual change.

Testing

  • The existing tests for these widgets cover each one's counts and placeholders. The first commit adds widgets/__tests__/GitCountWidgets.test.ts (63 tests) for the rest, checked against the current code before the refactor:

    • all six against the same repositories (dirty, clean, only removed lines, only added lines, only an untracked file, and none), labeled, raw and with each hide state;
    • Git Changes staying visible with the zero state hidden while only one of its counts is zero;
    • the editor label and hide-state labels, the file counts' numeric value, and that a raw item on Insertions, Deletions or Changes still shows its glyph;
    • glyph overrides in a repository, and the glyph editor's rows.

    It passes at both commits under Bun and under Node. Breaking Git Changes' zero rule, a zero-state label, Git Untracked Files' numeric value, Git Deletions' glyph editor, Git Insertions' no-git hide state or Git Staged Files' raw value each makes it fail.

  • bun run lint and bun test (2,421 pass).

  • Before/after render check: all six widgets rendered on main and on this branch, in every state their own keys reach, raw and labeled, at all three color levels. It ran in the TUI preview and against seven real repositories: staged, unstaged and untracked changes; only staged; only untracked; a merge conflict; clean; no commits yet; and a directory outside any repository. All 420 cases (60 per repository) are byte-identical, as are each widget's editor label and keys. A wider sweep over the same repositories also varied the hide states, glyph overrides (set and emptied) and raw value (on, off, unset): all 4,320 renders are identical, as are each widget's numeric value, hide states, keys and glyph-editor rows.

  • Built dist/ and piped a status line with all six widgets through it, from the dirty and the clean repository, in plain and Powerline mode under Bun and Node 16, 18, 20, 22, 24 and 26: every runtime agrees, and the output matches main. The TUI opens and exits cleanly on each.

  • Under Node with Vitest, the full suite fails in the same 17 files as main (all from spying on Node built-ins), and the new tests pass.

The existing tests for Git Staged, Unstaged and Untracked Files, Insertions,
Deletions and Changes cover each widget's counts and placeholders, but not
the editor label and hide-state labels, the file counts' numeric value, glyph
overrides outside the preview, a raw item on the widgets without a raw mode,
or Git Changes staying visible with the zero state hidden while only one of
its counts is zero. Cover all six against the same repositories: dirty,
clean, only removed lines, only added lines, only an untracked file, and none.
Git is answered by a mocked execFileSync, like the other Git widget tests.
Git Staged, Unstaged and Untracked Files, Insertions, Deletions and Changes
each carried the same editor label, "no git" and "zero" hide states and
render: the preview sample, then "(no git)" outside a work tree, or nothing
when that state is hidden, then nothing for a zero count when the zero state
is hidden. The three file-count widgets were also one class written three
times, and Insertions and Deletions nearly so.

GitCountWidgetBase now holds that render and the hide states; each widget
says when its counts are zero and how they show. GitFileCountWidget adds the
file counts' git query, raw-or-labeled format and numeric value, so those
three only name their count and label. GitLineCountWidget adds the line
counts' git query, preview sample, glyph key and editor for Insertions,
Deletions and Changes.

Behavior is unchanged.
@sirmalloc
sirmalloc merged commit 4424292 into sirmalloc:main Oct 6, 2026
3 checks passed
@eric-engberg
eric-engberg deleted the refactor/git-count-widget-duplication branch October 9, 2026 21:47
pcvelz added a commit to pcvelz/ccstatusline-usage that referenced this pull request Oct 11, 2026
Upstream: label prefixes editable with e (sirmalloc#601, sirmalloc#666), Session Cost Rate (sirmalloc#671) and Extra Usage Daily Budget (sirmalloc#672) widgets, own-lock-only usage lock clearing (sirmalloc#624), NO_PROXY and request deadlines, terminal control-sequence sanitizing, git hardening and linear-time parsing, shared searchable option editor (sirmalloc#649) and widget base classes (sirmalloc#646-sirmalloc#654), node: imports (sirmalloc#657), Number.* globals lint (sirmalloc#645), test waits on redraw instead of fixed delays (sirmalloc#652).

Hand edits outside conflicts:
- src/widgets/shared/searchable-option-editor.tsx: compat fix - ports the fork's ref-based input handling (Enter picks from what was just typed) into upstream's new shared editor
- src/utils/usage-fetch.ts: compat fix - fork's fetchFreshUsageData uses upstream's clearOwnUsageLock (sirmalloc#624) instead of the removed clearUsageLock
- src/widgets/ApiUsage.tsx, src/widgets/Battery.ts: compat fix - Number.isNaN/Number.parseInt for upstream's no-restricted-globals rule
- src/utils/__tests__/usage-prefetch.test.ts: compat fix - mock renamed to upstream's mockFetchUsage
- src/tui/components/__tests__/PowerlineSetup.test.ts, src/tui/components/__tests__/StatusLinePreview.test.ts, src/tui/components/__tests__/TerminalWidthMenu.test.ts: compat fix - drop the fork's flushInk import, unused after upstream's waitFor switch
- README.md: upstream notes linked docs/USAGE.md (deleted in the fork); inlined the proxy and workspace-trust text instead; upstream's own release-note entries not taken
- .fork-keep-deleted: src/widgets/FableWeeklyUsage.ts (upstream widget, unregistered in the fork, which ships WeeklyFableUsage.ts)
- src/widgets/WeeklyFableUsage.ts: compat fix - adopts upstream's shared getUsagePercentWidgetEditorDisplay, which carries the time-cursor modifier
- src/tui/__tests__/App.test.ts: fork deviation - one extra down-arrow for the fork's Off Hours main-menu row
- src/widgets/__tests__/CacheTimer.test.ts, src/widgets/CacheTimer.ts: compat fix - emoji toggle moved from e to o because upstream reserves e for edit-label
- src/widgets/AgentAddress.ts: split the session-title guard into its own if (same behaviour) so the .fork-invariants nameSource guard matches again
- docs/test-retirements.md: ledger for tests upstream renamed or replaced
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants