@aemonge-dev/pi-stardeck 0.7.0 → 0.8.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.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "A Vim- and tmux-inspired command deck for Pi",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -421,7 +421,15 @@ function companionIconColor(text: string | undefined, active: boolean): string {
421
421
  : 'accent';
422
422
  }
423
423
 
424
- function extensionStatusCodes(state: HeaderDeckState, theme: Theme): string {
424
+ function identityCluster(
425
+ state: HeaderDeckState,
426
+ theme: Theme,
427
+ ): { text: string; trailingBare: boolean } {
428
+ // Line-1 corner identity: devbox lead chip, then the companion chips.
429
+ // Returns the assembled text plus whether the last chip is bare — bare
430
+ // chips keep the wide gap to their right (0.7.1 amendment: the major
431
+ // separator rides double-spaced after a bare trailing chip, matching
432
+ // the bare→bare chip spacing).
425
433
  const texts = new Map<string, string>();
426
434
  for (const item of state.piStatus?.nativeStatuses ?? []) {
427
435
  if (item.key === 'goal' || texts.has(item.key)) continue;
@@ -452,14 +460,14 @@ function extensionStatusCodes(state: HeaderDeckState, theme: Theme): string {
452
460
  ),
453
461
  });
454
462
  }
455
- let out = '';
456
- let previousBare = false;
463
+ let text = '';
464
+ let trailingBare = false;
457
465
  for (const part of parts) {
458
- out +=
459
- out === '' ? part.text : `${previousBare && part.bare ? ' ' : ' '}${part.text}`;
460
- previousBare = part.bare;
466
+ text +=
467
+ text === '' ? part.text : `${trailingBare && part.bare ? ' ' : ' '}${part.text}`;
468
+ trailingBare = part.bare;
461
469
  }
462
- return out;
470
+ return { text, trailingBare };
463
471
  }
464
472
 
465
473
  const BLOCKED_NOTICE = '\u26d4 blocked';
@@ -521,9 +529,10 @@ function noticeTile(state: HeaderDeckState, theme: Theme): string {
521
529
  }
522
530
 
523
531
  function telemetryRowLeft(state: HeaderDeckState, theme: Theme): string {
524
- // Line 4 left (2.4 second amendment): the engine group — engine ›
525
- // thinking, then context, compaction, and the calendar quota — owns
526
- // the row's left end; the notice slot owns the right end (Task 3).
532
+ // Line 4 left (deck-corner-context amendment): the global group —
533
+ // engine › thinking, then the long-horizon quota — owns the row's left
534
+ // end; the notice slot owns the right end (Task 3). Context and
535
+ // compaction live in the line-1 corner now.
527
536
  return modelGroupLeft(state, theme);
528
537
  }
529
538
 
@@ -532,10 +541,11 @@ export function machineCornerSegments(
532
541
  theme: Theme,
533
542
  nowMs = Date.now(),
534
543
  ): 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.
544
+ // Machine corner (deck-machine-corner; deck-corner-context): icon-led
545
+ // machine readings for the line-1 corner — coding quota flame with its
546
+ // countdown, CPU, memory, MCP availability, context percent, summary
547
+ // (compaction) count, global cost — in that frozen order, joined by
548
+ // minor separators. Segments yield right-to-left on narrow frames.
539
549
  const segments: string[] = [];
540
550
  const flame = windowQuotaTiles(state, theme, nowMs);
541
551
  if (flame !== '') segments.push(flame);
