@aemonge-dev/pi-stardeck 0.5.0 → 0.7.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.
Files changed (30) hide show
  1. package/README.md +3 -2
  2. package/docs/architecture.md +8 -5
  3. package/licenses/README.md +9 -7
  4. package/package.json +2 -2
  5. package/packages/footer/README.md +15 -0
  6. package/packages/footer/index.ts +37 -0
  7. package/packages/footer/package.json +32 -0
  8. package/packages/footer/src/protocol.ts +10 -0
  9. package/packages/footer/src/quota.ts +26 -0
  10. package/packages/footer/src/telemetry.ts +59 -0
  11. package/packages/pi-stardeck/README.md +7 -6
  12. package/packages/pi-stardeck/src/deck.ts +79 -44
  13. package/packages/status/package.json +1 -1
  14. package/src/health.ts +0 -1
  15. package/src/index.ts +3 -4
  16. package/licenses/pi-fancy-footer-LICENSE +0 -21
  17. package/packages/pi-fancy-footer-full-palette/LICENSE +0 -21
  18. package/packages/pi-fancy-footer-full-palette/LOCAL-CHANGES.md +0 -41
  19. package/packages/pi-fancy-footer-full-palette/README.md +0 -468
  20. package/packages/pi-fancy-footer-full-palette/package.json +0 -52
  21. package/packages/pi-fancy-footer-full-palette/src/api.ts +0 -204
  22. package/packages/pi-fancy-footer-full-palette/src/config-editor.ts +0 -634
  23. package/packages/pi-fancy-footer-full-palette/src/config.ts +0 -852
  24. package/packages/pi-fancy-footer-full-palette/src/data-widgets.ts +0 -259
  25. package/packages/pi-fancy-footer-full-palette/src/git.ts +0 -130
  26. package/packages/pi-fancy-footer-full-palette/src/index.ts +0 -479
  27. package/packages/pi-fancy-footer-full-palette/src/layout.ts +0 -309
  28. package/packages/pi-fancy-footer-full-palette/src/provider-status.ts +0 -1466
  29. package/packages/pi-fancy-footer-full-palette/src/render.ts +0 -1061
  30. package/packages/pi-fancy-footer-full-palette/src/shared.ts +0 -903
package/README.md CHANGED
@@ -21,12 +21,13 @@ Header Deck ◀── bounded lifecycle, work, project, model, and resource cont
21
21
  zero-row Vim command surface ── Insert transition ──▶ Neovim prompt.md
22
22
  ◀── :x + auto-submit ───┘
23
23
 
24
- responsive Footer ◀── model, quota, context, cost, Git, and capability telemetry
24
+ deck telemetry row ◀── model, context, and cost from session usage; quota via an empty seam
25
25
  ```
26
26
 
27
27
  The package composes four bounded modules in dependency-safe order:
28
28
 
29
- - **Fancy Footer** — responsive model, quota, context, cost, Git, and resource telemetry;
29
+ - **Footer telemetry** — session usage cost for the deck's telemetry row, with an
30
+ empty quota seam;
30
31
  - **Status provider** — lifecycle, OpenSpec, Session Work, and diagnostic state;
31
32
  - **Header Deck** — current activity, focus, project state, and compact telemetry.
32
33
  Companion extensions surface as leading telemetry icons:
@@ -6,7 +6,7 @@ The baseline is one Pi package identity with four existing extension factories:
6
6
 
7
7
  ```text
8
8
  root package manifest
9
- ├── Fancy Footer telemetry provider
9
+ ├── Footer telemetry provider (`packages/footer`)
10
10
  ├── status and focus producer
11
11
  ├── Header Deck and telemetry producer
12
12
  └── focused zero-row Vim command surface and external-editor consumer
@@ -18,7 +18,7 @@ through the event protocols that already isolate them.
18
18
 
19
19
  Startup order is intentional:
20
20
 
21
- 1. Fancy Footer subscribes to widget snapshots.
21
+ 1. Footer telemetry publishes session-cost snapshots for the deck row.
22
22
  2. Status registers focus tools and publishes lifecycle/work snapshots.
23
23
  3. Context Header consumes status and supplies the Header Deck renderer.
