···2525- Supervisor logs every skip reason in `doRestart` for debugging
2626- Supervisor state directory moved to `~/.local/state/pty/supervisor/` (no longer pollutes session dir)
27272828+### TUI framework
2929+- Export `SelectableGroup<T>` interface from `@myobie/pty/tui`
3030+- `groupedSelectable` `renderHeader` callback now receives the full group object instead of `(title, count)` — allows custom group rendering
3131+- Empty groups are now rendered (header shown) instead of being silently skipped
3232+2833## 0.7.0
29343035### Supervisor
+1-1
README.md
···192192tags = { strategy = "permanent" }
193193```
194194195195-For macOS auto-start on login: `pty supervisor launchd install`.
195195+For macOS auto-start on login: `pty supervisor launchd install`. The install will compile a small wrapper binary and prompt you to grant it Full Disk Access (required for sessions on external/removable volumes). Linux (systemd) and other OS integrations are a TODO — contributions welcome.
196196197197### Plugins
198198
+1-1
src/cli.ts
···581581 pty supervisor status Show supervised sessions
582582 pty supervisor forget <name> Stop supervising a session
583583 pty supervisor reset <name> Reset a failed session for retry
584584- pty supervisor launchd install [--path PATH] Register with macOS launchd
584584+ pty supervisor launchd install [--path PATH] Register with macOS launchd (requires FDA)
585585 pty supervisor launchd uninstall Remove from launchd`);
586586 break;
587587 }
+9-5
src/tui/builders.ts
···166166 * Headers and spacing rows are included in the visual output but the
167167 * scroll offset is mapped from item-space to visual-row-space automatically.
168168 */
169169+export interface SelectableGroup<T> {
170170+ title: string;
171171+ items: T[];
172172+}
173173+169174export function groupedSelectable<T>(
170175 region: ScrollRegion,
171171- groups: { title: string; items: T[] }[],
176176+ groups: SelectableGroup<T>[],
172177 renderItem: (item: T, globalIndex: number, selected: boolean) => UINode[],
173173- renderHeader: (title: string, count: number) => UINode[] =
174174- (title, count) => [text(title, "accent", { bold: true }), text(` (${count})`, "muted")],
178178+ renderHeader: (group: SelectableGroup<T>) => UINode[] =
179179+ (group) => [text(group.title, "accent", { bold: true }), text(` (${group.items.length})`, "muted")],
175180): SelectableNode {
176181 const allRows: UINode[][] = [];
177182 let globalIdx = 0;
···179184 let selectedVisualRow = 0;
180185181186 for (const group of groups) {
182182- if (group.items.length === 0) continue;
183187 if (allRows.length > 0) allRows.push([]); // spacing
184184- allRows.push(renderHeader(group.title, group.items.length)); // header
188188+ allRows.push(renderHeader(group)); // header
185189 for (const item of group.items) {
186190 if (globalIdx === region.selectedIndex) {
187191 selectedVisualRow = allRows.length;