@pellux/goodvibes-tui 0.19.66 → 0.19.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,17 @@ All notable changes to GoodVibes TUI.
8
8
 
9
9
  ---
10
10
 
11
+ ## [0.19.67] — 2026-05-06
12
+
13
+ ### Fixed
14
+ - Styled `/config` category group headings in bold and indented category rows so the category rail hierarchy is easier to scan.
15
+
16
+ ### Verified
17
+ - `bun test src/test/renderer/settings-modal.test.ts`
18
+ - `bunx tsc --noEmit`
19
+
20
+ ---
21
+
11
22
  ## [0.19.66] — 2026-05-06
12
23
 
13
24
  ### Fixed
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml/badge.svg)](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![Version](https://img.shields.io/badge/version-0.19.66-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
5
+ [![Version](https://img.shields.io/badge/version-0.19.67-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
6
6
 
7
7
  A terminal-native AI coding, operations, automation, knowledge, and integration console with a typed runtime, omnichannel surfaces, structured memory/knowledge, and a raw ANSI renderer.
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-tui",
3
- "version": "0.19.66",
3
+ "version": "0.19.67",
4
4
  "description": "Terminal-native GoodVibes product for coding, operations, automation, knowledge, channels, and daemon-backed control-plane workflows.",
5
5
  "type": "module",
6
6
  "main": "src/main.ts",
@@ -417,6 +417,12 @@ type CategoryRailEntry =
417
417
  | { readonly type: 'group'; readonly label: string }
418
418
  | { readonly type: 'category'; readonly category: SettingsCategory; readonly index: number };
419
419
 
420
+ type CategoryRailRow = {
421
+ readonly text: string;
422
+ readonly type: CategoryRailEntry['type'] | 'more' | 'empty';
423
+ readonly selected: boolean;
424
+ };
425
+
420
426
  function buildCategoryRailEntries(): CategoryRailEntry[] {
421
427
  const entries: CategoryRailEntry[] = [];
422
428
  for (const group of SETTINGS_CATEGORY_GROUPS) {
@@ -434,26 +440,26 @@ function buildCategoryRailEntries(): CategoryRailEntry[] {
434
440
  return entries;
435
441
  }
436
442
 
437
- function renderCategories(modal: SettingsModal, width: number, height: number): string[] {
438
- const rows: string[] = [];
443
+ function renderCategories(modal: SettingsModal, width: number, height: number): CategoryRailRow[] {
444
+ const rows: CategoryRailRow[] = [];
439
445
  const entries = buildCategoryRailEntries();
440
446
  const selectedEntryIndex = Math.max(0, entries.findIndex(entry => entry.type === 'category' && entry.index === modal.categoryIndex));
441
447
  const window = stableWindow(entries.length, selectedEntryIndex, height);
442
- if (window.start > 0) rows.push(`${GLYPHS.navigation.moreAbove} ${window.start} more row(s) above`);
448
+ if (window.start > 0) rows.push({ text: `${GLYPHS.navigation.moreAbove} ${window.start} more row(s) above`, type: 'more', selected: false });
443
449
  for (let railIndex = window.start; railIndex < window.end; railIndex += 1) {
444
450
  const entry = entries[railIndex]!;
445
451
  if (entry.type === 'group') {
446
- rows.push(` ${entry.label.toUpperCase()}`);
452
+ rows.push({ text: entry.label.toUpperCase(), type: 'group', selected: false });
447
453
  continue;
448
454
  }
449
455
  const category = entry.category;
450
456
  const active = entry.index === modal.categoryIndex;
451
457
  const count = categoryItemCount(modal, category);
452
458
  const cursor = active ? (modal.focusPane === 'categories' ? GLYPHS.navigation.selected : '•') : ' ';
453
- rows.push(`${cursor} ${CATEGORY_LABELS[category]} (${count})`);
459
+ rows.push({ text: ` ${cursor} ${CATEGORY_LABELS[category]} (${count})`, type: 'category', selected: active });
454
460
  }
455
- if (window.end < entries.length) rows.push(`${GLYPHS.navigation.moreBelow} ${entries.length - window.end} more row(s) below`);
456
- while (rows.length < height) rows.push('');
461
+ if (window.end < entries.length) rows.push({ text: `${GLYPHS.navigation.moreBelow} ${entries.length - window.end} more row(s) below`, type: 'more', selected: false });
462
+ while (rows.length < height) rows.push({ text: '', type: 'empty', selected: false });
457
463
  return rows.slice(0, height);
458
464
  }
459
465
 
@@ -643,13 +649,12 @@ export function renderSettingsModal(
643
649
  fillRange(line, 1, dividerX - 1, PALETTE.categoryBg);
644
650
  drawVertical(line, dividerX, bg);
645
651
 
646
- const categoryText = categoryRows[row] ?? '';
647
- const categoryActive = categoryText.startsWith(GLYPHS.navigation.selected) || categoryText.startsWith('•');
648
- if (categoryText.startsWith(GLYPHS.navigation.selected)) fillRange(line, leftStart, dividerX - 1, PALETTE.selectedBg);
649
- writeText(line, leftStart + 1, leftWidth - 3, categoryText, {
650
- fg: categoryActive ? PALETTE.text : PALETTE.muted,
651
- bg: categoryText.startsWith(GLYPHS.navigation.selected) ? PALETTE.selectedBg : PALETTE.categoryBg,
652
- bold: categoryActive,
652
+ const categoryRow = categoryRows[row] ?? { text: '', type: 'empty' as const, selected: false };
653
+ if (categoryRow.selected) fillRange(line, leftStart, dividerX - 1, PALETTE.selectedBg);
654
+ writeText(line, leftStart + 1, leftWidth - 3, categoryRow.text, {
655
+ fg: categoryRow.selected ? PALETTE.text : categoryRow.type === 'group' ? PALETTE.subtitle : PALETTE.muted,
656
+ bg: categoryRow.selected ? PALETTE.selectedBg : PALETTE.categoryBg,
657
+ bold: categoryRow.selected || categoryRow.type === 'group',
653
658
  });
654
659
 
655
660
  if (inSeparator) {
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '0.19.66';
9
+ let _version = '0.19.67';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
12
12
  _version = pkg.version ?? _version;