@pi-unipi/footer 0.1.2 → 0.1.4

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/README.md CHANGED
@@ -1,78 +1,65 @@
1
1
  # @pi-unipi/footer
2
2
 
3
- Persistent status bar for the Unipi extension suite.
3
+ Persistent status bar at the bottom of the terminal. Shows live stats from all Unipi packages — compactor tokens saved, memory count, MCP status, Ralph loops, workflow state, kanboard tasks, notifications.
4
4
 
5
- Subscribes to `UNIPI_EVENTS` and renders key stats from all unipi packages using pi's `setFooter` + `setWidget` APIs with responsive layout, presets, and per-segment toggling.
5
+ Subscribes to events from every package and renders segments using Pi's `setFooter` + `setWidget` APIs. Responsive layout adjusts to terminal width, with a secondary row for narrow terminals.
6
6
 
7
- ## Features
7
+ ## Commands
8
8
 
9
- - **Persistent status bar** — always-visible footer showing key stats from all unipi packages
10
- - **Segment groups** — organized by package (core, compactor, memory, MCP, ralph, workflow, kanboard, notify)
11
- - **Presets** default, minimal, compact, full, nerd, ascii
12
- - **Responsive layout** adjusts to terminal width with secondary row overflow
13
- - **Per-segment toggling** enable/disable individual segments or entire groups
14
- - **Theme integration** uses pi's theme system with semantic colors
15
- - **Nerd Font support** auto-detection with ASCII fallback
16
- - **Separator styles** powerline, powerline-thin, slash, pipe, dot, ascii
9
+ | Command | Description |
10
+ |---------|-------------|
11
+ | `/unipi:footer` | Toggle footer on/off |
12
+ | `/unipi:footer <preset>` | Switch preset (default, minimal, compact, full, nerd, ascii) |
13
+ | `/unipi:footer sep:<style>` | Change separator style |
14
+ | `/unipi:footer icon:<style>` | Change icon style (nerd, emoji, text) |
15
+ | `/unipi:footer on` / `/unipi:footer off` | Enable/disable explicitly |
16
+ | `/unipi:footer-settings` | Open settings TUI for per-group/per-segment toggles |
17
17
 
18
- ## Architecture
18
+ ## Special Triggers
19
19
 
20
- ```
21
- ┌─────────────────────────────────────────────────────┐
22
- │ FooterRenderer (setFooter + setWidget) │ ← Renders to screen
23
- │ - Responsive layout (top + secondary rows) │
24
- │ - Preset system, separators, theming │
25
- ├─────────────────────────────────────────────────────┤
26
- │ FooterRegistry (segment groups) │ ← Manages segments
27
- │ - Subscribes to UNIPI_EVENTS │
28
- │ - Per-segment enable/disable │
29
- │ - Reactive data caching │
30
- ├─────────────────────────────────────────────────────┤
31
- │ Event Sources (existing packages) │ ← Data providers
32
- │ - compactor, memory, workflow, ralph, mcp, │
33
- │ kanboard, notify, core │
34
- └─────────────────────────────────────────────────────┘
35
- ```
20
+ Footer subscribes to events from every Unipi package:
36
21
 
37
- ## Usage
22
+ | Group | Events | Segments |
23
+ |-------|--------|----------|
24
+ | core | Pi SDK | model, thinking, path, git, context_pct, cost, tokens, session |
25
+ | compactor | `COMPACTOR_STATS_UPDATED` | session_events, compactions, tokens_saved, compression_ratio |
26
+ | memory | `MEMORY_STORED`/`DELETED`/`CONSOLIDATED` | project_count, total_count, consolidations |
27
+ | mcp | `MCP_SERVER_STARTED`/`STOPPED`/`ERROR` | servers_total, servers_active, tools_total |
28
+ | ralph | `RALPH_LOOP_START`/`END`/`ITERATION_DONE` | active_loops, total_iterations, loop_status |
29
+ | workflow | `WORKFLOW_START`/`END` | current_command, sandbox_level, command_duration |
30
+ | kanboard | Direct registry read | docs_count, tasks_done, tasks_total, task_pct |
31
+ | notify | `NOTIFICATION_SENT` | platforms_enabled, last_sent |
38
32
 
