Files
claude-code-gnome-extension/tests/test-abbrev.js
T
av a4e5d653de Show one chip per session, right of the clock
The panel showed a single aggregate: the most urgent session, plus a "+N"
for the others. That answers "what is the worst thing happening", which
is not the question with five projects open -- "what is each of them
doing" needs each of them on screen.

Each session now gets a chip: state glyph plus a three-character project
label, in the centre box just right of the clock. Time in state stays on
the first chip only; five counters side by side are a row of numbers,
not an answer.

Labels are initials for multi-segment names, first letters otherwise.
Initials rather than a prefix, because a prefix collapses dev-skills and
dev-conventions onto the same "dev" -- exactly the pair that has to stay
apart. Collisions, including two sessions in one project where the cwd
is identical, take a digit: ds, ds2, ds3.

Two rules keep a label still, and the feature is worthless without them.
Assignment runs oldest-session-first, so a session starting now takes
the suffix instead of displacing one already on screen; and a label
belongs to its session until it ends, even after whatever forced the
digit has closed. A label that moves under your hand is worse than one
carrying a digit that no longer looks necessary.

The zellij tab name was considered as the label source and dropped. It
arrives asynchronously from dump-layout, so a sticky label would freeze
whatever the project name produced first and never adopt it. Digits
already separate same-project sessions, so the tab name buys nothing
here and stays where it is useful, in the menu.

Menu rows now lead with the same label, so the mapping from "ds" to
dev-skills is read rather than guessed.
2026-08-09 18:45:44 +03:00

78 lines
3.5 KiB
JavaScript

#!/usr/bin/gjs -m
// Chip labelling: shortening, collisions, and stickiness.
//
// Run: gjs -m tests/test-abbrev.js (or: node tests/test-abbrev.js)
import { abbreviate, assignChips } from '../lib/abbrev.js';
let failures = 0;
const out = typeof print === 'function' ? print : console.log;
function check(name, expected, actual) {
const ok = JSON.stringify(expected) === JSON.stringify(actual);
if (!ok)
failures++;
out(`${ok ? 'ok ' : 'FAIL'} ${name}${ok ? '' : ` (expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)})`}`);
}
// --- shortening ------------------------------------------------------------
check('initials for multi-segment names', 'ds', abbreviate('dev-skills'));
check('a plain prefix would have collided here', 'dc', abbreviate('dev-conventions'));
check('three segments', 'pps', abbreviate('pet-project-server'));
check('four segments truncate to three', 'ccg', abbreviate('claude-code-gnome-extension'));
check('single word takes first letters', 'jel', abbreviate('jellybit'));
check('short single word stays short', 'um', abbreviate('um'));
check('camelCase counts as segments', 'om', abbreviate('outlineMcp'));
check('digits survive', 'p2', abbreviate('proj_2'));
check('empty name does not crash', '?', abbreviate(''));
// --- collisions ------------------------------------------------------------
const two = [
{ sessionId: 'a', since: 100, base: 'dev-skills' },
{ sessionId: 'b', since: 200, base: 'dev-skills' },
];
let map = assignChips(two);
check('oldest keeps the clean label', 'ds', map.get('a'));
check('newer takes the digit', 'ds2', map.get('b'));
const three = [...two, { sessionId: 'c', since: 300, base: 'dev-skills' }];
map = assignChips(three, map);
check('third in the same project', 'ds3', map.get('c'));
check('adding one does not disturb the first', 'ds', map.get('a'));
check('adding one does not disturb the second', 'ds2', map.get('b'));
// Order of the input array must not matter -- only start time does.
const shuffled = [three[2], three[0], three[1]];
const fromScratch = assignChips(shuffled);
check('assignment follows start time, not array order',
['ds', 'ds2', 'ds3'],
[fromScratch.get('a'), fromScratch.get('b'), fromScratch.get('c')]);
// --- stickiness ------------------------------------------------------------
// The point of the whole module: closing the session that forced the digit
// must not renumber the one still on screen.
const afterClose = assignChips([three[1], three[2]], map);
check('surviving session keeps its digit', 'ds2', afterClose.get('b'));
check('and so does the one after it', 'ds3', afterClose.get('c'));
// A label freed by a closed session may be handed to a genuinely new one.
const reused = assignChips(
[three[1], { sessionId: 'd', since: 400, base: 'dev-skills' }], afterClose);
check('freed label is reused', 'ds', reused.get('d'));
check('existing session still untouched', 'ds2', reused.get('b'));
// --- widths ----------------------------------------------------------------
const many = Array.from({ length: 12 }, (_, i) =>
({ sessionId: `s${i}`, since: i, base: 'umbar' }));
const wide = assignChips(many);
const lengths = [...new Set([...wide.values()].map(l => l.length))];
check('every label fits in three characters', [3], lengths);
check('twelve sessions in one project are all distinct',
12, new Set(wide.values()).size);
out(failures ? `\n${failures} failure(s)` : '\nall passed');
if (typeof imports !== 'undefined')
imports.system.exit(failures ? 1 : 0);
else if (failures)
process.exit(1);