24
24
  4. Pi Vim installs the focused editor component, renders that deck with its live mode,
@@ -29,10 +29,13 @@ when present; it is not a dependency of this package.
29
29
 
30
30
  ## Stable protocol seams
31
31
 
32
- The imported modules currently coordinate through:
32
+ The composed modules currently coordinate through:
33
33
 
34
- - `pi-fancy-footer:widget` — complete protocol-1 widget snapshots;
35
- - `pi-fancy-footer:ready` — consumer readiness and producer republish;
34
+ - `pi-stardeck:footer-telemetry/v1` — session-cost snapshots from
35
+ `packages/footer` (plus an optional quota seam);
36
+ - `pi-fancy-footer:widget` — complete protocol-1 widget snapshots (live wire
37
+ between the deck and the Vim prompt rail; the name predates the footer
38
+ fork's removal and is slated for renaming);
36
39
  - `galactica-status:header` — bounded lifecycle, selection, suggestion, and active-run
37
40
  snapshots;
38
41
  - `galactica-status:prompt-row` — prompt-rail telemetry;
@@ -3,15 +3,17 @@
3
3
  `pi-stardeck` is private and currently marked `UNLICENSED` as a whole. Imported
4
4
  third-party-derived components retain their own licenses:
5
5
 
6
- - `pi-vim-LICENSE` covers the imported `pi-vim-top-border` descendant;
7
- - `pi-fancy-footer-LICENSE` covers the imported Fancy Footer fork.
6
+ - `pi-vim-LICENSE` covers the imported `pi-vim-top-border` descendant.
8
7
 
9
8
  The same license text remains beside each imported source. Local modifications are
10
9
  recorded in:
11
10
 
12
- - `packages/pi-vim-top-border/LOCAL-CHANGES.md`;
13
- - `packages/pi-fancy-footer-full-palette/LOCAL-CHANGES.md`.
11
+ - `packages/pi-vim-top-border/LOCAL-CHANGES.md`.
14
12
 
15
- Do not remove or replace these notices when consolidating or packing the source. A
16
- future public release requires an explicit project-level licensing decision in addition
17
- to retaining upstream attribution.
13
+ The former Fancy Footer fork was replaced by the clean-room
14
+ `packages/footer` telemetry provider (see
15
+ `openspec/changes/replace-fancy-footer-with-stardeck-footer`); its license and
16
+ local-change records were removed with it. Do not remove or replace the
17
+ remaining notices when consolidating or packing the source. A future public
18
+ release requires an explicit project-level licensing decision in addition to
19
+ retaining upstream attribution.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aemonge-dev/pi-stardeck",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "A Vim- and tmux-inspired command deck for Pi",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -49,7 +49,7 @@
49
49
  "test:tooling": "node --import tsx/esm --test test/*.test.ts",
50
50
  "test:status": "npm --prefix packages/status test",
51
51
  "test:stardeck": "npm --prefix packages/pi-stardeck test",
52
- "test:footer": "npm --prefix packages/pi-fancy-footer-full-palette test",
52
+ "test:footer": "npm --prefix packages/footer test",
53
53
  "test:vim": "npm --prefix packages/pi-vim-top-border test",
54
54
  "test:components": "npm run test:status && npm run test:stardeck && npm run test:footer && npm run test:vim"
55
55
  },
