@pi-unipi/unipi 2.2.7 → 2.3.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/CHANGELOG.md +35 -0
- package/package.json +31 -25
- package/packages/ask-user/package.json +2 -2
- package/packages/autocomplete/package.json +1 -1
- package/packages/btw/package.json +2 -2
- package/packages/cocoindex/package.json +2 -2
- package/packages/compactor/package.json +3 -3
- package/packages/compactor/src/info-screen.ts +4 -4
- package/packages/core/package.json +1 -1
- package/packages/core/utils.ts +37 -0
- package/packages/footer/package.json +2 -2
- package/packages/image/package.json +2 -2
- package/packages/info-screen/README.md +4 -4
- package/packages/info-screen/config.ts +28 -8
- package/packages/info-screen/core-groups.ts +5 -39
- package/packages/info-screen/index.ts +25 -10
- package/packages/info-screen/package.json +2 -2
- package/packages/info-screen/tui/info-overlay.ts +114 -38
- package/packages/info-screen/types.ts +20 -5
- package/packages/info-screen/usage-parser.ts +318 -128
- package/packages/input-shortcuts/package.json +2 -2
- package/packages/kanboard/package.json +2 -2
- package/packages/mcp/package.json +2 -2
- package/packages/memory/index.ts +60 -22
- package/packages/memory/mempalace.ts +66 -1
- package/packages/memory/package.json +3 -3
- package/packages/memory/storage.ts +75 -12
- package/packages/milestone/package.json +2 -2
- package/packages/notify/package.json +2 -2
- package/packages/ralph/package.json +3 -3
- package/packages/subagents/package.json +4 -4
- package/packages/unipi/bundled.js +37694 -0
- package/packages/updater/package.json +2 -2
- package/packages/utility/package.json +2 -2
- package/packages/utility/src/tools/env.ts +1 -22
- package/packages/web-api/package.json +2 -2
- package/packages/workflow/package.json +2 -2
|
@@ -15,6 +15,14 @@ import { getInfoSettings } from "../config.js";
|
|
|
15
15
|
import type { InfoGroup, GroupData } from "../types.js";
|
|
16
16
|
import { boxInnerWidth } from "@pi-unipi/core";
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* How long to wait before warming the non-visible tabs.
|
|
20
|
+
*
|
|
21
|
+
* Long enough that startup and the first paint finish first, short enough that
|
|
22
|
+
* a tab switch a second later is already warm.
|
|
23
|
+
*/
|
|
24
|
+
const PREFETCH_DELAY_MS = 1500;
|
|
25
|
+
|
|
18
26
|
/** Tab color palette */
|
|
19
27
|
const TAB_FG: Array<"accent" | "success" | "warning" | "error"> = [
|
|
20
28
|
"accent",
|
|
@@ -48,6 +56,10 @@ export class InfoOverlay implements Component {
|
|
|
48
56
|
private lastGlobalUpdate = 0;
|
|
49
57
|
private unsubscribers: Array<() => void> = [];
|
|
50
58
|
private _destroyed = false;
|
|
59
|
+
/** Groups whose fetch has already been kicked off (lazy-load bookkeeping). */
|
|
60
|
+
private fetched = new Set<string>();
|
|
61
|
+
private prefetchTimer: ReturnType<typeof setTimeout> | null = null;
|
|
62
|
+
private bootTimer: ReturnType<typeof setTimeout> | null = null;
|
|
51
63
|
|
|
52
64
|
onClose?: () => void;
|
|
53
65
|
requestRender?: () => void;
|
|
@@ -89,31 +101,55 @@ export class InfoOverlay implements Component {
|
|
|
89
101
|
})
|
|
90
102
|
);
|
|
91
103
|
|
|
92
|
-
//
|
|
93
|
-
this.
|
|
104
|
+
// Fetch the visible tab now; everything else waits for idle.
|
|
105
|
+
this.fetchActiveGroup();
|
|
106
|
+
this.schedulePrefetch();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Fetch one group, tracking its loading state. Safe to call repeatedly. */
|
|
110
|
+
private fetchGroup(groupId: string): void {
|
|
111
|
+
if (this._destroyed) return;
|
|
112
|
+
if (this.fetched.has(groupId)) return;
|
|
113
|
+
this.fetched.add(groupId);
|
|
114
|
+
infoRegistry.getGroupData(groupId).then(() => {
|
|
115
|
+
this.groupLoading.set(groupId, false);
|
|
116
|
+
}).catch(() => {
|
|
117
|
+
this.groupLoading.set(groupId, false);
|
|
118
|
+
});
|
|
94
119
|
}
|
|
95
120
|
|
|
96
121
|
/**
|
|
97
|
-
* Fetch
|
|
122
|
+
* Fetch the currently visible group.
|
|
98
123
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* heavy providers (usage stats parse 1GB+ of session files, memory scans)
|
|
103
|
-
* blocked the session_start handler for seconds.
|
|
124
|
+
* Deferred to a macrotask because an async dataProvider still runs
|
|
125
|
+
* synchronously up to its first `await`; calling it inline would put that
|
|
126
|
+
* work back on the constructor's caller (session_start).
|
|
104
127
|
*/
|
|
105
|
-
private
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
128
|
+
private fetchActiveGroup(): void {
|
|
129
|
+
const group = this.groups[this.activeTabIndex];
|
|
130
|
+
if (!group) return;
|
|
131
|
+
setTimeout(() => this.fetchGroup(group.id), 0);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Warm the remaining tabs once the app is idle.
|
|
136
|
+
*
|
|
137
|
+
* Fetching every group up front cost seconds of startup for panels the user
|
|
138
|
+
* may never open. Prefetching after a delay keeps tab switches instant
|
|
139
|
+
* without paying for them before the first prompt is ready.
|
|
140
|
+
*/
|
|
141
|
+
private schedulePrefetch(): void {
|
|
142
|
+
if (this.prefetchTimer) return;
|
|
143
|
+
this.prefetchTimer = setTimeout(() => {
|
|
144
|
+
this.prefetchTimer = null;
|
|
145
|
+
if (this._destroyed) return;
|
|
146
|
+
for (const group of this.groups) {
|
|
147
|
+
if (group.id === this.groups[this.activeTabIndex]?.id) continue;
|
|
148
|
+
this.fetchGroup(group.id);
|
|
149
|
+
}
|
|
150
|
+
}, PREFETCH_DELAY_MS);
|
|
151
|
+
// Never hold the process open just to warm a panel.
|
|
152
|
+
this.prefetchTimer.unref?.();
|
|
117
153
|
}
|
|
118
154
|
|
|
119
155
|
/**
|
|
@@ -127,29 +163,30 @@ export class InfoOverlay implements Component {
|
|
|
127
163
|
this.applyOrder();
|
|
128
164
|
}
|
|
129
165
|
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
166
|
+
// Adopt any data the registry already has. Registration notifications
|
|
167
|
+
// inject `{}` to trigger a re-sync; that is not real data and must not be
|
|
168
|
+
// treated as fetched, or the stats render as "—".
|
|
169
|
+
//
|
|
170
|
+
// Groups are NOT fetched here: doing so would defeat lazy loading, since
|
|
171
|
+
// syncGroups() runs on every render. Fetches are driven by tab visibility
|
|
172
|
+
// (fetchActiveGroup) and the idle prefetch instead.
|
|
133
173
|
for (const group of this.groups) {
|
|
134
174
|
const existing = this.groupData.get(group.id);
|
|
135
175
|
const hasRealData = existing && Object.keys(existing).length > 0;
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
this.groupLoading.set(group.id, true);
|
|
142
|
-
infoRegistry.getGroupData(group.id).then((data) => {
|
|
143
|
-
this.groupData.set(group.id, data);
|
|
144
|
-
this.groupLoading.set(group.id, false);
|
|
145
|
-
this.lastGlobalUpdate = Date.now();
|
|
146
|
-
this.requestRender?.();
|
|
147
|
-
}).catch(() => {
|
|
148
|
-
this.groupLoading.set(group.id, false);
|
|
149
|
-
});
|
|
150
|
-
}
|
|
176
|
+
if (hasRealData) continue;
|
|
177
|
+
|
|
178
|
+
const cached = infoRegistry.getCachedData(group.id);
|
|
179
|
+
if (cached && Object.keys(cached).length > 0) {
|
|
180
|
+
this.groupData.set(group.id, cached);
|
|
151
181
|
}
|
|
152
182
|
}
|
|
183
|
+
|
|
184
|
+
// A late-arriving group may now be the visible one, and the prefetch pass
|
|
185
|
+
// may have already run — make sure the active tab still gets its data.
|
|
186
|
+
if (hadNewGroups) {
|
|
187
|
+
this.fetchActiveGroup();
|
|
188
|
+
this.schedulePrefetch();
|
|
189
|
+
}
|
|
153
190
|
}
|
|
154
191
|
|
|
155
192
|
private applyOrder(): void {
|
|
@@ -169,23 +206,59 @@ export class InfoOverlay implements Component {
|
|
|
169
206
|
*/
|
|
170
207
|
destroy(): void {
|
|
171
208
|
this._destroyed = true;
|
|
209
|
+
this.cancelBootTimer();
|
|
210
|
+
if (this.prefetchTimer) {
|
|
211
|
+
clearTimeout(this.prefetchTimer);
|
|
212
|
+
this.prefetchTimer = null;
|
|
213
|
+
}
|
|
172
214
|
for (const unsub of this.unsubscribers) {
|
|
173
215
|
unsub();
|
|
174
216
|
}
|
|
175
217
|
this.unsubscribers = [];
|
|
176
218
|
}
|
|
177
219
|
|
|
220
|
+
/** Stop the boot auto-close timer, if one is pending. */
|
|
221
|
+
private cancelBootTimer(): void {
|
|
222
|
+
if (this.bootTimer) {
|
|
223
|
+
clearTimeout(this.bootTimer);
|
|
224
|
+
this.bootTimer = null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Auto-close the overlay after `ms`, unless the user interacts first.
|
|
230
|
+
*
|
|
231
|
+
* Used when the overlay is shown on boot: the dashboard is informational, so
|
|
232
|
+
* it should get out of the way on its own rather than requiring a keypress.
|
|
233
|
+
*/
|
|
234
|
+
startBootTimer(ms: number): void {
|
|
235
|
+
this.cancelBootTimer();
|
|
236
|
+
if (!Number.isFinite(ms) || ms <= 0) return;
|
|
237
|
+
this.bootTimer = setTimeout(() => {
|
|
238
|
+
this.bootTimer = null;
|
|
239
|
+
if (this._destroyed) return;
|
|
240
|
+
this.destroy();
|
|
241
|
+
this.onClose?.();
|
|
242
|
+
}, ms);
|
|
243
|
+
this.bootTimer.unref?.();
|
|
244
|
+
}
|
|
245
|
+
|
|
178
246
|
invalidate(): void {
|
|
179
247
|
this.syncGroups();
|
|
180
248
|
}
|
|
181
249
|
|
|
182
250
|
handleInput(data: string): void {
|
|
251
|
+
// Any keypress means the user is driving; stop the boot auto-close.
|
|
252
|
+
this.cancelBootTimer();
|
|
253
|
+
|
|
183
254
|
if (data === "\x1b[C" || data === "l") {
|
|
184
255
|
this.activeTabIndex = (this.activeTabIndex + 1) % this.groups.length;
|
|
185
256
|
this.scrollOffset = 0;
|
|
257
|
+
this.fetchActiveGroup();
|
|
186
258
|
} else if (data === "\x1b[D" || data === "h") {
|
|
187
259
|
this.activeTabIndex = (this.activeTabIndex - 1 + this.groups.length) % this.groups.length;
|
|
188
260
|
this.scrollOffset = 0;
|
|
261
|
+
this.fetchActiveGroup();
|
|
189
262
|
} else if (data === "\x1b[B" || data === "j") {
|
|
190
263
|
this.scrollOffset++;
|
|
191
264
|
} else if (data === "\x1b[A" || data === "k") {
|
|
@@ -211,12 +284,15 @@ export class InfoOverlay implements Component {
|
|
|
211
284
|
if (!group) return;
|
|
212
285
|
this.groupLoading.set(group.id, true);
|
|
213
286
|
this.requestRender?.();
|
|
287
|
+
// Explicit refresh must bypass the lazy-load guard.
|
|
288
|
+
this.fetched.add(group.id);
|
|
214
289
|
infoRegistry.refreshGroup(group.id);
|
|
215
290
|
}
|
|
216
291
|
|
|
217
292
|
private refreshAll(): void {
|
|
218
293
|
for (const group of this.groups) {
|
|
219
294
|
this.groupLoading.set(group.id, true);
|
|
295
|
+
this.fetched.add(group.id);
|
|
220
296
|
}
|
|
221
297
|
this.requestRender?.();
|
|
222
298
|
infoRegistry.refreshAll();
|
|
@@ -47,11 +47,26 @@ export interface InfoGroup {
|
|
|
47
47
|
dataProvider: () => Promise<GroupData>;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/** How the dashboard behaves at startup. */
|
|
51
|
+
export type BootMode = "on" | "off" | "auto-close";
|
|
52
|
+
|
|
53
|
+
/** All valid boot modes, in the order the settings UI cycles them. */
|
|
54
|
+
export const BOOT_MODES: BootMode[] = ["on", "auto-close", "off"];
|
|
55
|
+
|
|
50
56
|
/** Settings for info-screen in settings.json */
|
|
51
57
|
export interface InfoScreenSettings {
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
/**
|
|
59
|
+
* What the dashboard does at startup:
|
|
60
|
+
* - "on": show it and leave it up until dismissed (q/Esc)
|
|
61
|
+
* - "off": do not show it at all (no data is fetched)
|
|
62
|
+
* - "auto-close": show it, then close after `bootTimeoutMs`
|
|
63
|
+
*/
|
|
64
|
+
bootMode: BootMode;
|
|
65
|
+
/**
|
|
66
|
+
* How long the boot dashboard stays up in "auto-close" mode, in ms.
|
|
67
|
+
* Any keypress cancels the timer and keeps the overlay open.
|
|
68
|
+
* Does not apply to the overlay opened via /unipi:info.
|
|
69
|
+
*/
|
|
55
70
|
bootTimeoutMs: number;
|
|
56
71
|
/** Per-group settings */
|
|
57
72
|
groups: Record<string, GroupSettings>;
|
|
@@ -69,8 +84,8 @@ export interface GroupSettings {
|
|
|
69
84
|
|
|
70
85
|
/** Default settings */
|
|
71
86
|
export const DEFAULT_SETTINGS: InfoScreenSettings = {
|
|
72
|
-
|
|
73
|
-
bootTimeoutMs:
|
|
87
|
+
bootMode: "auto-close",
|
|
88
|
+
bootTimeoutMs: 2000,
|
|
74
89
|
groups: {},
|
|
75
90
|
groupOrder: [],
|
|
76
91
|
};
|