omegon 0.7.4 → 0.7.5

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.
@@ -43,10 +43,19 @@ export interface SplashItem {
43
43
  state: "hidden" | "pending" | "active" | "done" | "failed";
44
44
  }
45
45
 
46
+ export interface QueuedNotification {
47
+ message: string;
48
+ type: "info" | "warning" | "error";
49
+ }
50
+
46
51
  export interface SplashState {
47
52
  items: SplashItem[];
48
53
  /** Set to true when all session_start hooks have returned */
49
54
  loadingComplete: boolean;
55
+ /** True while splash is displayed — notifications should be queued */
56
+ active: boolean;
57
+ /** Queued notifications to flush after splash dismissal */
58
+ notificationQueue: QueuedNotification[];
50
59
  }
51
60
 
52
61
  function getSharedState(): SplashState {
@@ -61,6 +70,8 @@ function getSharedState(): SplashState {
61
70
  { label: "tools", state: "pending" },
62
71
  ],
63
72
  loadingComplete: false,
73
+ active: true,
74
+ notificationQueue: [],
64
75
  };
65
76
  (globalThis as any)[SPLASH_KEY] = state;
66
77
  }
@@ -111,6 +122,7 @@ class SplashHeader implements Component {
111
122
  private onTransition: (() => void) | null = null;
112
123
  private cachedLines: string[] | undefined;
113
124
  private cachedWidth: number | undefined;
125
+ private promptBlink = false;
114
126
 
115
127
  private markRows: number;
116
128
  private logoWidth: number;
@@ -128,6 +140,21 @@ class SplashHeader implements Component {
128
140
  this.timer = setInterval(() => this.tick(), FRAME_INTERVAL_MS);
129
141
  }
130
142
 
143
+ /** Called externally (e.g. on keypress) to dismiss the splash. */
144
+ dismiss(): void {
145
+ if (this.transitioned) return;
146
+ this.transitioned = true;
147
+ this.dispose();
148
+ this.onTransition?.();
149
+ this.onTransition = null;
150
+ }
151
+
152
+ /** True when the animation is done and loading is complete — ready for dismissal. */
153
+ get readyToDismiss(): boolean {
154
+ const state = getSharedState();
155
+ return this.animDone && this.holdCount >= HOLD_FRAMES && state.loadingComplete;
156
+ }
157
+
131
158
  private tick(): void {
132
159
  this.frame++;
133
160
  this.scanFrame = (this.scanFrame + 1) % SCAN_FRAMES.length;
@@ -139,13 +166,9 @@ class SplashHeader implements Component {
139
166
 
140
167
  if (this.animDone && !this.transitioned) {
141
168
  this.holdCount++;
142
- const state = getSharedState();
143
- if (this.holdCount >= HOLD_FRAMES && state.loadingComplete) {
144
- this.transitioned = true;
145
- this.dispose();
146
- this.onTransition?.();
147
- this.onTransition = null; // prevent double-fire
148
- return;
169
+ // Blink the "press any key" prompt every ~500ms (10 frames at 50ms)
170
+ if (this.readyToDismiss && this.holdCount % 10 === 0) {
171
+ this.promptBlink = !this.promptBlink;
149
172
  }
150
173
  }
151
174
 
@@ -178,6 +201,15 @@ class SplashHeader implements Component {
178
201
  lines.push(""); // spacer
179
202
  const checklistLines = this.renderChecklist(width);
180
203
  lines.push(...checklistLines);
204
+
205
+ // "Press any key" prompt when ready
206
+ if (this.readyToDismiss) {
207
+ lines.push(""); // spacer
208
+ const prompt = "press any key to continue";
209
+ const promptPad = Math.max(0, Math.floor((width - prompt.length) / 2));
210
+ const promptColor = this.promptBlink ? DIM : PRIMARY;
211
+ lines.push(" ".repeat(promptPad) + `${promptColor}${prompt}${RESET}`);
212
+ }
181
213
  }
182
214
 
183
215
  lines.push(""); // bottom spacer
@@ -460,8 +492,48 @@ export default function splashExtension(pi: ExtensionAPI): void {
460
492
  const canFitCompact = termWidth >= COMPACT_LINE_WIDTH + 4 && termRows >= COMPACT_LOGO_LINES.length + 6;
461
493
  const canFitWordmark = termWidth >= LINE_WIDTH + 4 && termRows >= WORDMARK_LINES.length + 6;
462
494
 
495
+ // -----------------------------------------------------------------------
496
+ // Notification queueing — intercept ctx.ui.notify while splash is active
497
+ // -----------------------------------------------------------------------
498
+ const state = getSharedState();
499
+ const originalNotify = ctx.ui.notify.bind(ctx.ui);
500
+ ctx.ui.notify = (message: string, type?: "info" | "warning" | "error") => {
501
+ if (state.active) {
502
+ state.notificationQueue.push({ message, type: type ?? "info" });
503
+ } else {
504
+ originalNotify(message, type);
505
+ }
506
+ };
507
+
508
+ /** Flush queued notifications and restore original notify. */
509
+ const flushAndRestore = () => {
510
+ state.active = false;
511
+ ctx.ui.notify = originalNotify;
512
+ for (const n of state.notificationQueue) {
513
+ originalNotify(n.message, n.type);
514
+ }
515
+ state.notificationQueue.length = 0;
516
+ };
517
+
518
+ // -----------------------------------------------------------------------
519
+ // Splash header setup
520
+ // -----------------------------------------------------------------------
521
+ let activeSplash: SplashHeader | null = null;
522
+
523
+ const dismissSplash = () => {
524
+ if (!activeSplash) return;
525
+ activeSplash.dismiss();
526
+ activeSplash = null;
527
+ if (unsubInput) { unsubInput(); unsubInput = null; }
528
+ flushAndRestore();
529
+ };
530
+
531
+ let unsubInput: (() => void) | null = null;
532
+
463
533
  if (!canFitCompact && !canFitWordmark) {
464
- // Too small for any animation — minimal branded header
534
+ // Too small for any animation — minimal branded header, no splash gate
535
+ state.active = false;
536
+ ctx.ui.notify = originalNotify;
465
537
  ctx.ui.setHeader(() => new BrandedHeader(version));
466
538
  } else {
467
539
  let artLines: string[];
@@ -484,10 +556,21 @@ export default function splashExtension(pi: ExtensionAPI): void {
484
556
  const splash = new SplashHeader(tui, () => {
485
557
  // Transition to minimal branded header
486
558
  ctx.ui.setHeader((_, _t) => new BrandedHeader(version));
559
+ flushAndRestore();
487
560
  }, artLines, markRows, logoWidth);
488
561
  splash.start();
562
+ activeSplash = splash;
489
563
  return splash;
490
564
  });
565
+
566
+ // Listen for any keypress to dismiss splash once ready
567
+ unsubInput = ctx.ui.onTerminalInput((data) => {
568
+ if (activeSplash?.readyToDismiss && data) {
569
+ dismissSplash();
570
+ return { consume: true };
571
+ }
572
+ return undefined;
573
+ });
491
574
  }
492
575
 
493
576
  // Poll shared state to detect when all subsystems have reported.
@@ -503,17 +586,19 @@ export default function splashExtension(pi: ExtensionAPI): void {
503
586
  }
504
587
  }, FRAME_INTERVAL_MS);
505
588
 
506
- // Safety timeout — don't hold splash forever if an extension never reports.
507
- // 3s is generous; most startups complete in <2s.
589
+ // Safety timeout — if an extension never reports done, force the checklist
590
+ // to complete so the "press any key" prompt appears. Does NOT auto-dismiss;
591
+ // the user must press a key.
508
592
  const safetyTimer = setTimeout(() => {
509
593
  clearInterval(pollTimer);
510
594
  splashDone();
511
- }, 3000);
595
+ }, 5000);
512
596
 
513
597
  // Clean up on early session exit to prevent timer leaks
514
598
  pi.on("session_shutdown", async () => {
515
599
  clearInterval(pollTimer);
516
600
  clearTimeout(safetyTimer);
601
+ if (activeSplash) dismissSplash();
517
602
  });
518
603
  });
519
604
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omegon",
3
- "version": "0.7.4",
3
+ "version": "0.7.5",
4
4
  "description": "Omegon — an opinionated distribution of pi (by Mario Zechner) with extensions for lifecycle management, memory, orchestration, and visualization",
5
5
  "bin": {
6
6
  "omegon": "bin/omegon.mjs",