Skip to content
Open
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
107 changes: 90 additions & 17 deletions src/widgets/CompactionCounter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ const TOGGLE_TRIGGERS_ACTION = 'toggle-triggers';
const SHOW_TRIGGERS_METADATA_KEY = 'showTriggers';
const TOGGLE_RECLAIMED_ACTION = 'toggle-reclaimed';
const SHOW_RECLAIMED_METADATA_KEY = 'showReclaimed';
// Selectable metric. The default 'count' keeps the full composite display
// (icon, count, optional trigger split, optional reclaimed). The other metrics
// render just that one value as a raw number, so several instances can be
// composed with custom separators/symbols into a layout like "2 · 1a 1m · ↓2M".
const METRICS = ['count', 'auto', 'manual', 'unknown', 'reclaimed'] as const;
type CompactionMetric = typeof METRICS[number];
const DEFAULT_METRIC: CompactionMetric = 'count';
const METRIC_METADATA_KEY = 'metric';
const CYCLE_METRIC_ACTION = 'cycle-metric';
const RECLAIMED_SLOT: SymbolSlot = { id: 'symbolReclaimed', label: 'Reclaimed', defaultSymbol: '↓' };
const SAMPLE_STATS: CompactionData = Object.freeze({
count: 2,
Expand Down Expand Up @@ -102,6 +111,41 @@ function toggleHideZero(item: WidgetItem): WidgetItem {
};
}

function getMetric(item: WidgetItem): CompactionMetric {
const metric = item.metadata?.[METRIC_METADATA_KEY];
return (METRICS as readonly string[]).includes(metric ?? '') ? (metric as CompactionMetric) : DEFAULT_METRIC;
}

function setMetric(item: WidgetItem, metric: CompactionMetric): WidgetItem {
if (metric === DEFAULT_METRIC) {
const { [METRIC_METADATA_KEY]: removedMetric, ...restMetadata } = item.metadata ?? {};
void removedMetric;

return {
...item,
metadata: Object.keys(restMetadata).length > 0 ? restMetadata : undefined
};
}

return {
...item,
metadata: {
...(item.metadata ?? {}),
[METRIC_METADATA_KEY]: metric
}
};
}

function getMetricValue(data: CompactionData, metric: CompactionMetric): number {
switch (metric) {
case 'count': return data.count;
case 'auto': return data.byTrigger.auto;
case 'manual': return data.byTrigger.manual;
case 'unknown': return data.byTrigger.unknown;
case 'reclaimed': return data.tokensReclaimed;
}
}

function formatReclaimedSuffix(tokensReclaimed: number, item: WidgetItem): string {
if (tokensReclaimed <= 0) {
return '';
Expand Down Expand Up @@ -169,23 +213,32 @@ function formatCount(count: number, format: CompactionCounterFormat, icon: strin
* compaction has occurred by counting compact_boundary markers in the transcript.
*
* Shows ↻ N by default, including ↻ 0 before compaction occurs. Can be
* configured to hide when count is 0.
* configured to hide when count is 0. A `metric` selector switches it to emit a
* single raw value (count, auto, manual, unknown, or reclaimed) so several
* instances can be composed into a custom layout.
*/
export class CompactionCounterWidget implements Widget {
getDefaultColor(): string { return 'yellow'; }
getDescription(): string { return 'Count of context compaction events in the current session.'; }
getDisplayName(): string { return 'Compaction Counter'; }
getCategory(): string { return 'Context'; }
getEditorDisplay(item: WidgetItem): WidgetEditorDisplay {
const modifiers: string[] = [getFormat(item)];
if (isNerdFontEnabled(item)) {
modifiers.push('nerd font');
}
if (isMetadataFlagEnabled(item, SHOW_TRIGGERS_METADATA_KEY)) {
modifiers.push('trigger split');
}
if (isMetadataFlagEnabled(item, SHOW_RECLAIMED_METADATA_KEY)) {
modifiers.push('reclaimed');
const metric = getMetric(item);
const modifiers: string[] = [];

if (metric !== DEFAULT_METRIC) {
modifiers.push(`${metric} value`);
} else {
modifiers.push(getFormat(item));
if (isNerdFontEnabled(item)) {
modifiers.push('nerd font');
}
if (isMetadataFlagEnabled(item, SHOW_TRIGGERS_METADATA_KEY)) {
modifiers.push('trigger split');
}
if (isMetadataFlagEnabled(item, SHOW_RECLAIMED_METADATA_KEY)) {
modifiers.push('reclaimed');
}
}
if (isHideZeroEnabled(item)) {
modifiers.push('hide zero');
Expand All @@ -198,6 +251,13 @@ export class CompactionCounterWidget implements Widget {
}

handleEditorAction(action: string, item: WidgetItem): WidgetItem | null {
if (action === CYCLE_METRIC_ACTION) {
const currentMetric = getMetric(item);
const nextMetric = METRICS[(METRICS.indexOf(currentMetric) + 1) % METRICS.length] ?? DEFAULT_METRIC;

return setMetric(item, nextMetric);
}

if (action === CYCLE_FORMAT_ACTION) {
const currentFormat = getFormat(item);
const nextFormat = FORMATS[(FORMATS.indexOf(currentFormat) + 1) % FORMATS.length] ?? DEFAULT_FORMAT;
Expand Down Expand Up @@ -226,28 +286,41 @@ export class CompactionCounterWidget implements Widget {

render(item: WidgetItem, context: RenderContext, settings: Settings): string | null {
void settings;
const icon = isNerdFontEnabled(item) ? COMPACTION_NERD_FONT_ICON : COMPACTION_ICON;
const data = context.isPreview ? SAMPLE_STATS : (context.compactionData ?? ZERO_COMPACTION_STATS);
const metric = getMetric(item);

if (context.isPreview) {
return formatStats(SAMPLE_STATS, item, icon);
if (metric !== DEFAULT_METRIC) {
const value = getMetricValue(data, metric);
if (value === 0 && isHideZeroEnabled(item) && !context.isPreview) {
return null;
}
return metric === 'reclaimed' ? formatTokens(value) : String(value);
}

const data = context.compactionData ?? ZERO_COMPACTION_STATS;
if (data.count === 0 && isHideZeroEnabled(item))
if (data.count === 0 && isHideZeroEnabled(item) && !context.isPreview) {
return null;
}

const icon = isNerdFontEnabled(item) ? COMPACTION_NERD_FONT_ICON : COMPACTION_ICON;
return formatStats(data, item, icon);
}

getCustomKeybinds(item?: WidgetItem): CustomKeybind[] {
const keybinds: CustomKeybind[] = [
{ key: 'f', label: '(f)ormat', action: CYCLE_FORMAT_ACTION }
{ key: 'm', label: '(m)etric', action: CYCLE_METRIC_ACTION }
];

// The format / glyph / trigger toggles only shape the composite 'count'
// display; a single-metric value just needs the metric and hide-zero.
if (item !== undefined && getMetric(item) !== DEFAULT_METRIC) {
keybinds.push({ key: 'h', label: '(h)ide when zero', action: TOGGLE_HIDE_ZERO_ACTION });
return keybinds;
}

keybinds.push({ key: 'f', label: '(f)ormat', action: CYCLE_FORMAT_ACTION });
if (item === undefined || getFormat(item) === DEFAULT_FORMAT) {
keybinds.push({ key: 'n', label: '(n)erd font', action: TOGGLE_NERD_FONT_ACTION });
}

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 });
Expand Down
101 changes: 100 additions & 1 deletion src/widgets/__tests__/CompactionCounter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,9 @@ describe('CompactionCounterWidget', () => {
});

describe('editor', () => {
it('uses f and n as keybinds for the default format', () => {
it('uses metric, format, and toggle keybinds in count mode', () => {
expect(new CompactionCounterWidget().getCustomKeybinds(ITEM)).toEqual([
{ key: 'm', label: '(m)etric', action: 'cycle-metric' },
{ key: 'f', label: '(f)ormat', action: 'cycle-format' },
{ key: 'n', label: '(n)erd font', action: 'toggle-nerd-font' },
{ key: 's', label: '(s)plit by trigger', action: 'toggle-triggers' },
Expand All @@ -268,6 +269,7 @@ describe('CompactionCounterWidget', () => {
...ITEM,
metadata: { format: 'text-and-number' }
})).toEqual([
{ key: 'm', label: '(m)etric', action: 'cycle-metric' },
{ 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' },
Expand Down Expand Up @@ -410,4 +412,101 @@ describe('CompactionCounterWidget', () => {
});
});
});

describe('metric selector', () => {
it('renders only the auto-trigger count when metric is auto', () => {
expect(render({
compactionData: { count: 5, byTrigger: { auto: 3, manual: 2, unknown: 0 } },
item: { ...ITEM, metadata: { metric: 'auto' } }
})).toBe('3');
});

it('renders only the manual-trigger count when metric is manual', () => {
expect(render({
compactionData: { count: 5, byTrigger: { auto: 3, manual: 2, unknown: 0 } },
item: { ...ITEM, metadata: { metric: 'manual' } }
})).toBe('2');
});

it('renders only the unknown-trigger count when metric is unknown', () => {
expect(render({
compactionData: { count: 5, byTrigger: { auto: 3, manual: 1, unknown: 1 } },
item: { ...ITEM, metadata: { metric: 'unknown' } }
})).toBe('1');
});

it('renders the reclaimed tokens formatted when metric is reclaimed', () => {
expect(render({
compactionData: { count: 2, tokensReclaimed: 887000 },
item: { ...ITEM, metadata: { metric: 'reclaimed' } }
})).toBe('887.0k');
});

it('emits a raw value, ignoring format and icon settings', () => {
expect(render({
compactionData: { count: 5, byTrigger: { auto: 3, manual: 2, unknown: 0 } },
item: { ...ITEM, metadata: { metric: 'auto', format: 'icon-space-number', nerdFont: 'true' } }
})).toBe('3');
});

it('hides a zero metric value when hide zero is enabled', () => {
expect(render({
compactionData: { count: 4, byTrigger: { auto: 0, manual: 4, unknown: 0 } },
item: { ...ITEM, metadata: { metric: 'auto', hideZero: 'true' } }
})).toBeNull();
});

it('still shows a zero metric value when hide zero is off', () => {
expect(render({
compactionData: { count: 4, byTrigger: { auto: 0, manual: 4, unknown: 0 } },
item: { ...ITEM, metadata: { metric: 'auto' } }
})).toBe('0');
});

it('shows the sample metric value in preview mode, ignoring hide zero', () => {
expect(render({
isPreview: true,
item: { ...ITEM, metadata: { metric: 'unknown', hideZero: 'true' } }
})).toBe('0');
expect(render({
isPreview: true,
item: { ...ITEM, metadata: { metric: 'reclaimed' } }
})).toBe('120.0k');
});

it('shows the metric in the editor display', () => {
expect(new CompactionCounterWidget().getEditorDisplay({
...ITEM,
metadata: { metric: 'reclaimed', hideZero: 'true' }
})).toEqual({
displayText: 'Compaction Counter',
modifierText: '(reclaimed value, hide zero)'
});
});

it('uses only metric and hide-zero keybinds in metric mode', () => {
expect(new CompactionCounterWidget().getCustomKeybinds({
...ITEM,
metadata: { metric: 'auto' }
})).toEqual([
{ key: 'm', label: '(m)etric', action: 'cycle-metric' },
{ key: 'h', label: '(h)ide when zero', action: 'toggle-hide-zero' }
]);
});

it('cycles count -> auto -> manual -> unknown -> reclaimed -> count', () => {
const widget = new CompactionCounterWidget();
const auto = widget.handleEditorAction('cycle-metric', ITEM);
const manual = widget.handleEditorAction('cycle-metric', auto ?? ITEM);
const unknown = widget.handleEditorAction('cycle-metric', manual ?? ITEM);
const reclaimed = widget.handleEditorAction('cycle-metric', unknown ?? ITEM);
const count = widget.handleEditorAction('cycle-metric', reclaimed ?? ITEM);

expect(auto?.metadata?.metric).toBe('auto');
expect(manual?.metadata?.metric).toBe('manual');
expect(unknown?.metadata?.metric).toBe('unknown');
expect(reclaimed?.metadata?.metric).toBe('reclaimed');
expect(count?.metadata?.metric).toBeUndefined();
});
});
});