@aemonge-dev/pi-stardeck 0.10.0 → 0.12.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aemonge-dev/pi-stardeck",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "A Vim- and tmux-inspired command deck for Pi",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -48,10 +48,11 @@
48
48
  "check:openspec": "node scripts/check-openspec.mjs",
49
49
  "test:tooling": "node --import tsx/esm --test test/*.test.ts",
50
50
  "test:status": "npm --prefix packages/status test",
51
+ "test:work-state": "npm --prefix packages/work-state test",
51
52
  "test:stardeck": "npm --prefix packages/pi-stardeck test",
52
53
  "test:footer": "npm --prefix packages/footer test",
53
54
  "test:vim": "npm --prefix packages/pi-vim-top-border test",
54
- "test:components": "npm run test:status && npm run test:stardeck && npm run test:footer && npm run test:vim"
55
+ "test:components": "npm run test:status && npm run test:work-state && npm run test:stardeck && npm run test:footer && npm run test:vim"
55
56
  },
56
57
  "peerDependencies": {
57
58
  "@earendil-works/pi-ai": "*",
@@ -4,79 +4,46 @@ A dependency-free Pi extension that coordinates the prompt information architect
4
4
  through Pi's supported widget and event APIs.
5
5
 
6
6
  ```text
7
- ─ 󰠭 › ─────────────────────────────────────────────────────────────────────
8
- ( 3 › 2 · 00:05'12) working ⟩ checking Stabilize runtime and focus · step 3/5  task 1/2 › stps 3/5
9
- 󰓾 Stabilize runtime and focus in the cockpit
10
- ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
11
- [󰆧]  ~/projects/example  main ⟩  clean
12
- ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
13
- 󰚩 Opus › high ⟩ 󰾆 42% › 󰎞 2  10% › 󰜦 $0.10 ⟩  12% ›  375M ›  3/3
14
- ─ 󰏫 ─────────────────────────────────────────────────────────────── ‹ 󰠭 ─
7
+ ─ 󰠭 › ────────────────────────────────────────────────────────────────────
8
+ 󰠭 [󰆧] ~/projects/example › main › +0 ~1 1 󰚩 Opus › high › 󰃰 42%
9
+ ──────────────────────────────────────────────────────────────────────
10
+ Stabilize runtime and focus in the cockpit planning 6/7 › 󰄱 9/19
11
+ ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
12
+ editing deck.ts 00:38'00 · 4.2k
13
+ └ (00'12) scout working
14
+ ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
15
+ 󰃰 5h 52% › 󰆧 3% › 󰉡 48% › 󰞏 2 › 󰂻 268M › 󰄠 38% › 󰈛 $5.16 󰠭 abcd1234
15
16
  ```
16
17
 
17
- The preferred editor-deck surface keeps two stable semantic rows. The first is a
18
- left-packed operational strip: runtime capsule, lifecycle, activity, controlled next
19
- action, and Task/Step progress. The second is a full-width explicit selection canvas
20
- that wraps to at most two lines. Every slot remains present; zero, idle, unavailable,
21
- and unselected states are dim instead of appearing or disappearing. Long content may
22
- wrap responsively, but runtime state never changes the deck topology. Decorative rails
23
- remain slim and contain no telemetry.
24
-
25
- The editor deck uses blue for the active runtime capsule, the bold lifecycle anchor,
26
- and regular-weight explicit selection. Ordinary active activity remains neutral, except
27
- that `recovering` and its ordinary suggestion are blue together while only the lifecycle
28
- is bold. Other ordinary suggestions remain green. Incomplete Task/Step progress is
29
- neutral until each counter completes and stays right-aligned without a major boundary.
30
- The lifecycle/activity boundary and rails remain purple; minor separators are dim.
31
- Human-input and validation cues stay yellow, blockers stay red, and every inactive value
32
- stays dim.
33
-
34
- The legacy above-editor widget owns only current work identity and preserves the
35
- ` ready ⟩ no focused task` idle row when no work is selected. The leading icon is
36
- runtime-authoritative: `` while Pi is busy in an active lifecycle and `` while
37
- settled, waiting, blocked, listening, or aborted. Internal lifecycle states render as
38
- short readable verbs. The Ramona loop remains exact: `understand`, `work`, `assure`,
39
- `learn`, and `answer`. Runtime boundaries are `wait`, `blocked`, `ready`, or `stopped`.
40
- The title has four styled groups: lifecycle, a structured active ancestry, focus, and
41
- optional focus detail. Each activity-path segment receives the blue minor `›`; the
42
- violet-pink major `⟩` divides runtime ancestry from the green narrative focus. Human
43
- prompt text is never converted into focus automatically. OpenSpec may add its exact Task
44
- after another green `›`. Focus selection prefers an exact OpenSpec Task, then
45
- Session Work. Active turns without a selection use the explicit `OpenSpec › no focus`,
46
- `no OpenSpec`, or `no focus` state supplied by the status producer; `Direct response` is
47
- never presented as a focus. Empty states are dim while real focus remains bold semantic
48
- green.
49
-
50
- Wide rows render lifecycle → active ancestry → optional focus → optional focus detail,
51
- for example `assure › checking quality › assuring tests ⟩ Implement global flow`. The
52
- renderer generates every separator. Empty or rejected paths leave no separator or
53
- placeholder whitespace. As width falls it tries full labels, compact labels, a collapsed
54
- `… › deepest` ancestry, and finally compact lifecycle plus the deepest active segment.
55
- Narrative focus disappears before meaningful active ancestry. Terminal display-cell
56
- measurement and final truncation remain Unicode- and Nerd-Font-aware.
57
-
58
- Runtime anchor and lifecycle are bold blue `#076678`; activity ancestry and its minor
59
- `›` are regular blue; the major `⟩` is purple; focus is bold semantic success green; and
60
- optional focus hierarchy is regular success green. Dim is reserved for the two empty
61
- focus fallbacks; muted, text, cyan, yellow, and gray are never used in the title.
62
-
63
- The extension hides Pi's separate built-in `Working...` loader row with
64
- `ctx.ui.setWorkingVisible(false)`, including its vertical space. The header therefore
65
- uses its complete width for lifecycle, bounded activity, and focus. Runtime age is
66
- published for the bottom prompt rail as an eight-cell chronometer measuring time since
67
- the most recent observed foreground provider, assistant, or tool event. It stays
68
- fixed-width as it advances: `00:00'00`, `00:00'45`, `00:12'30`, or `12:34'56`; hour/day
69
- fallbacks also remain eight cells. Active timing uses the high-contrast deep-cyan
70
- `accent` color; settled states display dim `00:00'00` rather than repeating the header's
71
- `wait`, `ready`, or other lifecycle word. A session-scoped ticker publishes only when
72
- the formatted chronometer changes and is cleared on shutdown. No message content,
73
- thinking, reasoning, prompts, tool arguments, or child-agent output enters the widget.
18
+ The deck renders four stable rows above the editor:
19
+
20
+ 1. **Where and what** — identity and companion chips lead, then the project path
21
+ and Git segment; the right corner carries the engine group (model › thinking ›
22
+ quota) and any alert.
23
+ 2. **Focus** — the session subject (auto-titled from the first real prompt,
24
+ settable through the `title` tool) with the OpenSpec work tile on the right.
25
+ 3. **Pure activity band (optional)** — exists only while an observed activity is
26
+ live — `editing`, `reading`, `grepping`, `bash`, or `subagent` — with the turn
27
+ chronometer and token count riding on the right; subagent and bash child runs
28
+ keep their `└` span rows directly beneath. Between tools and at rest the band
29
+ retires entirely: Pi's own `Working…` indicator stays visible exactly as the
30
+ core renders it, and no phase words or `awaiting Human` filler are ever
31
+ generated.
32
+ 4. **Machine and status** — quota, CPU/RAM, context, compactions, and session
33
+ cost yield right-to-left into the width the status end leaves: notices when
34
+ present, otherwise the dim ladybug sentinel wearing the short session id.
35
+
36
+ Rows appear only when they speak — a silent band and spanless state collapse
37
+ their seam. Every slot is bounded, sanitized display vocabulary; no message
38
+ content, thinking, reasoning, prompts, tool arguments, or child-agent output
39
+ enters the widget. Terminal display-cell measurement and final truncation remain
40
+ Unicode- and Nerd-Font-aware.
74
41
 
75
42
  The additive header protocol projects selection independently from live work:
76
43
 
77
44
  ```ts
78
45
  selection: {
79
- source: 'openspec' | 'session-work';
46
+ source: 'openspec' | 'subject' | 'legacy';
80
47
  titles: string[]; // one or two bounded, normalized titles
81
48
  color: HeaderColor;
82
49
  } | null;
@@ -3,32 +3,16 @@ import type {
3
3
  ExtensionContext,
4
4
  Theme,
5
5
  } from '@earendil-works/pi-coding-agent';
6
- import { truncateToWidth } from '@earendil-works/pi-tui';
7
6
 
8
7
  import {
9
- buildAgentFooterWidgets,
10
- buildPromptStatusSnapshot,
11
- COMPACTION_WIDGET_ID,
12
- CONTEXT_WIDGET_ID,
13
8
  countCompactions,
14
- ENVIRONMENT_WIDGET_ID,
15
- FANCY_FOOTER_READY_CHANNEL,
16
9
  FOOTER_TELEMETRY_CHANNEL,
17
- FANCY_FOOTER_WIDGET_CHANNEL,
18
10
  formatFooterBranch,
19
11
  formatFooterPath,
20
12
  GALACTICA_HEADER_CHANNEL,
21
- GIT_BRANCH_WIDGET_ID,
22
- GIT_CONFLICT_WIDGET_ID,
23
- GIT_SCOPE_SEPARATOR_WIDGET_ID,
24
- GIT_MODIFIED_WIDGET_ID,
25
- GIT_STAGED_WIDGET_ID,
26
- GIT_UNTRACKED_WIDGET_ID,
27
13
  type FooterTelemetrySnapshot,
28
14
  type GitStatusSummary,
29
15
  type HeaderSnapshot,
30
- LEGACY_GIT_WIDGET_ID,
31
- LOCATION_WIDGET_ID,
32
16
  normalizeContextPercent,
33
17
  parseFooterTelemetry,
34
18
  parseGitBranch,
@@ -37,24 +21,12 @@ import {
37
21
  parsePiStatusSnapshot,
38
22
  PI_NATIVE_STATUS_CHANNEL,
39
23
  PI_STATUS_SNAPSHOT_CHANNEL,
40
- PROMPT_STATUS_CHANNEL,
41
- REASONING_WIDGET_ID,
42
- SCOPE_SEPARATOR_WIDGET_ID,
43
- runtimeHeaderWork,
44
24
  parseVimMode,
45
25
  VIM_MODE_CHANNEL,
46
26
  type PiStatusSnapshot,
47
27
  type VimMode,
48
28
  } from './src/gauge.ts';
49
29
  import {
50
- buildRuntimeFooterWidgets,
51
- formatRailChronometer,
52
- RUNTIME_SCOPE_SEPARATOR_WIDGET_ID,
53
- RUNTIME_STATUS_WIDGET_ID,
54
- } from './src/activity-age.ts';
55
- import {
56
- buildCapabilityFooterWidget,
57
- CAPABILITY_WIDGET_ID,
58
30
  LSP_STATUS_EVENT,
59
31
  MCP_STATUS_EVENT,
60
32
  parseLspCapability,
@@ -64,11 +36,8 @@ import {
64
36
  } from './src/capabilities.ts';
65
37
  import { shouldRefreshGitAfterTool } from './src/git-refresh.ts';
66
38
  import {
67
- buildResourceFooterWidgets,
68
39
  buildResourceTelemetry,
69
40
  calculateCpuPercent,
70
- CPU_WIDGET_ID,
71
- MEMORY_WIDGET_ID,
72
41
  RESOURCE_REFRESH_MS,
73
42
  sampleProcessTreeResources,
74
43
  updateCpuSmoothing,
@@ -77,7 +46,6 @@ import {
77
46
  type ResourceTelemetry,
78
47
  } from './src/process-resources.ts';
79
48
  import { renderHeaderDeck } from './src/deck.ts';
80
- import { layoutLifecycleTitle, renderLifecycleTitleSections } from './src/title.ts';
81
49
 
82
50
  const WIDGET_KEY = 'galactica.work-identity';
83
51
  const PI_STATUS_SOURCE_CHANNEL = 'pi-stardeck:status-source/v1';
@@ -103,7 +71,11 @@ export interface ContextHeaderDeckSurface {
103
71
  }
104
72
 
105
73
  export interface ContextHeaderOptions {
106
- surface?: 'legacy' | 'deck' | 'editor-deck';
74
+ /** The deck surface is the product: `'deck'` registers the above-editor
75
+ * widget, `'editor-deck'` renders into an embedder's component instead
76
+ * (vim border). The pre-deck legacy title line is retired
77
+ * (simplify-deck-defaults). */
78
+ surface?: 'deck' | 'editor-deck';
107
79
  deckSurface?: ContextHeaderDeckSurface;
108
80
  }
109
81
 
@@ -164,7 +136,6 @@ export default function galacticaContextHeader(
164
136
  pi: ExtensionAPI,
165
137
  options: ContextHeaderOptions = {},
166
138
  ): void {
167
- const deckMode = options.surface === 'deck' || options.surface === 'editor-deck';
168
139
  const editorDeckSurface =
169
140
  options.surface === 'editor-deck' ? options.deckSurface : undefined;
170
141
  let clearWidget: (() => void) | undefined;
@@ -176,7 +147,6 @@ export default function galacticaContextHeader(
176
147
  let header: HeaderSnapshot | null = null;
177
148
  let agentBusy = false;
178
149
  let lastObservedActivityAt: number | null = null;
179
- let lastActivityAgeSignature = '';
180
150
  let activityAgeTimer: ReturnType<typeof setInterval> | undefined;
181
151
  let sessionGeneration = 0;
182
152
  let sessionShortId: string | undefined;
@@ -196,64 +166,9 @@ export default function galacticaContextHeader(
196
166
  let piStatus: PiStatusSnapshot | undefined;
197
167
  let nativeStatusBridgeAvailable = true;
198
168
  let vimMode: VimMode = 'insert';
199
- let lastCapabilitySignature = '';
200
169
 
201
170
  const currentHeader = () => header;
202
171
 
203
- const publishPromptStatus = () => {
204
- if (deckMode) return;
205
- pi.events.emit(
206
- PROMPT_STATUS_CHANNEL,
207
- buildPromptStatusSnapshot(currentHeader(), branch, gitStatus),
208
- );
209
- };
210
-
211
- const publishFooter = () => {
212
- if (deckMode) return;
213
- const ctx = activeCtx;
214
- if (!ctx) return;
215
- const usage = ctx.getContextUsage();
216
- const contextPercent = normalizeContextPercent(
217
- usage?.percent,
218
- usage?.tokens,
219
- usage?.contextWindow ?? ctx.model?.contextWindow,
220
- );
221
- const thinking = pi.getThinkingLevel();
222
- for (const message of buildAgentFooterWidgets({
223
- cwd: formatFooterPath(activeCwd || ctx.cwd, process.env.HOME),
224
- devbox: process.env.DEVBOX_PROJECT_ROOT !== undefined,
225
- branch: formatFooterBranch(branch),
226
- git: gitStatus,
227
- gitAvailable,
228
- model: ctx.model?.name || ctx.model?.id || 'no model',
229
- ...(thinking && thinking !== 'off' ? { thinking } : {}),
230
- contextPercent,
231
- compactionCount: countCompactions(ctx.sessionManager.getBranch()),
232
- })) {
233
- pi.events.emit(FANCY_FOOTER_WIDGET_CHANNEL, message);
234
- }
235
- };
236
-
237
- const publishLatestResourceFooter = () => {
238
- if (deckMode || !latestResourceTelemetry) return;
239
- for (const message of buildResourceFooterWidgets(latestResourceTelemetry)) {
240
- pi.events.emit(FANCY_FOOTER_WIDGET_CHANNEL, message);
241
- }
242
- };
243
-
244
- const publishCapabilityFooter = (force = false) => {
245
- if (deckMode) return;
246
- const signature = [mcpCapability?.healthy ?? '', mcpCapability?.total ?? ''].join(
247
- ':',
248
- );
249
- if (!force && signature === lastCapabilitySignature) return;
250
- lastCapabilitySignature = signature;
251
- pi.events.emit(
252
- FANCY_FOOTER_WIDGET_CHANNEL,
253
- buildCapabilityFooterWidget(mcpCapability),
254
- );
255
- };
256
-
257
172
  const refreshResources = async (): Promise<void> => {
258
173
  if (!activeCtx || resourceRefreshInFlight) return;
259
174
  resourceRefreshInFlight = true;
@@ -274,58 +189,28 @@ export default function galacticaContextHeader(
274
189
  latestResourceTelemetry = telemetry;
275
190
  if (signature === lastResourceSignature) return;
276
191
  lastResourceSignature = signature;
277
- if (deckMode) requestRender?.();
278
- else publishLatestResourceFooter();
192
+ requestRender?.();
279
193
  } finally {
280
194
  resourceRefreshInFlight = false;
281
195
  }
282
196
  };
283
197
 
284
- const removeFooterWidgets = () => {
285
- for (const id of [
286
- LOCATION_WIDGET_ID,
287
- LEGACY_GIT_WIDGET_ID,
288
- GIT_BRANCH_WIDGET_ID,
289
- GIT_SCOPE_SEPARATOR_WIDGET_ID,
290
- GIT_STAGED_WIDGET_ID,
291
- GIT_MODIFIED_WIDGET_ID,
292
- GIT_UNTRACKED_WIDGET_ID,
293
- GIT_CONFLICT_WIDGET_ID,
294
- ENVIRONMENT_WIDGET_ID,
295
- REASONING_WIDGET_ID,
296
- CONTEXT_WIDGET_ID,
297
- COMPACTION_WIDGET_ID,
298
- CPU_WIDGET_ID,
299
- MEMORY_WIDGET_ID,
300
- RUNTIME_STATUS_WIDGET_ID,
301
- RUNTIME_SCOPE_SEPARATOR_WIDGET_ID,
302
- SCOPE_SEPARATOR_WIDGET_ID,
303
- CAPABILITY_WIDGET_ID,
304
- ]) {
305
- pi.events.emit(FANCY_FOOTER_WIDGET_CHANNEL, {
306
- protocol: 1,
307
- type: 'remove',
308
- id,
309
- });
310
- }
311
- };
312
-
313
198
  const currentActivityAge = (): number | null =>
314
199
  agentBusy && lastObservedActivityAt !== null
315
200
  ? Math.max(0, Date.now() - lastObservedActivityAt)
316
201
  : null;
317
202
 
318
- const publishRuntimeFooter = () => {
319
- if (deckMode) return;
320
- const activityAge = currentActivityAge();
321
- const status = formatRailChronometer(activityAge);
322
- const active = activityAge !== null;
323
- const signature = `${active ? 'active' : 'idle'}:${status}`;
324
- if (signature === lastActivityAgeSignature) return;
325
- lastActivityAgeSignature = signature;
326
- for (const message of buildRuntimeFooterWidgets(status, active)) {
327
- pi.events.emit(FANCY_FOOTER_WIDGET_CHANNEL, message);
328
- }
203
+ const startActivityTicker = (): void => {
204
+ // Duty-cycled: the 50 ms chronometer ticks only while an activity can
205
+ // render — idle sessions cost zero wakeups (simplify-deck-defaults).
206
+ if (activityAgeTimer) return;
207
+ activityAgeTimer = setInterval(refreshActivityAge, 50);
208
+ };
209
+
210
+ const stopActivityTicker = (): void => {
211
+ if (activityAgeTimer === undefined) return;
212
+ clearInterval(activityAgeTimer);
213
+ activityAgeTimer = undefined;
329
214
  };
330
215
 
331
216
  const publishCapabilityHealth = () => {
@@ -363,12 +248,6 @@ export default function galacticaContextHeader(
363
248
  });
364
249
  };
365
250
 
366
- const stopReady = pi.events.on(FANCY_FOOTER_READY_CHANNEL, () => {
367
- publishFooter();
368
- publishLatestResourceFooter();
369
- publishRuntimeFooter();
370
- publishCapabilityFooter(true);
371
- });
372
251
  const stopLspStatus = pi.events.on(LSP_STATUS_EVENT, (raw) => {
373
252
  lspCapability = parseLspCapability(raw);
374
253
  requestRender?.();
@@ -377,7 +256,6 @@ export default function galacticaContextHeader(
377
256
  const stopMcpStatus = pi.events.on(MCP_STATUS_EVENT, (raw) => {
378
257
  mcpCapability = parseMcpCapability(raw);
379
258
  requestRender?.();
380
- publishCapabilityFooter();
381
259
  publishCapabilityHealth();
382
260
  });
383
261
  const stopFooterTelemetry = pi.events.on(FOOTER_TELEMETRY_CHANNEL, (raw) => {
@@ -401,33 +279,28 @@ export default function galacticaContextHeader(
401
279
  const stopHeader = pi.events.on(GALACTICA_HEADER_CHANNEL, (raw) => {
402
280
  header = parseHeaderSnapshot(raw);
403
281
  requestRender?.();
404
- publishPromptStatus();
405
- publishRuntimeFooter();
406
282
  });
407
283
 
408
284
  const markObservedActivity = () => {
409
285
  lastObservedActivityAt = Date.now();
410
- lastActivityAgeSignature = '';
286
+ startActivityTicker();
411
287
  requestRender?.();
412
- publishRuntimeFooter();
413
288
  };
414
289
 
415
290
  const clearObservedActivity = () => {
416
291
  lastObservedActivityAt = null;
417
- lastActivityAgeSignature = '';
292
+ stopActivityTicker();
418
293
  };
419
294
 
420
295
  const refreshActivityAge = () => {
421
296
  if (currentActivityAge() === null) return;
422
- if (deckMode) requestRender?.();
423
- else publishRuntimeFooter();
297
+ requestRender?.();
424
298
  };
425
299
 
426
300
  pi.on('before_agent_start', (_event, ctx) => {
427
301
  if (ctx.mode !== 'tui') return;
428
302
  agentBusy = true;
429
303
  markObservedActivity();
430
- publishPromptStatus();
431
304
  });
432
305
 
433
306
  pi.on('turn_start', markObservedActivity);
@@ -480,11 +353,7 @@ export default function galacticaContextHeader(
480
353
  gitStatus = gitAvailable
481
354
  ? parseGitStatus(String(statusResult?.stdout ?? ''))
482
355
  : { ...EMPTY_GIT };
483
- if (deckMode) requestRender?.();
484
- else {
485
- publishPromptStatus();
486
- publishFooter();
487
- }
356
+ requestRender?.();
488
357
  } while (gitRefreshQueued && activeCwd);
489
358
  } finally {
490
359
  gitRefreshInFlight = false;
@@ -495,9 +364,8 @@ export default function galacticaContextHeader(
495
364
  clearWidget?.();
496
365
  clearFooter?.();
497
366
  clearFooter = undefined;
498
- if (activityAgeTimer) clearInterval(activityAgeTimer);
367
+ stopActivityTicker();
499
368
  if (resourceTimer) clearInterval(resourceTimer);
500
- activityAgeTimer = undefined;
501
369
  resourceTimer = undefined;
502
370
  ++sessionGeneration;
503
371
  activeCtx = ctx;
@@ -519,34 +387,23 @@ export default function galacticaContextHeader(
519
387
  cpuSmoothing = undefined;
520
388
  latestResourceTelemetry = undefined;
521
389
  lastResourceSignature = '';
522
- lastCapabilitySignature = '';
523
390
  clearObservedActivity();
524
- publishFooter();
525
- publishPromptStatus();
526
- publishRuntimeFooter();
527
- publishCapabilityFooter();
528
391
  publishCapabilityHealth();
529
- if (ctx.mode === 'tui') {
530
- ctx.ui.setWorkingVisible(false);
531
- activityAgeTimer = setInterval(refreshActivityAge, 50);
532
- }
533
392
  if (!ctx.hasUI) return;
534
- if (deckMode) {
535
- clearFooter = installEmptyDeckFooter(
536
- ctx,
537
- (statuses) => {
538
- pi.events.emit(PI_NATIVE_STATUS_CHANNEL, {
539
- protocol: 1,
540
- type: 'snapshot',
541
- statuses,
542
- });
543
- },
544
- (available) => {
545
- nativeStatusBridgeAvailable = available;
546
- publishCapabilityHealth();
547
- },
548
- );
549
- }
393
+ clearFooter = installEmptyDeckFooter(
394
+ ctx,
395
+ (statuses) => {
396
+ pi.events.emit(PI_NATIVE_STATUS_CHANNEL, {
397
+ protocol: 1,
398
+ type: 'snapshot',
399
+ statuses,
400
+ });
401
+ },
402
+ (available) => {
403
+ nativeStatusBridgeAvailable = available;
404
+ publishCapabilityHealth();
405
+ },
406
+ );
550
407
  void refreshResources();
551
408
  resourceTimer = setInterval(() => void refreshResources(), RESOURCE_REFRESH_MS);
552
409
  activeCwd = ctx.cwd;
@@ -610,16 +467,7 @@ export default function galacticaContextHeader(
610
467
  requestRender = () => tui.requestRender();
611
468
  return {
612
469
  render(width: number): string[] {
613
- if (deckMode) return renderDeck(width, theme);
614
- const { work, idle } = runtimeHeaderWork(currentHeader()?.work, agentBusy);
615
- const layout = layoutLifecycleTitle(work, width, agentBusy);
616
- const line = renderLifecycleTitleSections(
617
- layout,
618
- work.lifecycle,
619
- idle,
620
- theme,
621
- );
622
- return [truncateToWidth(line, width, '…')];
470
+ return renderDeck(width, theme);
623
471
  },
624
472
  invalidate() {},
625
473
  };
@@ -634,21 +482,17 @@ export default function galacticaContextHeader(
634
482
  });
635
483
 
636
484
  pi.on('model_select', () => {
637
- publishFooter();
638
485
  requestRender?.();
639
486
  });
640
487
 
641
488
  pi.on('thinking_level_select', () => {
642
- if (deckMode) requestRender?.();
643
- else publishFooter();
489
+ requestRender?.();
644
490
  });
645
491
  pi.on('message_end', (event) => {
646
492
  if (event.message.role === 'assistant') markObservedActivity();
647
- publishFooter();
648
493
  });
649
494
  pi.on('session_compact', () => {
650
- if (deckMode) requestRender?.();
651
- else publishFooter();
495
+ requestRender?.();
652
496
  });
653
497
 
654
498
  pi.on('tool_execution_end', (event) => {
@@ -662,9 +506,6 @@ export default function galacticaContextHeader(
662
506
  agentBusy = false;
663
507
  clearObservedActivity();
664
508
  requestRender?.();
665
- publishPromptStatus();
666
- publishFooter();
667
- publishRuntimeFooter();
668
509
  void refreshGit();
669
510
  });
670
511
 
@@ -675,9 +516,7 @@ export default function galacticaContextHeader(
675
516
  gitAvailable = false;
676
517
  agentBusy = false;
677
518
  clearObservedActivity();
678
- if (activityAgeTimer) clearInterval(activityAgeTimer);
679
519
  if (resourceTimer) clearInterval(resourceTimer);
680
- activityAgeTimer = undefined;
681
520
  resourceTimer = undefined;
682
521
  resourceRefreshInFlight = false;
683
522
  previousResourceSample = undefined;
@@ -689,9 +528,7 @@ export default function galacticaContextHeader(
689
528
  clearWidget = undefined;
690
529
  clearFooter?.();
691
530
  clearFooter = undefined;
692
- removeFooterWidgets();
693
531
  stopHeader();
694
- stopReady();
695
532
  stopLspStatus();
696
533
  stopMcpStatus();
697
534
  stopPiStatus();