@@ -0,0 +1,15 @@
1
+ # footer
2
+
3
+ Footer telemetry producer for the [pi-stardeck](../../README.md) deck.
4
+
5
+ The deck renders the telemetry row; this module only feeds it. It walks the
6
+ session branch for assistant usage cost and publishes snapshots on
7
+ `pi-stardeck:footer-telemetry/v1` after the events that can change them
8
+ (session start, message end, model selection, compaction).
9
+
10
+ It renders nothing, reads no files, sets no timers, and talks to no network.
11
+ Quota gauges are a seam (`src/quota.ts`) with no implementations: register a
12
+ provider there and the deck row gains its gauge back without renderer
13
+ changes.
14
+
15
+ Composed by the root package; do not install separately.
@@ -0,0 +1,37 @@
1
+ import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';
2
+
3
+ import { activeQuotaProviders } from './src/quota.ts';
4
+ import { FOOTER_TELEMETRY_CHANNEL } from './src/protocol.ts';
5
+ import { buildFooterTelemetryMessage, collectTotalCost } from './src/telemetry.ts';
6
+
7
+ /**
8
+ * Footer telemetry producer for the pi-stardeck deck. Renders nothing,
9
+ * configures nothing, polls nothing: it walks the session branch for usage
10
+ * cost and publishes deck-consumable snapshots on the footer-telemetry
11
+ * channel after the events that can change them.
12
+ */
13
+ export default function footerTelemetry(pi: ExtensionAPI): void {
14
+ let context: ExtensionContext | undefined;
15
+
16
+ const publish = () => {
17
+ if (!context) return;
18
+ const quota = activeQuotaProviders()
19
+ .map((provider) => provider.snapshot())
20
+ .find((snapshot) => snapshot !== undefined);
21
+ pi.events.emit(
22
+ FOOTER_TELEMETRY_CHANNEL,
23
+ buildFooterTelemetryMessage(collectTotalCost(context), quota),
24
+ );
25
+ };
26
+
27
+ pi.on('session_start', (_event, ctx) => {
28
+ context = ctx;
29
+ publish();
30
+ });
31
+ pi.on('message_end', publish);
32
+ pi.on('model_select', publish);
33
+ pi.on('session_compact', publish);
34
+ pi.on('session_shutdown', () => {
35
+ context = undefined;
36
+ });
37
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "footer",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "description": "Footer telemetry producer for the pi-stardeck deck",
6
+ "type": "module",
7
+ "keywords": [
8
+ "pi-package",
9
+ "pi-extension",
10
+ "telemetry"
11
+ ],
12
+ "engines": {
13
+ "node": ">=24.0.0"
14
+ },
15
+ "scripts": {
16
+ "format": "prettier --write \"**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}\" --ignore-path .prettierignore",
17
+ "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}\" --ignore-path .prettierignore",
18
+ "typecheck": "tsc --noEmit",
19
+ "test": "node --test --experimental-strip-types test/*.test.ts",
20
+ "check": "npm run format:check && npm run typecheck && npm test"
21
+ },
22
+ "pi": {
23
+ "extensions": [
24
+ "./index.ts"
25
+ ]
26
+ },
27
+ "peerDependencies": {
28
+ "@earendil-works/pi-ai": "*",
29
+ "@earendil-works/pi-coding-agent": "*",
30
+ "typebox": "*"
31
+ }
32
+ }
@@ -0,0 +1,10 @@
1
+ export const FOOTER_TELEMETRY_PROTOCOL_VERSION = 1 as const;
2
+
3
+ export const FOOTER_TELEMETRY_CHANNEL = 'pi-stardeck:footer-telemetry/v1';
4
+
5
+ export interface FooterTelemetryMessage {
6
+ protocol: typeof FOOTER_TELEMETRY_PROTOCOL_VERSION;
7
+ type: 'snapshot';
8
+ totalCost: number;
9
+ quotaPercent?: number;
10
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Quota seam. The provider ships with no implementations: every quota source
3
+ * this cockpit had (Codex OAuth, Claude OAuth, z.ai coding plan) is gone with
4
+ * the previous footer module. A future slice registers a provider here
5
+ * and the deck row gains its gauge back without any renderer change.
6
+ */
7
+ export interface QuotaSnapshot {
8
+ /** Whole-session quota usage, 0–100. */
9
+ percent: number;
10
+ }
11
+
12
+ export interface QuotaProvider {
13
+ readonly id: string;
14
+ snapshot(): QuotaSnapshot | undefined;
15
+ }
16
+
17
+ const quotaProviders: QuotaProvider[] = [];
18
+
19
+ export function registerQuotaProvider(provider: QuotaProvider): void {
20
+ if (quotaProviders.some(({ id }) => id === provider.id)) return;
21
+ quotaProviders.push(provider);
22
+ }
23
+
24
+ export function activeQuotaProviders(): readonly QuotaProvider[] {
25
+ return quotaProviders;
26
+ }
@@ -0,0 +1,59 @@
1
+ import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
2
+
3
+ import type { QuotaSnapshot } from './quota.ts';
4
+ import {
5
+ FOOTER_TELEMETRY_PROTOCOL_VERSION,
6
+ type FooterTelemetryMessage,
7
+ } from './protocol.ts';
8
+
9
+ function toCost(value: unknown): number {
10
+ return typeof value === 'number' && Number.isFinite(value) ? Math.max(0, value) : 0;
11
+ }
12
+
13
+ /**
14
+ * Sums assistant usage cost over a session branch. Non-message entries,
15
+ * non-assistant messages, and malformed usage contribute nothing, matching
16
+ * the walk the deck row has consumed since 0.1.0.
17
+ */
18
+ export function totalCostFromEntries(entries: readonly unknown[]): number {
19
+ let totalCost = 0;
20
+ for (const entry of entries) {
21
+ if (typeof entry !== 'object' || entry === null) continue;
22
+ const { type, message } = entry as { type?: unknown; message?: unknown };
23
+ if (type !== 'message') continue;
24
+ if (typeof message !== 'object' || message === null) continue;
25
+ const { role, usage } = message as { role?: unknown; usage?: unknown };
26
+ if (role !== 'assistant') continue;
27
+ if (typeof usage !== 'object' || usage === null) continue;
28
+ const cost = (usage as { cost?: unknown }).cost;
29
+ if (typeof cost !== 'object' || cost === null) continue;
30
+ totalCost += toCost((cost as { total?: unknown }).total);
31
+ }
32
+ return totalCost;
33
+ }
34
+
35
+ export function collectTotalCost(ctx: ExtensionContext): number {
36
+ return totalCostFromEntries(ctx.sessionManager.getBranch());
37
+ }
38
+
39
+ export function buildFooterTelemetryMessage(
40
+ totalCost: number,
41
+ quota?: QuotaSnapshot,
42
+ ): FooterTelemetryMessage {
43
+ const cost =
44
+ typeof totalCost === 'number' && Number.isFinite(totalCost)
45
+ ? Math.max(0, totalCost)
46
+ : 0;
47
+ const percent =
48
+ quota !== undefined &&
49
+ typeof quota.percent === 'number' &&
50
+ Number.isFinite(quota.percent)
51
+ ? Math.min(100, Math.max(0, quota.percent))
52
+ : undefined;
53
+ return {
54
+ protocol: FOOTER_TELEMETRY_PROTOCOL_VERSION,
55
+ type: 'snapshot',
56
+ totalCost: cost,
57
+ ...(percent !== undefined ? { quotaPercent: percent } : {}),
58
+ };
59
+ }
@@ -169,8 +169,8 @@ sustained CPU pressure above 80% or unusually high session memory; one resource
169
169
  does not recolor its neighbors. Pink and red are never used in these three telemetry
170
170
  groups. In the title, lifecycle and bounded activity are blue; the major boundary is
171
171
  purple; real focus and optional Task hierarchy are green; empty focus fallbacks and the
172
- footer runtime age/state are dim. Persistent cache-hit presentation is disabled, while
173
- Fancy Footer retains its internal cache accounting. Compaction count is hidden at zero.
172
+ footer runtime age/state are dim. Persistent cache-hit presentation is disabled and no
173
+ module keeps cache accounting since the footer fork's replacement. Compaction count is hidden at zero.
174
174
  The first deck row always begins with the compact runtime capsule
175
175
  `( children ›  subagents · 00:00'00)`. Each zero count and the settled timer remain
176
176
  dim in place; a live count or timer changes color without moving neighboring content.
@@ -182,12 +182,12 @@ Quota is reduced to the most constrained provider-reported percentage with no we
182
182
  analysis. Cost is Pi's native estimated session cost in USD, rendered without trailing
183
183
  fractional zeros; its preceding `⟩` retains the shared purple major-boundary color while
184
184
  the value remains blue. No local daily aggregation or currency conversion is performed.
185
- No center group or gauges are used. The local Fancy Footer fork expands its widget
186
- protocol to accept these native theme tokens. The narrative lifecycle header begins with
185
+ No center group or gauges are used. The deck's widget protocol (historically the
186
+ Fancy Footer protocol, `pi-fancy-footer:widget`) accepts these native theme tokens. The narrative lifecycle header begins with
187
187
  its active/passive terminal icon; the prompt border keeps only its right-aligned vi-mode
