@pi-archimedes/core 1.8.2 → 1.9.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": "@pi-archimedes/core",
3
- "version": "1.8.2",
3
+ "version": "1.9.0",
4
4
  "type": "module",
5
5
  "keywords": [
6
6
  "pi-package"
package/src/index.ts CHANGED
@@ -59,6 +59,7 @@ export function getCoreSettingsItems(config: CoreConfig): SettingItem[] {
59
59
  // Module-level state for session lifecycle (shared between session_start and session_shutdown)
60
60
  let coreRef: ListingRef | undefined;
61
61
  let coreCtx: ExtensionContext | undefined;
62
+ let coreTui: TUI | undefined;
62
63
 
63
64
  export function registerCore(pi: ExtensionAPI): void {
64
65
  // Patch console.log for model scope capture
@@ -72,26 +73,56 @@ export function registerCore(pi: ExtensionAPI): void {
72
73
  const listingRef = g["listingRef"] as ListingRef | undefined;
73
74
  if (listingRef) { listingRef.settled = true; }
74
75
 
75
- // Restore patched chat.addChild to prevent closure accumulation across reloads
76
- const ORIG_ADD_CHILD = Symbol.for("splashscreen:origAddChild");
76
+ // Restore patched addChild to prevent closure accumulation across reloads
77
77
  const PATCHED_LISTING = Symbol.for("splashscreen:listingPatched");
78
- if (coreCtx) {
78
+ const ORIG_ADD_CHILD = Symbol.for("splashscreen:origAddChild");
79
+ if (coreTui) {
79
80
  try {
80
- const tui = (coreCtx.ui as any).tui;
81
- if (tui?.children) {
82
- for (const child of tui.children) {
81
+ // Direct TUI children (old Pi: chatContainer was a direct child)
82
+ for (const child of coreTui.children) {
83
+ const cc = child as any;
84
+ if (cc[PATCHED_LISTING] && cc[ORIG_ADD_CHILD]) {
85
+ cc.addChild = cc[ORIG_ADD_CHILD];
86
+ cc[PATCHED_LISTING] = false;
87
+ cc[ORIG_ADD_CHILD] = undefined;
88
+ }
89
+ }
90
+ // Nested children (Pi 0.84+: loadedResourcesContainer is inside documentContainer)
91
+ for (const topChild of coreTui.children) {
92
+ const tc = topChild as any;
93
+ if (!tc.children) continue;
94
+ for (const child of tc.children) {
83
95
  const cc = child as any;
84
96
  if (cc[PATCHED_LISTING] && cc[ORIG_ADD_CHILD]) {
85
- child.addChild = cc[ORIG_ADD_CHILD];
97
+ cc.addChild = cc[ORIG_ADD_CHILD];
86
98
  cc[PATCHED_LISTING] = false;
87
99
  cc[ORIG_ADD_CHILD] = undefined;
88
100
  }
89
101
  }
90
102
  }
91
- } catch {
92
- /* TUI structure may have changed ignore */
103
+
104
+ // Clear animation interval and debounce timer for prompt cleanup
105
+ const ANIM_INTERVAL = Symbol.for("splashscreen:animInterval");
106
+ const DEBOUNCE_TIMER = Symbol.for("splashscreen:debounceTimer");
107
+ for (const child of coreTui.children) {
108
+ const cc = child as any;
109
+ if (cc[ANIM_INTERVAL]) clearInterval(cc[ANIM_INTERVAL]);
110
+ if (cc[DEBOUNCE_TIMER]) clearTimeout(cc[DEBOUNCE_TIMER]);
111
+ }
112
+ // Nested children (Pi 0.84+: loadedResourcesContainer is inside documentContainer)
113
+ for (const topChild of coreTui.children) {
114
+ const tc = topChild as any;
115
+ if (!tc.children) continue;
116
+ for (const child of tc.children) {
117
+ const cc = child as any;
118
+ if (cc[ANIM_INTERVAL]) clearInterval(cc[ANIM_INTERVAL]);
119
+ if (cc[DEBOUNCE_TIMER]) clearTimeout(cc[DEBOUNCE_TIMER]);
120
+ }
93
121
  }
122
+ } catch {
123
+ /* TUI structure may have changed — ignore */
94
124
  }
125
+ }
95
126
 
96
127
  // Clear editor component override
97
128
  if (coreCtx) { coreCtx.ui.setEditorComponent(undefined); }
@@ -116,6 +147,7 @@ export function registerCore(pi: ExtensionAPI): void {
116
147
  };
117
148
  const ref = coreRef;
118
149
  const headerFactory = (tui: TUI, theme: Theme): Component & { dispose?(): void } => {
150
+ coreTui = tui; // Capture for shutdown cleanup
119
151
  const comp: Component & { dispose?(): void } = {
120
152
  invalidate(): void { /* no-op */ },
121
153
  render(width: number): string[] {
@@ -159,22 +159,46 @@ function findChatContainer(tui: TUI): Container | undefined {
159
159
  return undefined;
160
160
  }
161
161
 
162
+ // Pi 0.84+ moved resource sections from chatContainer to loadedResourcesContainer
163
+ // (child of documentContainer). Find it by locating documentContainer (the first TUI
164
+ // child that contains Container sub-children), then taking its second Container child.
165
+ //
166
+ // Pi 0.84+ TUI layout (regular mode, 7 top-level children):
167
+ // documentContainer → [headerContainer(0), loadedResourcesContainer(1), chatContainer(2)]
168
+ function findLoadedResourcesContainer(tui: TUI): Container | undefined {
169
+ // Only run this heuristic on Pi >= 0.84.0 where loadedResourcesContainer exists.
170
+ // On older Pi versions, return undefined so the caller falls back to findChatContainer().
171
+ if (compareVersions(VERSION, "0.84.0") < 0) return undefined;
172
+
173
+ // Find documentContainer: first tui child that has Container sub-children
174
+ const docContainer = tui.children.find(
175
+ (c) => c instanceof Container && c.children.some((child) => child instanceof Container)
176
+ ) as Container | undefined;
177
+ if (!docContainer) return undefined;
178
+
179
+ const containerChildren = docContainer.children.filter(
180
+ (c): c is Container => c instanceof Container
181
+ );
182
+ // Pi 0.84+: [headerContainer(0), loadedResourcesContainer(1), chatContainer(2)]
183
+ // The index [1] assumption is load-bearing — tied to Pi 0.84+ initialization order.
184
+ if (containerChildren.length < 2) return undefined;
185
+ return containerChildren[1]; // loadedResourcesContainer
186
+ }
187
+
162
188
  export function patchStartupListing(
163
189
  tui: TUI,
164
190
  _theme: Theme,
165
191
  ref: ListingRef,
166
192
  ): void {
167
- const chat = findChatContainer(tui);
168
- if (!chat) {
169
- // Graceful degradation: if we can't find the chat container,
170
- // the startup listing won't render but the extension continues.
171
- console.warn("[archimedes] Could not find chat container — startup listing disabled. This may indicate a pi TUI structure change.");
193
+ const resourcesContainer = findLoadedResourcesContainer(tui) ?? findChatContainer(tui);
194
+ if (!resourcesContainer) {
195
+ console.warn("[archimedes] Could not find resources container startup listing disabled. This may indicate a pi TUI structure change.");
172
196
  return;
173
197
  }
174
- const cc = chat as any;
198
+ const rc = resourcesContainer as any;
175
199
 
176
200
  // Always update ref + restart animation (critical for /reload)
177
- cc[LISTING_REF] = ref;
201
+ rc[LISTING_REF] = ref;
178
202
  ref.frame = 0;
179
203
  ref.revealed = false;
180
204
  ref.revealedAt = 0;
@@ -184,12 +208,12 @@ export function patchStartupListing(
184
208
  delete ref.cachedWidth;
185
209
  delete ref.maxHeaderHeight;
186
210
 
187
- if (cc[ANIM_INTERVAL]) clearInterval(cc[ANIM_INTERVAL]);
188
- if (cc[DEBOUNCE_TIMER]) clearTimeout(cc[DEBOUNCE_TIMER]);
211
+ if (rc[ANIM_INTERVAL]) clearInterval(rc[ANIM_INTERVAL]);
212
+ if (rc[DEBOUNCE_TIMER]) clearTimeout(rc[DEBOUNCE_TIMER]);
189
213
 
190
214
  const interval = setInterval(() => {
191
215
  try {
192
- const current: ListingRef = cc[LISTING_REF];
216
+ const current: ListingRef = rc[LISTING_REF];
193
217
  if (!current) {
194
218
  clearInterval(interval);
195
219
  return;
@@ -197,7 +221,7 @@ export function patchStartupListing(
197
221
  current.frame++;
198
222
  if (current.settled && current.frame >= LOGO_SETTLE_FRAME) {
199
223
  clearInterval(interval);
200
- cc[ANIM_INTERVAL] = null;
224
+ rc[ANIM_INTERVAL] = null;
201
225
  return;
202
226
  }
203
227
  tui.requestRender();
@@ -206,12 +230,12 @@ export function patchStartupListing(
206
230
  }
207
231
  }, 16);
208
232
 
209
- cc[ANIM_INTERVAL] = interval;
233
+ rc[ANIM_INTERVAL] = interval;
210
234
 
211
235
  // Fetch latest version from npm
212
236
  fetchLatestVersion().then(v => {
213
237
  if (v) {
214
- const current: ListingRef = cc[LISTING_REF];
238
+ const current: ListingRef = rc[LISTING_REF];
215
239
  current.latestVersion = v;
216
240
  // Invalidate cache so version updates on next render
217
241
  delete current.cachedLines;
@@ -220,28 +244,28 @@ export function patchStartupListing(
220
244
  });
221
245
 
222
246
  // Patch clear() to intercept container rebuild
223
- if (!cc[PATCHED_CLEAR]) {
224
- cc[PATCHED_CLEAR] = true;
225
- const origClear = chat.clear.bind(chat);
226
- chat.clear = () => {
247
+ if (!rc[PATCHED_CLEAR]) {
248
+ rc[PATCHED_CLEAR] = true;
249
+ const origClear = resourcesContainer.clear.bind(resourcesContainer);
250
+ resourcesContainer.clear = () => {
227
251
  return origClear();
228
252
  };
229
253
  }
230
254
 
231
- // Only patch addChild once — the closure reads cc[LISTING_REF] dynamically
232
- if (cc[PATCHED_LISTING]) {
233
- chat.clear();
255
+ // Only patch addChild once — the closure reads rc[LISTING_REF] dynamically
256
+ if (rc[PATCHED_LISTING]) {
257
+ resourcesContainer.clear();
234
258
  return;
235
259
  }
236
- cc[PATCHED_LISTING] = true;
260
+ rc[PATCHED_LISTING] = true;
237
261
 
238
- const origAddChild = chat.addChild.bind(chat);
239
- cc[ORIG_ADD_CHILD] = origAddChild; // Store for cleanup on shutdown
240
- chat.clear();
262
+ const origAddChild = resourcesContainer.addChild.bind(resourcesContainer);
263
+ rc[ORIG_ADD_CHILD] = origAddChild; // Store for cleanup on shutdown
264
+ resourcesContainer.clear();
241
265
 
242
- chat.addChild = (component: Component) => {
266
+ resourcesContainer.addChild = (component: Component) => {
243
267
  try {
244
- const currentRef: ListingRef = cc[LISTING_REF];
268
+ const currentRef: ListingRef = rc[LISTING_REF];
245
269
 
246
270
  if (component instanceof Text) {
247
271
  // pi ≥0.67.6 wraps startup sections in ExpandableText; collapsed body
@@ -270,14 +294,14 @@ export function patchStartupListing(
270
294
  tui.requestRender();
271
295
  } else {
272
296
  // Batch initial sections — reset debounce on each arrival
273
- if (cc[DEBOUNCE_TIMER]) clearTimeout(cc[DEBOUNCE_TIMER]);
274
- cc[DEBOUNCE_TIMER] = setTimeout(() => {
275
- const ref: ListingRef = cc[LISTING_REF];
297
+ if (rc[DEBOUNCE_TIMER]) clearTimeout(rc[DEBOUNCE_TIMER]);
298
+ rc[DEBOUNCE_TIMER] = setTimeout(() => {
299
+ const ref: ListingRef = rc[LISTING_REF];
276
300
  ref.revealed = true;
277
301
  ref.revealedAt = ref.frame;
278
302
  ref.scaffoldAt = ref.frame;
279
303
  tui.requestRender();
280
- cc[DEBOUNCE_TIMER] = null;
304
+ rc[DEBOUNCE_TIMER] = null;
281
305
  }, REVEAL_DEBOUNCE_MS);
282
306
  }
283
307
 
@@ -297,7 +321,7 @@ export function patchStartupListing(
297
321
  try {
298
322
  origAddChild(component);
299
323
  } catch (err) {
300
- console.error("[archimedes] Error in chat.addChild:", err);
324
+ console.error("[archimedes] Error in resourcesContainer.addChild:", err);
301
325
  }
302
326
  } catch (err) {
303
327
  console.error("[archimedes] Error in startup listing addChild handler:", err);