From f7f9fbefeafa28ac6a80fd91641fa4e4f8f53a3f Mon Sep 17 00:00:00 2001 From: Zach Date: Mon, 15 Jun 2026 15:54:00 -0500 Subject: [PATCH] feat(compaction): make the reclaimed-tokens arrow an overridable glyph The reclaimed-tokens suffix hardcoded its leading down-arrow. Route it through the existing symbol-override slot system (metadata slot symbolReclaimed) so it can be customized or cleared from the glyph editor, the same way the git widgets expose their symbols. The default is unchanged, so existing configs render identically. An empty override drops the glyph but keeps the separating space. Closes #448 Co-Authored-By: Claude --- src/widgets/CompactionCounter.ts | 23 ++++++++++++++++--- .../__tests__/CompactionCounter.test.ts | 20 ++++++++++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/widgets/CompactionCounter.ts b/src/widgets/CompactionCounter.ts index 65ab6a38..5b6467be 100644 --- a/src/widgets/CompactionCounter.ts +++ b/src/widgets/CompactionCounter.ts @@ -7,6 +7,7 @@ import type { CustomKeybind, Widget, WidgetEditorDisplay, + WidgetEditorProps, WidgetItem } from '../types/Widget'; import { ZERO_COMPACTION_STATS } from '../utils/compaction'; @@ -16,6 +17,12 @@ import { isMetadataFlagEnabled, toggleMetadataFlag } from './shared/metadata'; +import { + getSlotSymbol, + getSymbolKeybind, + renderSymbolSlotsEditor, + type SymbolSlot +} from './shared/symbol-override'; const COMPACTION_ICON = '↻'; const COMPACTION_NERD_FONT_ICON = '\uF021'; @@ -32,6 +39,7 @@ const TOGGLE_TRIGGERS_ACTION = 'toggle-triggers'; const SHOW_TRIGGERS_METADATA_KEY = 'showTriggers'; const TOGGLE_RECLAIMED_ACTION = 'toggle-reclaimed'; const SHOW_RECLAIMED_METADATA_KEY = 'showReclaimed'; +const RECLAIMED_SLOT: SymbolSlot = { id: 'symbolReclaimed', label: 'Reclaimed', defaultSymbol: '↓' }; const SAMPLE_STATS: CompactionData = Object.freeze({ count: 2, byTrigger: Object.freeze({ auto: 1, manual: 1, unknown: 0 }), @@ -94,8 +102,12 @@ function toggleHideZero(item: WidgetItem): WidgetItem { }; } -function formatReclaimedSuffix(tokensReclaimed: number): string { - return tokensReclaimed > 0 ? ` ↓${formatTokens(tokensReclaimed)}` : ''; +function formatReclaimedSuffix(tokensReclaimed: number, item: WidgetItem): string { + if (tokensReclaimed <= 0) { + return ''; + } + const symbol = getSlotSymbol(item, RECLAIMED_SLOT); + return symbol.length > 0 ? ` ${symbol}${formatTokens(tokensReclaimed)}` : ` ${formatTokens(tokensReclaimed)}`; } function formatTriggerSuffix(byTrigger: CompactionData['byTrigger']): string { @@ -118,7 +130,7 @@ function formatStats(data: CompactionData, item: WidgetItem, icon: string): stri out += formatTriggerSuffix(data.byTrigger); } if (isMetadataFlagEnabled(item, SHOW_RECLAIMED_METADATA_KEY)) { - out += formatReclaimedSuffix(data.tokensReclaimed); + out += formatReclaimedSuffix(data.tokensReclaimed, item); } return out; } @@ -239,10 +251,15 @@ export class CompactionCounterWidget implements Widget { keybinds.push({ key: 's', label: '(s)plit by trigger', action: TOGGLE_TRIGGERS_ACTION }); keybinds.push({ key: 't', label: '(t)okens reclaimed', action: TOGGLE_RECLAIMED_ACTION }); keybinds.push({ key: 'h', label: '(h)ide when zero', action: TOGGLE_HIDE_ZERO_ACTION }); + keybinds.push(getSymbolKeybind()); return keybinds; } + renderEditor(props: WidgetEditorProps) { + return renderSymbolSlotsEditor(props, [RECLAIMED_SLOT]); + } + supportsRawValue(): boolean { return false; } supportsColors(item: WidgetItem): boolean { return true; } } diff --git a/src/widgets/__tests__/CompactionCounter.test.ts b/src/widgets/__tests__/CompactionCounter.test.ts index ddbea780..e3f9cef3 100644 --- a/src/widgets/__tests__/CompactionCounter.test.ts +++ b/src/widgets/__tests__/CompactionCounter.test.ts @@ -235,6 +235,20 @@ describe('CompactionCounterWidget', () => { item: { ...ITEM, metadata: { showReclaimed: 'true' } } })).toBe('↻ 2 ↓120.0k'); }); + + it('renders a custom reclaimed glyph from the symbolReclaimed override', () => { + expect(render({ + compactionData: { count: 2, tokensReclaimed: 887000 }, + item: { ...ITEM, metadata: { showReclaimed: 'true', symbolReclaimed: 'X' } } + })).toBe('↻ 2 X887.0k'); + }); + + it('drops the reclaimed glyph but keeps the space when the override is empty', () => { + expect(render({ + compactionData: { count: 2, tokensReclaimed: 887000 }, + item: { ...ITEM, metadata: { showReclaimed: 'true', symbolReclaimed: '' } } + })).toBe('↻ 2 887.0k'); + }); }); describe('editor', () => { @@ -244,7 +258,8 @@ describe('CompactionCounterWidget', () => { { key: 'n', label: '(n)erd font', action: 'toggle-nerd-font' }, { key: 's', label: '(s)plit by trigger', action: 'toggle-triggers' }, { key: 't', label: '(t)okens reclaimed', action: 'toggle-reclaimed' }, - { key: 'h', label: '(h)ide when zero', action: 'toggle-hide-zero' } + { key: 'h', label: '(h)ide when zero', action: 'toggle-hide-zero' }, + { key: 'g', label: '(g)lyph', action: 'edit-symbol-override' } ]); }); @@ -256,7 +271,8 @@ describe('CompactionCounterWidget', () => { { key: 'f', label: '(f)ormat', action: 'cycle-format' }, { key: 's', label: '(s)plit by trigger', action: 'toggle-triggers' }, { key: 't', label: '(t)okens reclaimed', action: 'toggle-reclaimed' }, - { key: 'h', label: '(h)ide when zero', action: 'toggle-hide-zero' } + { key: 'h', label: '(h)ide when zero', action: 'toggle-hide-zero' }, + { key: 'g', label: '(g)lyph', action: 'edit-symbol-override' } ]); });