188
188
  glyph. Concurrent Git refreshes coalesce without polling.
189
189
 
190
- Agent-environment, context, CPU, RAM, and compaction widgets use the same Fancy Footer
190
+ Agent-environment, context, CPU, RAM, and compaction widgets use the same widget
191
191
  protocol, including producer-requested bold text for the PWD. Every three seconds on
192
192
  Linux, the extension discovers only Pi descendants through bounded
193
193
  `/proc/<pid>/task/<pid>/children` reads, then reads their `stat` and `status` records
@@ -196,7 +196,8 @@ private anonymous resident pages (`RssAnon`) instead of counting shared file-bac
196
196
  pages once per process. Individual status-read failures fall back to that process's RSS;
197
197
  unavailable `/proc` falls back honestly to current-process CPU and RSS. Sampling,
198
198
  smoothing, publication, timers, and resource widgets are session-scoped and cleaned up
199
- on shutdown. The local Fancy Footer fork owns cache, quota, and session-cost collection.
199
+ on shutdown. Session-cost collection lives in `packages/footer`; cache and quota
200
+ collection were removed with the fork, leaving quota as an empty seam.
200
201
  When the footer reports quota windows, the compact telemetry row paints icon-native
201
202
  tiles in cost, long-window, coding-window order — `󰜦 $5.21 ⟩ 󰃰 12% ⟩ 󰈸 62% › 󰅐 1:45`.