39
- The footer is automatically enabled when unipi loads. Use commands to control it:
33
+ Footer works even if packages load after it late-arriving events update the cache.
40
34
 
41
- - `/unipi:footer` — toggle footer on/off
42
- - `/unipi:footer <preset>` — switch preset (default, minimal, compact, full, nerd, ascii)
43
- - `/unipi:footer sep:<style>` — change separator style (powerline, powerline-thin, slash, pipe, dot, ascii)
44
- - `/unipi:footer icon:<style>` — change icon style (nerd, emoji, text)
45
- - `/unipi:footer on` / `/unipi:footer off` — enable/disable explicitly
46
- - `/unipi:footer-settings` — open settings TUI for per-group/per-segment toggles
47
-
48
- ## Segment Groups
35
+ ## Presets
49
36
 
50
- | Group | Segments | Default | Data Source |
51
- |-------|----------|---------|-------------|
52
- | **core** | `model`, `thinking`, `path`, `git`, `context_pct`, `cost`, `tokens_total`, `tokens_in`, `tokens_out`, `session`, `hostname`, `time` | ON (except hostname, time, tokens variants) | pi SDK (ctx.sessionManager, footerData) |
53
- | **compactor** | `session_events`, `compactions`, `tokens_saved`, `compression_ratio`, `indexed_docs`, `sandbox_runs`, `search_queries` | ON (key stats only) | `COMPACTOR_STATS_UPDATED` event |
54
- | **memory** | `project_count`, `total_count`, `consolidations` | ON | `MEMORY_STORED`/`DELETED`/`CONSOLIDATED` events |
55
- | **mcp** | `servers_total`, `servers_active`, `tools_total`, `servers_failed` | ON | `MCP_SERVER_STARTED`/`STOPPED`/`ERROR` events |
56
- | **ralph** | `active_loops`, `total_iterations`, `loop_status` | ON | `RALPH_LOOP_START`/`END`/`ITERATION_DONE` events |
57
- | **workflow** | `current_command`, `sandbox_level`, `command_duration` | ON | `WORKFLOW_START`/`END` events |
58
- | **kanboard** | `docs_count`, `tasks_done`, `tasks_total`, `task_pct` | ON | Kanboard registry (direct read) |
59
- | **notify** | `platforms_enabled`, `last_sent` | OFF | `NOTIFICATION_SENT` event |
60
- | **status_ext** | `extension_statuses` | ON | `footerData.getExtensionStatuses()` |
37
+ | Preset | Description |
38
+ |--------|-------------|
39
+ | `default` | Balanced: model, thinking, path, git, context, cost + compactor + memory + ralph |
40
+ | `minimal` | Essentials only: path, git, context |
41
+ | `compact` | Core + key stats: model, git, cost, context + compactor + memory |
42
+ | `full` | Everything from all groups |
43
+ | `nerd` | Full + hostname + time + session + extensions |
44
+ | `ascii` | Core segments with ASCII icons |
61
45
 
62
- ## Presets
46
+ ## Segment Groups
63
47
 
