Two changes that pull in the same direction: the panel says less, and the menu stops pretending to do anything. The chip row had no bound. It sits in the centre box next to the clock, so enough open sessions would have shoved the clock off centre. It now shows the first N, settable and three by default, and counts the rest as "+N". Chips are already ordered by urgency, so the ones that survive the cut are the ones that need you soonest. Labels are still assigned across every session, including hidden ones, so a chip does not change when the cap does or when a session ahead of it disappears. Clicking a menu row used to switch the zellij tab and raise a terminal. That is gone. It cost real machinery for what it saved -- gnome-terminal runs every window under one shared server process, so windows cannot be matched by pid and the code fell back to matching the zellij session name against window titles, with all the ways that misses. The menu reports status; alt-tab is not the bottleneck. Rows are built inert rather than demoted after the fact, because PopupBaseMenuItem latches _activatable in its constructor. zellij tab lookup stays: naming the tab is the better half of that feature and costs one process every couple of minutes. The preferences test now asserts a control per settings key rather than a switch per key, so the new spin row counts and a future non-boolean setting cannot slip in without one.
122 lines
4.9 KiB
JavaScript
122 lines
4.9 KiB
JavaScript
import Adw from 'gi://Adw';
|
|
import Gtk from 'gi://Gtk';
|
|
import Gio from 'gi://Gio';
|
|
import GLib from 'gi://GLib';
|
|
|
|
import { ExtensionPreferences, gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
|
|
|
|
export default class ClaudeCodeStatusPreferences extends ExtensionPreferences {
|
|
fillPreferencesWindow(window) {
|
|
const settings = this.getSettings();
|
|
|
|
const page = new Adw.PreferencesPage({
|
|
title: _('General'),
|
|
icon_name: 'utilities-terminal-symbolic',
|
|
});
|
|
window.add(page);
|
|
|
|
const dispGroup = new Adw.PreferencesGroup({ title: _('Panel') });
|
|
page.add(dispGroup);
|
|
|
|
dispGroup.add(this._switchRow(settings, 'show-project-name',
|
|
_('Label chips with the project'),
|
|
_('Name the sessions, not just their states.')));
|
|
dispGroup.add(this._switchRow(settings, 'abbreviate-names',
|
|
_('Shorten names to three characters'),
|
|
_('“dev-skills” becomes “ds”. Collisions get a digit by seniority, so a label already on screen never changes.')));
|
|
dispGroup.add(this._spinRow(settings, 'max-chips',
|
|
_('Chips shown'),
|
|
_('The most urgent sessions get a chip; the rest are counted as “+N”.'),
|
|
1, 12));
|
|
dispGroup.add(this._switchRow(settings, 'show-age',
|
|
_('Show time in state'),
|
|
_('Shown on the most urgent session only.')));
|
|
|
|
const zellijGroup = new Adw.PreferencesGroup({
|
|
title: _('zellij'),
|
|
description: _('Sessions running inside zellij can be located by tab name instead of by path.'),
|
|
});
|
|
page.add(zellijGroup);
|
|
|
|
zellijGroup.add(this._switchRow(settings, 'zellij-integration',
|
|
_('Resolve tab names'),
|
|
_('Name the zellij tab each session runs in. Ignored when zellij is not installed.')));
|
|
|
|
// --- Hooks ---------------------------------------------------------
|
|
// The indicator is only as good as the hooks feeding it, and a silent
|
|
// panel looks identical whether nothing is running or nothing is
|
|
// installed. Say which it is.
|
|
const hooksGroup = new Adw.PreferencesGroup({
|
|
title: _('Hooks'),
|
|
description: _('Claude Code writes one state file per session; the panel watches them.'),
|
|
});
|
|
page.add(hooksGroup);
|
|
|
|
hooksGroup.add(new Adw.ActionRow({
|
|
title: _('Status'),
|
|
subtitle: this._hooksStatus(),
|
|
}));
|
|
hooksGroup.add(new Adw.ActionRow({
|
|
title: _('State directory'),
|
|
subtitle: this._stateDir(),
|
|
}));
|
|
|
|
const installRow = new Adw.ActionRow({
|
|
title: _('Install command'),
|
|
subtitle: `${this.path}/hooks/install.py`,
|
|
});
|
|
const copyButton = new Gtk.Button({
|
|
icon_name: 'edit-copy-symbolic',
|
|
valign: Gtk.Align.CENTER,
|
|
tooltip_text: _('Copy to clipboard'),
|
|
});
|
|
copyButton.connect('clicked', () => {
|
|
window.get_clipboard().set(`${this.path}/hooks/install.py`);
|
|
});
|
|
installRow.add_suffix(copyButton);
|
|
hooksGroup.add(installRow);
|
|
}
|
|
|
|
_stateDir() {
|
|
const base = GLib.getenv('XDG_STATE_HOME') ||
|
|
GLib.build_filenamev([GLib.get_home_dir(), '.local', 'state']);
|
|
return GLib.build_filenamev([base, 'claude-code-status']);
|
|
}
|
|
|
|
_hooksStatus() {
|
|
const path = GLib.build_filenamev([GLib.get_home_dir(), '.claude', 'settings.json']);
|
|
try {
|
|
const [ok, bytes] = GLib.file_get_contents(path);
|
|
if (!ok)
|
|
return _('~/.claude/settings.json is unreadable');
|
|
const settings = JSON.parse(new TextDecoder().decode(bytes));
|
|
const events = Object.entries(settings.hooks ?? {})
|
|
.filter(([, groups]) => groups.some(g =>
|
|
(g.hooks ?? []).some(h => (h.command ?? '').includes('claude-status-hook.py'))))
|
|
.map(([event]) => event);
|
|
if (!events.length)
|
|
return _('Not installed — run the install command below, then restart your sessions');
|
|
return `${_('Installed for')}: ${events.join(', ')}`;
|
|
} catch (e) {
|
|
return _('~/.claude/settings.json could not be parsed');
|
|
}
|
|
}
|
|
|
|
_spinRow(settings, key, title, subtitle, lower, upper) {
|
|
const row = new Adw.SpinRow({
|
|
title, subtitle,
|
|
adjustment: new Gtk.Adjustment({
|
|
lower, upper, step_increment: 1, page_increment: 1,
|
|
}),
|
|
});
|
|
settings.bind(key, row, 'value', Gio.SettingsBindFlags.DEFAULT);
|
|
return row;
|
|
}
|
|
|
|
_switchRow(settings, key, title, subtitle) {
|
|
const row = new Adw.SwitchRow({ title, subtitle });
|
|
settings.bind(key, row, 'active', Gio.SettingsBindFlags.DEFAULT);
|
|
return row;
|
|
}
|
|
}
|