202
203
  The flame is the coding window's permanent icon and always carries the clock
@@ -386,21 +386,25 @@ function globalLeft(state: HeaderDeckState, theme: Theme): string {
386
386
  }
387
387
 
388
388
  function projectDeckLeft(state: HeaderDeckState, theme: Theme, width: number): string {
389
- // Line 1 left: the devbox marker is a path prefix, then the path; the git
390
- // segment yields below 79 columns before the path does. The companion
391
- // chips ride line 1's right end and never drop (always-visible contract).
392
- const devbox = state.devbox ? `${color(theme, 'success', '[󰆧]', true)} ` : '';
393
- const path = `${devbox}${color(theme, 'success', ` ${safeText(state.cwd)}`)}`;
389
+ // Line 1 left: the path; the git segment yields below 79 columns before
390
+ // the path does. The devbox marker moved to the line-1 corner with the
391
+ // companion chips (deck-machine-corner); the identity cluster never
392
+ // drops, so the path never carries it again.
393
+ const path = color(theme, 'success', ` \u{F07B} ${safeText(state.cwd)}`);
394
394
  const gitSegment =
395
395
  width >= 79 ? truncateToWidth(gitRight(state, theme), 52, '…') : '';
396
396
  return gitSegment ? `${path} ${semanticSeparator(theme)} ${gitSegment}` : path;
397
397
  }
398
398
 