64
- | Preset | Description | Key Segments |
65
- |--------|-------------|-------------|
66
- | `default` | Balanced view | model, thinking, path, git, context, cost + compactor + memory + ralph |
67
- | `minimal` | Just the essentials | path, git, context |
68
- | `compact` | Core + key stats | model, git, cost, context + compactor + memory |
69
- | `full` | Everything | All segments from all groups |
70
- | `nerd` | Maximum detail for Nerd Font users | full + hostname + time + session + extensions |
71
- | `ascii` | Safe for any terminal | Core segments with ASCII icons |
48
+ | Group | Default | Data Source |
49
+ |-------|---------|-------------|
50
+ | **core** | ON | Pi SDK (ctx.sessionManager, footerData) |
51
+ | **compactor** | ON | `COMPACTOR_STATS_UPDATED` event |
52
+ | **memory** | ON | `MEMORY_STORED`/`DELETED`/`CONSOLIDATED` events |
53
+ | **mcp** | ON | `MCP_SERVER_STARTED`/`STOPPED`/`ERROR` events |
54
+ | **ralph** | ON | `RALPH_LOOP_START`/`END`/`ITERATION_DONE` events |
55
+ | **workflow** | ON | `WORKFLOW_START`/`END` events |
56
+ | **kanboard** | ON | Kanboard registry (direct read) |
57
+ | **notify** | OFF | `NOTIFICATION_SENT` event |
58
+ | **status_ext** | ON | `footerData.getExtensionStatuses()` |
72
59
 
73
- ## Configuration
60
+ ## Configurables
74
61
 
75
- Settings are stored in `~/.pi/agent/settings.json` under `unipi.footer`:
62
+ Settings in `~/.pi/agent/settings.json` under `unipi.footer`:
76
63
 
77
64
  ```json
78
65
  {
@@ -88,19 +75,7 @@ Settings are stored in `~/.pi/agent/settings.json` under `unipi.footer`:
88
75
  "segments": {
89
76
  "session_events": true,
90
77
  "compactions": true,
91
- "tokens_saved": true,
92
- "compression_ratio": false,
93
- "indexed_docs": false,
94
- "sandbox_runs": false,
95
- "search_queries": false
96
- }
97
- },
98
- "memory": {
99
- "show": true,
100
- "segments": {
101
- "project_count": true,
102
- "total_count": true,
103
- "consolidations": false
78
+ "tokens_saved": true
104
79
  }
105
80
  }
106
81
  }
@@ -109,98 +84,38 @@ Settings are stored in `~/.pi/agent/settings.json` under `unipi.footer`:
109
84
  }
110
85
  ```
111
86
 
112
- ## Responsive Layout
87
+ ### Separator Styles
113
88
 
114
- ```
115
- Wide terminal (>120 cols):
116
- ┌─ model thinking path │ git │ context │ cost │ compactions │ tokens_saved │ project_count ─┐
117
- └──────────────────────────────────────────────────────────────────────────────────────────────┘
89
+ | Style | Look |
90
+ |-------|------|
91
+ | `powerline` | Thick powerline arrows |
92
+ | `powerline-thin` | Thin powerline arrows (default) |
93
+ | `slash` | / |
94
+ | `pipe` | \| |
95
+ | `dot` | Middle dot |
96
+ | `ascii` | > < |
118
97
 
119
- Narrow terminal (<120 cols):
120
- ┌─ model │ thinking │ path │ git │ context │ cost ───────────────────────────────────────────────┐
121
- └─ compactions │ tokens_saved │ project_count │ ralph │ workflow ────────────────────────────────┘
122
- ```
123
-
124
- ## Separator Styles
125
-
126
- | Style | Look | Description |
127
- |-------|------|-------------|
128
- | `powerline` | ◀ ▶ | Thick powerline arrows |
129
- | `powerline-thin` | | Thin powerline arrows (default) |
130
- | `slash` | / | Slash separator |
131
- | `pipe` | \| | Pipe separator |
132
- | `dot` | · | Middle dot separator |
133
- | `ascii` | > < | ASCII angle brackets |
134
-
135
- ## Icon Styles
136
-
137
- Three icon styles are available, controlled by `/unipi:footer icon:<style>` or the `iconStyle` setting:
138
-
139
- | Style | Description | Example |
140
- -------|-------------|--------|
141
- | `nerd` | Nerd Font glyphs (default, requires Nerd Font terminal) | , , |
142
- | `emoji` | Unicode emoji/symbols (works on most terminals) | ⚡, ◧, $] |
143
- | `text` | Plain text abbreviations (works everywhere, most compact) | evt, cmp, $] |
98
+ ### Icon Styles
144
99
 
