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.
- package/extensions/00-splash/index.ts +96 -11
- package/package.json +1 -1
|
@@ -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
|
-
|
|
143
|
-
if (this.holdCount
|
|
144
|
-
this.
|
|
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 —
|
|
507
|
-
//
|
|
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
|
-
},
|
|
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.
|
|
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",
|