@@ -557,6 +567,23 @@ export function machineCornerSegments(
557
567
  if (state.mcp) {
558
568
  segments.push(color(theme, 'accent', `${state.mcp.healthy}/${state.mcp.total}`));
559
569
  }
570
+ // deck-corner-context: context and summary counts ride the corner.
571
+ // Context wears warning at ≥ 85% (the threshold it carried engine-left);
572
+ // the summary count follows the context color as before.
573
+ const ctxColor =
574
+ state.contextPercent !== undefined && state.contextPercent >= 85
575
+ ? 'warning'
576
+ : state.contextPercent === undefined
577
+ ? 'dim'
578
+ : 'accent';
579
+ if (state.contextPercent !== undefined) {
580
+ segments.push(
581
+ color(theme, ctxColor, `\u{F0F86} ${formatDeckPercent(state.contextPercent)}`),
582
+ );
583
+ }
584
+ if (state.compactionCount > 0) {
585
+ segments.push(color(theme, ctxColor, `\u{F039E} ${state.compactionCount}`));
586
+ }
560
587
  const cost = costTile(state, theme);
561
588
  if (cost !== '') segments.push(cost);
562
589
  return segments;
@@ -569,49 +596,37 @@ function deckCornerRight(
569
596
  nowMs = Date.now(),
570
597
  ): string {
571
598
  // 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);
599
+ // split from the machine group by one major separator. A bare trailing
600
+ // chip keeps the wide gap before the separator (0.7.1 amendment);
601
+ // machine segments yield right-to-left (cost first) before the path
602
+ // truncates below a 20-column floor; the identity cluster never drops
603
+ // (always-visible contract).
604
+ const identity = identityCluster(state, theme);
577
605
  const segments = machineCornerSegments(state, theme, nowMs);
578
606
  const pathFloor = 20;
607
+ const cornerGap = identity.trailingBare ? ' ' : ' ';
579
608
  for (let keep = segments.length; keep >= 0; keep -= 1) {
580
609
  const tail = segments.slice(0, keep).join(` ${minorSeparator(theme)} `);
581
610
  const corner =
582
- tail === '' ? identity : `${identity} ${semanticSeparator(theme)} ${tail}`;
611
+ tail === ''
612
+ ? identity.text
613
+ : `${identity.text}${cornerGap}${semanticSeparator(theme)} ${tail}`;
583
614
  if (keep === 0 || width - visibleWidth(corner) - 1 >= pathFloor) {
584
615
  return corner;
585
616
  }
586
617
  }
587
- return identity;
618
+ return identity.text;
588
619
  }
589
620
 
590
621
  function modelGroupLeft(state: HeaderDeckState, theme: Theme): string {
591
- const ctxColor =
592
- state.contextPercent !== undefined && state.contextPercent >= 85
593
- ? 'warning'
594
- : state.contextPercent === undefined
595
- ? 'dim'
596
- : 'accent';
597
- const segments: string[] = [];
598
- if (state.contextPercent !== undefined) {
599
- segments.push(
600
- color(theme, 'accent', `󰾆 ${formatDeckPercent(state.contextPercent)}`),
601
- );
602
- }
603
- if (state.compactionCount > 0) {
604
- segments.push(color(theme, ctxColor, `󰎞 ${state.compactionCount}`));
605
- }
622
+ // Line 4 left (deck-corner-context): fully global — model › thinking,
623
+ // then the long-horizon quota (weekly calendar, or whole-session
624
+ // percent when no windows exist). Context and compaction moved to the
625
+ // line-1 corner.
606
626
  const quota = simpleQuotaTile(state, theme);
607
- if (quota) segments.push(quota);
608
- return segments.length === 0
609
- ? globalLeft(state, theme)
610
- : [
611
- globalLeft(state, theme),
612
- semanticSeparator(theme),
613
- segments.join(` ${minorSeparator(theme, ctxColor)} `),
614
- ].join(' ');
627
+ return quota
628
+ ? [globalLeft(state, theme), semanticSeparator(theme), quota].join(' ')
629
+ : globalLeft(state, theme);
615
630
  }
616
631
 
617
632
  function simpleQuotaTile(state: HeaderDeckState, theme: Theme): string {
@@ -802,12 +817,15 @@ export function renderHeaderDeck(
802
817
  // row's left end; the right end is notice-only. The machine group moved
803
818
  // to the line-1 corner, so notices can no longer starve machine
804
819
  // readings — the old wholesale yield is gone with the group itself.
820
+ // 0.7.1 amendment: a clean notice slot renders the dim `healthy`
821
+ // placeholder instead of trailing silence — it yields like a notice
822
+ // when the engine group needs the width.
805
823
  const engineGroup = telemetryRowLeft(state, theme);
806
824
  const noticeGroup = noticeTile(state, theme);
825
+ const rightGroup = noticeGroup !== '' ? noticeGroup : color(theme, 'dim', 'healthy');
807
826
  const telemetryRow =
808
- noticeGroup !== '' &&
809
- visibleWidth(engineGroup) + 1 + visibleWidth(noticeGroup) <= boundedWidth
810
- ? alignedSingleRow(engineGroup, noticeGroup, boundedWidth)
827
+ visibleWidth(engineGroup) + 1 + visibleWidth(rightGroup) <= boundedWidth
828
+ ? alignedSingleRow(engineGroup, rightGroup, boundedWidth)
811
829
  : fit(engineGroup, boundedWidth);
812
830
 
813
831
  // Band adjacency (Human-directed, accepted mockups; third 2.4