145
- When `iconStyle` is not explicitly set, the footer auto-detects Nerd Font support and
146
- defaults to `nerd` if available, `emoji` otherwise.
100
+ | Style | Description |
101
+ |-------|-------------|
102
+ | `nerd` | Nerd Font glyphs (auto-detected) |
103
+ | `emoji` | Unicode symbols (works on most terminals) |
104
+ | `text` | Plain text abbreviations (works everywhere) |
147
105
 
148
- ## Error Handling
106
+ When `iconStyle` is not set, footer auto-detects Nerd Font support and defaults to `nerd` if available, `emoji` otherwise.
149
107
 
150
- - **Event subscription failures:** Each handler wrapped in try/catch — one failing handler doesn't break others
151
- - **Data provider failures:** Segments hide when data unavailable (graceful degradation)
152
- - **Config parse failures:** Fall back to default preset with warning
153
- - **Module loading order:** Footer works even if packages load after it — late-arriving events update cache
108
+ ### Responsive Layout
154
109
 
155
- ## Development
156
-
157
- ```bash
158
- # Run tests
159
- pnpm test
110
+ ```
111
+ Wide terminal (>120 cols):
112
+ model | thinking | path | git | context | cost | compactions | tokens_saved | project_count
160
113
 
161
- # Type check
162
- pnpm tsc --noEmit
114
+ Narrow terminal (<120 cols):
115
+ Row 1: model | thinking | path | git | context | cost
116
+ Row 2: compactions | tokens_saved | project_count | ralph | workflow
163
117
  ```
164
118
 
165
- ## Package Structure
119
+ ## License
166
120
 
167
- ```
168
- packages/footer/
169
- ├── index.ts # Re-exports
170
- ├── types.ts # Re-exports from src/types.ts
171
- ├── package.json # Package manifest
172
- ├── tsconfig.json # TypeScript config
173
- ├── README.md # This file
174
- ├── src/
175
- │ ├── index.ts # Extension entry point
176
- │ ├── types.ts # Type definitions
177
- │ ├── config.ts # Settings load/save
178
- │ ├── events.ts # Event subscription wiring
179
- │ ├── commands.ts # Command registration
180
- │ ├── presets.ts # Preset definitions
181
- │ ├── registry/ # FooterRegistry
182
- │ │ └── index.ts
183
- │ ├── rendering/ # Rendering engine
184
- │ │ ├── renderer.ts # FooterRenderer class
185
- │ │ ├── separators.ts # Separator system
186
- │ │ ├── theme.ts # Theme color resolution
187
- │ │ └── icons.ts # Icon system with Nerd Font detection
188
- │ ├── segments/ # Segment implementations
189
- │ │ ├── core.ts # Core segments (model, path, git, etc.)
190
- │ │ ├── compactor.ts # Compactor segments
191
- │ │ ├── memory.ts # Memory segments
192
- │ │ ├── mcp.ts # MCP segments
193
- │ │ ├── ralph.ts # Ralph segments
194
- │ │ ├── workflow.ts # Workflow segments
195
- │ │ ├── kanboard.ts # Kanboard segments
196
- │ │ ├── notify.ts # Notify segments
197
- │ │ └── status-ext.ts # Extension statuses segment
198
- │ └── tui/
199
- │ └── settings-tui.ts # Settings overlay TUI
200
- └── tests/ # Unit tests
201
- ├── separators.test.ts
202
- ├── registry.test.ts
203
- ├── config.test.ts
204
- ├── segments.test.ts
205
- └── events.test.ts
206
- ```
121
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/footer",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Persistent status bar for Unipi — subscribes to UNIPI_EVENTS and renders key stats from all unipi packages",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/commands.ts CHANGED
@@ -8,34 +8,9 @@
8
8
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
9
9
  import { UNIPI_PREFIX, FOOTER_COMMANDS } from "@pi-unipi/core";
10
10
  import { loadFooterSettings, saveFooterSettings } from "./config.js";
11
- import { PRESET_NAMES } from "./presets.js";
12
11
  import { showFooterSettings } from "./tui/settings-tui.js";
13
- import type { FooterGroup, SeparatorStyle, IconStyle } from "./types.js";
14
- import { setIconStyle } from "./rendering/icons.js";
15
-
16
- /** Minimal autocomplete item (compatible with pi-tui AutocompleteItem) */
17
- interface ArgSuggestion {
18
- value: string;
19
- label: string;
20
- description?: string;
21
- }
22
-
23
- /** All valid separator styles */
24
- const SEPARATOR_STYLES: SeparatorStyle[] = [
25
- "powerline",
26
- "powerline-thin",
27
- "slash",
28
- "pipe",
29
- "dot",
30
- "ascii",
31
- ];
32
-
33
- /** All valid icon styles */
34
- const ICON_STYLES: IconStyle[] = [
35
- "nerd",
36
- "emoji",
37
- "text",
38
- ];
12
+ import { showFooterHelp } from "./help.js";
13
+ import type { FooterGroup, FooterSegment } from "./types.js";
39
14
 
40
15
  /** Extension state interface */
41
16
  interface FooterState {
@@ -46,7 +21,9 @@ interface FooterState {
46
21
  getPresetName(): string;
47
22
  resetLayoutCache(): void;
48
23
  };
24
+ segmentLookup: Map<string, FooterSegment>;
49
25
  piContext: unknown;
26
+ setupUI: ((pi: ExtensionAPI, ctx: any) => void) | null;
50
27
  }
51
28
 
52
29
  /**
@@ -57,75 +34,23 @@ export function registerCommands(
57
34
  state: FooterState,
58
35
  groups?: FooterGroup[],
59
36
  ): void {
60
- // /unipi:footer — toggle or switch preset
37
+ // /unipi:footer — toggle on/off only
61
38
  pi.registerCommand(`${UNIPI_PREFIX}${FOOTER_COMMANDS.FOOTER}`, {
62
- description: "Toggle footer or switch preset (default, minimal, compact, full, nerd, ascii)",
63
- getArgumentCompletions(argumentPrefix: string): ArgSuggestion[] | null {
64
- const allOptions: ArgSuggestion[] = [
65
- ...PRESET_NAMES.map(p => ({
66
- value: p,
67
- label: p,
68
- description: `Switch to ${p} preset`,
69
- })),
70
- ...SEPARATOR_STYLES.map(s => ({
71
- value: `sep:${s}`,
72
- label: `sep:${s}`,
73
- description: `Set separator style: ${s}`,
74
- })),
75
- ...ICON_STYLES.map(s => ({
76
- value: `icon:${s}`,
77
- label: `icon:${s}`,
78
- description: `Set icon style: ${s}`,
79
- })),
80
- {
81
- value: "on",
82
- label: "on",
83
- description: "Enable footer",
84
- },
85
- {
86
- value: "off",
87
- label: "off",
88
- description: "Disable footer",
89
- },
90
- ];
91
-
92
- if (!argumentPrefix) return allOptions;
93
-
94
- const prefix = argumentPrefix.toLowerCase();
95
- const filtered = allOptions.filter(o =>
96
- o.value.toLowerCase().startsWith(prefix),
97
- );
98
- return filtered.length > 0 ? filtered : null;
99
- },
39
+ description: "Toggle footer on/off",
100
40
  handler: async (args, ctx) => {
101
- if (!args?.trim()) {
102
- // Toggle on/off
103
- state.enabled = !state.enabled;
104
- state.renderer.setActive(state.enabled);
105
-
106
- if (state.enabled) {
107
- ctx.ui.notify("Footer enabled", "info");
108
- } else {
109
- ctx.ui.setFooter(undefined);
110
- ctx.ui.setWidget("footer-top", undefined);
111
- ctx.ui.setWidget("footer-secondary", undefined);
112
- ctx.ui.notify("Footer disabled", "info");
113
- }
41
+ const arg = args?.trim().toLowerCase();
114
42
 
115
- saveFooterSettings({ enabled: state.enabled });
116
- return;
117
- }
118
-
119
- const arg = args.trim().toLowerCase();
120
-
121
- // on / off
43
+ // on
122
44
  if (arg === "on") {
123
45
  state.enabled = true;
124
46
  state.renderer.setActive(true);
125
47
  saveFooterSettings({ enabled: true });
48
+ state.setupUI?.(pi, ctx);
126
49
  ctx.ui.notify("Footer enabled", "info");
127
50
  return;
128
51
  }
52
+
53
+ // off
129
54
  if (arg === "off") {
130
55
  state.enabled = false;
131
56
  state.renderer.setActive(false);
@@ -137,42 +62,21 @@ export function registerCommands(
137
62
  return;
138
63
  }
139
64
 
140
- // sep:<style> change separator
141
- if (arg.startsWith("sep:")) {
142
- const style = arg.slice(4) as SeparatorStyle;
143
- if (SEPARATOR_STYLES.includes(style)) {
144
- saveFooterSettings({ separator: style });
145
- state.renderer.resetLayoutCache();
146
- ctx.ui.notify(`Separator: ${style}`, "info");
147
- return;
148
- }
149
- ctx.ui.notify(`Unknown separator. Available: ${SEPARATOR_STYLES.join(", ")}`, "warning");
150
- return;
151
- }
65
+ // Toggle (no args or unknown args)
66
+ state.enabled = !state.enabled;
67
+ state.renderer.setActive(state.enabled);
152
68
 
153
- // icon:<style> — change icon style
154
- if (arg.startsWith("icon:")) {
155
- const style = arg.slice(5) as IconStyle;
156
- if (ICON_STYLES.includes(style)) {
157
- saveFooterSettings({ iconStyle: style });
158
- setIconStyle(style);
159
- state.renderer.resetLayoutCache();
160
- ctx.ui.notify(`Icon style: ${style}`, "info");
161
- return;
162
- }
163
- ctx.ui.notify(`Unknown icon style. Available: ${ICON_STYLES.join(", ")}`, "warning");
164
- return;
165
- }
166
-
167
- // Preset name
168
- if (PRESET_NAMES.includes(arg)) {
169
- state.renderer.setPreset(arg);
170
- saveFooterSettings({ preset: arg });
171
- ctx.ui.notify(`Footer preset: ${arg}`, "info");
172
- return;
69
+ if (state.enabled) {
70
+ state.setupUI?.(pi, ctx);
71
+ ctx.ui.notify("Footer enabled", "info");
72
+ } else {
73
+ ctx.ui.setFooter(undefined);
74
+ ctx.ui.setWidget("footer-top", undefined);
75
+ ctx.ui.setWidget("footer-secondary", undefined);
76
+ ctx.ui.notify("Footer disabled", "info");
173
77
  }
174
78
 
175
- ctx.ui.notify(`Unknown argument. Use a preset (${PRESET_NAMES.join(", ")}), sep:<style>, icon:<style>, on, or off`, "info");
79
+ saveFooterSettings({ enabled: state.enabled });
176
80
  },
177
81
  });
178
82
 
@@ -186,7 +90,12 @@ export function registerCommands(
186
90
  }
187
91
 
188
92
  if (groups && groups.length > 0) {
189
- showFooterSettings(ctx, groups);
93
+ showFooterSettings(ctx, groups, () => {
94
+ // Re-read settings and update renderer
95
+ const updated = loadFooterSettings();
96
+ state.renderer.setPreset(updated.preset);
97
+ state.renderer.resetLayoutCache();
98
+ });
190
99
  } else {
191
100
  // Fallback: show text summary
192
101
  const settings = loadFooterSettings();
@@ -201,4 +110,13 @@ export function registerCommands(
201
110
  }
202
111
  },
203
112
  });
113
+
114
+ // /unipi:footer-help — show help overlay
115
+ pi.registerCommand(`${UNIPI_PREFIX}${FOOTER_COMMANDS.FOOTER_HELP}`, {
116
+ description: "Show footer segment guide (icons, labels, descriptions)",
117
+ handler: async (_args, _ctx) => {
118
+ const allSegments = Array.from(state.segmentLookup.values());
119
+ showFooterHelp(pi, allSegments, state.renderer.getPresetName());
120
+ },
121
+ });
204
122
  }
package/src/config.ts CHANGED
@@ -17,6 +17,8 @@ export const DEFAULT_FOOTER_SETTINGS: FooterSettings = {
17
17
  preset: "default",
18
18
  separator: "powerline-thin",
19
19
  iconStyle: "nerd",
20
+ zoneSeparator: "\u2502", // │
21
+ showFullLabels: false,
20
22
  groups: {
21
23
  core: { show: true, segments: {} },
22
24
  compactor: { show: true, segments: {} },
@@ -48,8 +50,8 @@ function readSettingsFile(): Record<string, unknown> | null {
48
50
  if (!fs.existsSync(settingsPath)) return null;
49
51
  const raw = fs.readFileSync(settingsPath, "utf-8");
50
52
  return JSON.parse(raw) as Record<string, unknown>;
51
- } catch (err) {
52
- console.warn("[footer] Failed to read settings.json:", err);
53
+ } catch {
54
+ // Silently ignore — settings read failure falls back to null.
53
55
  return null;
54
56
  }
55
57
  }
@@ -66,8 +68,8 @@ function writeSettingsFile(settings: Record<string, unknown>): boolean {
66
68
  }
67
69
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
68
70
  return true;
69
- } catch (err) {
70
- console.warn("[footer] Failed to write settings.json:", err);
71
+ } catch {
72
+ // Silently ignore — settings write failure is non-blocking.
71
73
  return false;
72
74
  }
73
75
  }
@@ -92,13 +94,15 @@ export function loadFooterSettings(): FooterSettings {
92
94
  preset: typeof footer.preset === "string" ? footer.preset : DEFAULT_FOOTER_SETTINGS.preset,
93
95
  separator: isValidSeparator(footer.separator) ? footer.separator as SeparatorStyle : DEFAULT_FOOTER_SETTINGS.separator,
94
96
  iconStyle: isValidIconStyle(footer.iconStyle) ? footer.iconStyle as IconStyle : DEFAULT_FOOTER_SETTINGS.iconStyle,
97
+ zoneSeparator: typeof footer.zoneSeparator === "string" ? footer.zoneSeparator : DEFAULT_FOOTER_SETTINGS.zoneSeparator,
98
+ showFullLabels: typeof footer.showFullLabels === "boolean" ? footer.showFullLabels : DEFAULT_FOOTER_SETTINGS.showFullLabels,
95
99
  groups: mergeGroupSettings(
96
100
  DEFAULT_FOOTER_SETTINGS.groups,
97
101
  footer.groups as Record<string, FooterGroupSettings> | undefined,
98
102
  ),
99
103
  };
100
- } catch (err) {
101
- console.warn("[footer] Failed to parse footer settings, using defaults:", err);
104
+ } catch {
105
+ // Silently ignore parse failure falls back to defaults.
102
106
  return { ...DEFAULT_FOOTER_SETTINGS };
103
107
  }
104
108
  }