399
- const KNOWN_COMPANION_ICONS: ReadonlyArray<{
399
+ type CompanionSpec = {
400
400
  keys: readonly string[];
401
401
  icons: readonly string[];
402
- }> = [
403
- { keys: ['engram'], icons: ['\u{21BB}', '\u{25A4}'] },
402
+ /** Render the chip bracketed even at one icon (engram identity). */
403
+ bracket?: boolean;
404
+ };
405
+
406
+ const KNOWN_COMPANION_ICONS: ReadonlyArray<CompanionSpec> = [
407
+ { keys: ['engram'], icons: ['\u{21BB}'], bracket: true },
404
408
  { keys: ['graphify'], icons: ['\u{232C}'] },
405
409
  { keys: ['appa', 'openappa'], icons: ['\u{26E8}'] },
406
410
  ];
@@ -424,15 +428,22 @@ function extensionStatusCodes(state: HeaderDeckState, theme: Theme): string {
424
428
  texts.set(item.key, item.text);
425
429
  }
426
430
  const parts: Array<{ bare: boolean; text: string }> = [];
431
+ if (state.devbox) {
432
+ // Corner lead chip (deck-machine-corner): the devbox marker joins
433
+ // the identity cluster ahead of the companions, success-colored as
434
+ // it was as a path prefix.
435
+ parts.push({ bare: false, text: color(theme, 'success', '[\u{F01A7}]', true) });
436
+ }
427
437
  for (const companion of KNOWN_COMPANION_ICONS) {
428
438
  const published = companion.keys
429
439
  .map((key) => texts.get(key))
430
440
  .find((text) => text !== undefined);
431
441
  for (const key of companion.keys) texts.delete(key);
432
442
  const joined = companion.icons.join(' ');
433
- const chip = companion.icons.length > 1 ? `[${joined}]` : joined;
443
+ const bracketed = companion.bracket === true || companion.icons.length > 1;
444
+ const chip = bracketed ? `[${joined}]` : joined;
434
445
  parts.push({
435
- bare: companion.icons.length === 1,
446
+ bare: !bracketed,
436
447
  text: color(
437
448
  theme,
438
449
  companionIconColor(published, published !== undefined),
@@ -516,37 +527,64 @@ function telemetryRowLeft(state: HeaderDeckState, theme: Theme): string {
516
527
  return modelGroupLeft(state, theme);
517
528
  }
518
529
 
519
- function telemetryRowRight(
530
+ export function machineCornerSegments(
520
531
  state: HeaderDeckState,
521
532
  theme: Theme,
522
533
  nowMs = Date.now(),
523
- ): string {
524
- // Line 4 right (2.4 second amendment): the machine group — the coding
525
- // quota flame with its countdown, machine readings, global cost — in
526
- // that order, anchoring the row's right end.
527
- const machineSegments: string[] = [];
534
+ ): string[] {
535
+ // Machine corner (deck-machine-corner): icon-led machine readings for
536
+ // the line-1 corner — coding quota flame with its countdown, CPU,
537
+ // memory, MCP availability, global cost — in that frozen order, joined
538
+ // by minor separators. Segments yield right-to-left on narrow frames.
539
+ const segments: string[] = [];
540
+ const flame = windowQuotaTiles(state, theme, nowMs);
541
+ if (flame !== '') segments.push(flame);
528
542
  const resources = state.resources;
529
543
  if (resources) {
530
544
  const resourceColor =
531
545
  resources.cpuWarning || resources.memoryWarning ? 'warning' : 'accent';
532
- const parts = [
533
- color(theme, resourceColor, `${formatDeckCpu(resources.cpuPercent)}`),
534
- color(theme, resourceColor, `${formatDeckBytes(resources.memoryBytes)}`),
535
- ];
536
- machineSegments.push(parts.join(` ${minorSeparator(theme, resourceColor)} `));
546
+ segments.push(
547
+ color(theme, resourceColor, `\u{F035B} ${formatDeckCpu(resources.cpuPercent)}`),
548
+ );
549
+ segments.push(
550
+ color(
551
+ theme,
552
+ resourceColor,
553
+ `\u{F061A} ${formatDeckBytes(resources.memoryBytes)}`,
554
+ ),
555
+ );
537
556
  }
538
557
  if (state.mcp) {
539
- machineSegments.push(
540
- color(theme, 'accent', `${state.mcp.healthy}/${state.mcp.total}`),
541
- );
558
+ segments.push(color(theme, 'accent', `${state.mcp.healthy}/${state.mcp.total}`));
559
+ }
560
+ const cost = costTile(state, theme);
561
+ if (cost !== '') segments.push(cost);
562
+ return segments;
563
+ }
564
+
565
+ function deckCornerRight(
566
+ state: HeaderDeckState,
567
+ theme: Theme,
568
+ width: number,
569
+ nowMs = Date.now(),
570
+ ): string {
571
+ // Line 1 corner: the identity cluster (devbox lead, companion chips)
572
+ // split from the machine group by one major separator. Machine segments
573
+ // yield right-to-left (cost first) before the path truncates below a
574
+ // 20-column floor; the identity cluster never drops (always-visible
575
+ // contract).
576
+ const identity = extensionStatusCodes(state, theme);
577
+ const segments = machineCornerSegments(state, theme, nowMs);
578
+ const pathFloor = 20;
579
+ for (let keep = segments.length; keep >= 0; keep -= 1) {
580
+ const tail = segments.slice(0, keep).join(` ${minorSeparator(theme)} `);
581
+ const corner =
582
+ tail === '' ? identity : `${identity} ${semanticSeparator(theme)} ${tail}`;
583
+ if (keep === 0 || width - visibleWidth(corner) - 1 >= pathFloor) {
584
+ return corner;
585
+ }
542
586
  }
543
- return [
544
- windowQuotaTiles(state, theme, nowMs),
545
- machineSegments.join(` ${semanticSeparator(theme)} `),
546
- costTile(state, theme),
547
- ]
548
- .filter((group) => group !== '')
549
- .join(` ${semanticSeparator(theme)} `);
587
+ return identity;
550
588
  }
551
589
 
552
590
  function modelGroupLeft(state: HeaderDeckState, theme: Theme): string {
@@ -760,19 +798,16 @@ export function renderHeaderDeck(
760
798
  turnCostTile(state, theme),
761
799
  boundedWidth,
762
800
  );
763
- // Line 4 (2.4 second amendment + Task 3 placement default): the engine
764
- // group holds the row's left end. Its right end belongs to the machine
765
- // group — until notices land: a non-empty notice slot owns the right end
766
- // and the machine group yields wholesale, riding the same ladder as the
767
- // narrow-frame yield (the right group renders only when it fits beside
768
- // the engine group at full width; the engine group never yields to it).
801
+ // Line 4 (deck-machine-corner amendment): the engine group holds the
802
+ // row's left end; the right end is notice-only. The machine group moved
803
+ // to the line-1 corner, so notices can no longer starve machine
804
+ // readings — the old wholesale yield is gone with the group itself.
769
805
  const engineGroup = telemetryRowLeft(state, theme);
770
806
  const noticeGroup = noticeTile(state, theme);
771
- const rightGroup = noticeGroup !== '' ? noticeGroup : telemetryRowRight(state, theme);
772
807
  const telemetryRow =
773
- rightGroup !== '' &&
774
- visibleWidth(engineGroup) + 1 + visibleWidth(rightGroup) <= boundedWidth
775
- ? alignedSingleRow(engineGroup, rightGroup, boundedWidth)
808
+ noticeGroup !== '' &&
809
+ visibleWidth(engineGroup) + 1 + visibleWidth(noticeGroup) <= boundedWidth
810
+ ? alignedSingleRow(engineGroup, noticeGroup, boundedWidth)
776
811
  : fit(engineGroup, boundedWidth);
777
812
 
778
813
  // Band adjacency (Human-directed, accepted mockups; third 2.4
@@ -781,17 +816,17 @@ export function renderHeaderDeck(
781
816
  // line 4; pi's own prompt chrome below closes the visual block — while
782
817
  // the internal seams stay dotted ┈ between lines 2 and 3 and between
783
818
  // line 3 (spans rows included) and line 4.
784
- const chips = extensionStatusCodes(state, theme);
819
+ const corner = deckCornerRight(state, theme, boundedWidth);
785
820
  const alert = statusAlertTile(
786
821
  state,
787
- Math.max(0, boundedWidth - visibleWidth(chips) - 2),
822
+ Math.max(0, boundedWidth - visibleWidth(corner) - 2),
788
823
  theme,
789
824
  lineColor,
790
825
  );
791
826
  const rows = [
792
827
  alignedSingleRow(
793
828
  projectDeckLeft(state, theme, boundedWidth),
794
- alert ? `${alert} ${chips}` : chips,
829
+ alert ? `${alert} ${corner}` : corner,
795
830
  boundedWidth,
796
831
  ),
797
832
  solidRule,
@@ -2,7 +2,7 @@
2
2
  "name": "status",
3
3
  "version": "1.0.0",
4
4
  "private": true,
5
- "description": "Project status producer for pi-fancy-footer",
5
+ "description": "Project status producer for the pi-stardeck deck",
6
6
  "type": "module",
7
7
  "keywords": [
8
8
  "pi-package",
package/src/health.ts CHANGED
@@ -59,7 +59,6 @@ const PACKAGE_VERSION = (
59
59
  ).version;
60
60
 
61
61
  const PACKAGE_COMMANDS = [
62
- 'fancy-footer',
63
62
  'galactica-status',
64
63
  'galactica-status-debug',
65
64
  'galactica-status-refresh',
package/src/index.ts CHANGED
@@ -2,24 +2,23 @@ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
2
 
3
3
  import galacticaContextHeader from '../packages/pi-stardeck/index.js';
4
4
  import galacticaStatus from '../packages/status/index.js';
5
- import fancyFooter from '../packages/pi-fancy-footer-full-palette/src/index.js';
5
+ import footerTelemetry from '../packages/footer/index.js';
6
6
  import { registerStardeckHealth } from './health.js';
7
7
  import { registerPiStatus } from './pi-status.js';
8
8
 
9
9
  export const COCKPIT_SURFACES = {
10
- footer: 'telemetry',
11
10
  contextHeader: 'deck',
12
11
  } as const;
13
12
 
14
13
  export const COCKPIT_COMPOSITION = [
15
- `fancy-footer:${COCKPIT_SURFACES.footer}`,
14
+ 'footer:telemetry',
16
15
  'galactica-status:provider',
17
16
  `galactica-context-header:${COCKPIT_SURFACES.contextHeader}`,
18
17
  ] as const;
19
18
 
20
19
  export default function piStardeck(pi: ExtensionAPI): void {
21
20
  registerPiStatus(pi);
22
- fancyFooter(pi, { surface: COCKPIT_SURFACES.footer });
21
+ footerTelemetry(pi);
23
22
  galacticaStatus(pi);
24
23
  galacticaContextHeader(pi, {
25
24
  surface: COCKPIT_SURFACES.contextHeader,
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Matthias Vallentin
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Matthias Vallentin
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,41 +0,0 @@
1
- # Local changes
2
-
3
- This is a Galactica-managed local fork of `pi-fancy-footer` 3.0.1.
4
-
5
- ## Full Pi theme palette
6
-
7
- The extension-widget and configuration color schema accepts Pi's complete foreground
8
- theme-token palette instead of limiting footer widgets to seven generic colors. This
9
- lets Galactica use the configured cyan, purple, orange, green, red, and teal palette
10
- without embedding ANSI escapes or falling back to gray text.
11
-
12
- ## Extension-widget emphasis
13
-
14
- Protocol-1 extension widgets accept `style.bold: true`. The renderer applies
15
- theme-native bold after effective text-color resolution, allowing Galactica's PWD widget
16
- to stand out without embedding unsafe ANSI escapes or bolding its icon.
17
-
18
- ## Compact metric presentation
19
-
20
- The `percent` provider-status display mode joins the most constrained known quota to the
21
- LLM group as `›  4%`, with no window analysis. Its five-state battery depletes from ``
22
- through `` and changes semantic color as remaining quota falls. Cache accounting
23
- remains available even when its persistent widget is disabled. Session cost uses a
24
- leading major `⟩`, no treasure-chest glyph, and compact trailing-zero-free formatting of
25
- Pi's native USD estimate (`$0`, `$0.1`, `$1`, or `$1.23`). Cache and cost remain neutral
26
- local telemetry instead of inheriting unrelated context-pressure warnings; context, CPU,
27
- and RAM own their warning colors independently. Galactica now keeps exactly one footer
28
- row: project PWD and Git state on the left, with model, reasoning, and quota on the
29
- right. On wide terminals, the footer ends with the four-cell tail `⟨ 󰠭` after model
30
- quota: a purple left-pointing boundary and the static violet ladybug as Ramona's compact
31
- avatar, with no trailing cell after her. The bounded tail keeps the avatar distinct
32
- without producing a large empty region. Work progress, cost, MCP availability, runtime,
33
- resources, and context are still published as bounded widget snapshots but are hidden
34
- from the footer and consumed by the prompt rails. The renderer omits trailing rows that
35
- contain no visible widgets, allowing the configured footer to occupy one terminal row
36
- without changing the upstream two-row default. Non-growing widgets continue to honor
37
- `minWidth` where configured.
38
-
39
- The package is loaded from `./packages/pi-fancy-footer-full-palette` in `settings.json`.
40
- Do not replace it with the npm package without first preserving the expanded
41
- `FOOTER_WIDGET_COLORS` contract. Run the package tests and `/reload` after updates.