impel-cli 0.20.46 → 0.20.48
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/RELEASE_NOTES.md +33 -0
- package/package.json +1 -1
- package/src/desktopTasks.js +53 -20
- package/src/managedProfileVersion.js +3 -3
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Release notes
|
|
2
2
|
|
|
3
|
+
## 0.20.48 — Confine Tasks to the primary app window
|
|
4
|
+
|
|
5
|
+
- Fixes the remaining `0.20.47` regression that let an auxiliary ChatGPT or
|
|
6
|
+
Claude renderer attach a second authenticated Tasks board in a large framed
|
|
7
|
+
window, obscuring the normal app.
|
|
8
|
+
- Binds Tasks once to the canonical primary vendor window, rejects child,
|
|
9
|
+
internal, auxiliary, and unowned renderers fail-closed, and removes focused-
|
|
10
|
+
window guessing from navigation ownership.
|
|
11
|
+
- Couples fallback installation to the canonical renderer and deduplicates
|
|
12
|
+
paired Electron navigation events so one user action creates at most one
|
|
13
|
+
board view.
|
|
14
|
+
- Advances the managed profile contract to v39 so every existing install
|
|
15
|
+
rewrites and reloads the corrected external Tasks preload. Vendor pins and
|
|
16
|
+
built bundle bytes remain unchanged.
|
|
17
|
+
- Adds a behavioral reproduction of the production screenshot: an auxiliary
|
|
18
|
+
renderer fires both navigation events before the fallback is clicked, while
|
|
19
|
+
the test proves exactly one board attaches to the primary window and the
|
|
20
|
+
fallback remains a frameless 120×38 control.
|
|
21
|
+
|
|
22
|
+
## 0.20.47 — Keep Tasks on the primary app window
|
|
23
|
+
|
|
24
|
+
- Makes the native Tasks fallback choose the earliest surviving primary host
|
|
25
|
+
window deterministically, so a later ChatGPT auxiliary renderer cannot win a
|
|
26
|
+
probe race and cover the real app with an empty grey window.
|
|
27
|
+
- Adds a behavioral regression that returns the auxiliary window first, lets it
|
|
28
|
+
finish navigation probing first, and proves the fallback and authenticated
|
|
29
|
+
board still attach only to the primary window.
|
|
30
|
+
- Skips the prerelease soak because this is an immediate stable regression
|
|
31
|
+
hotfix for `0.20.46`. The change updates the external Tasks preload
|
|
32
|
+
orchestration without changing vendor pins, generated profile contracts, or
|
|
33
|
+
built bundle bytes; the full release CI, pinned managed-app launch smoke, and
|
|
34
|
+
Windows vendor contracts remain mandatory before tagging.
|
|
35
|
+
|
|
3
36
|
## 0.20.46-beta.2 — Reload reconciled apps safely
|
|
4
37
|
|
|
5
38
|
- Closes every running managed macOS app before rewriting its profile and
|
package/package.json
CHANGED
package/src/desktopTasks.js
CHANGED
|
@@ -364,14 +364,20 @@ if (
|
|
|
364
364
|
return;
|
|
365
365
|
}
|
|
366
366
|
const windows = new Map();
|
|
367
|
+
const internalWindows = new WeakSet();
|
|
367
368
|
const internalContents = new Set();
|
|
368
369
|
const nativeNavOwners = new Map();
|
|
369
370
|
const configuredSessions = new WeakSet();
|
|
370
371
|
let creatingInternalWindow = false;
|
|
371
372
|
let hasDomNavigation = false;
|
|
373
|
+
let primaryHost = null;
|
|
372
374
|
const createInternalWindow = (options) => {
|
|
373
375
|
creatingInternalWindow = true;
|
|
374
|
-
try {
|
|
376
|
+
try {
|
|
377
|
+
const window = new BrowserWindow(options);
|
|
378
|
+
internalWindows.add(window);
|
|
379
|
+
return window;
|
|
380
|
+
}
|
|
375
381
|
finally { creatingInternalWindow = false; }
|
|
376
382
|
};
|
|
377
383
|
const stateFor = (window) => {
|
|
@@ -467,7 +473,23 @@ if (
|
|
|
467
473
|
}
|
|
468
474
|
}
|
|
469
475
|
};
|
|
476
|
+
const isHostWindow = (window) => {
|
|
477
|
+
if (!window || window.isDestroyed() || internalWindows.has(window) || window.getParentWindow?.()) return false;
|
|
478
|
+
try {
|
|
479
|
+
const url = new URL(window.webContents.getURL());
|
|
480
|
+
if (url.searchParams.has("initialRoute")) return false;
|
|
481
|
+
} catch {}
|
|
482
|
+
return true;
|
|
483
|
+
};
|
|
484
|
+
const primaryHostWindow = () => {
|
|
485
|
+
if (isHostWindow(primaryHost)) return primaryHost;
|
|
486
|
+
primaryHost = BrowserWindow.getAllWindows()
|
|
487
|
+
.filter(isHostWindow)
|
|
488
|
+
.sort((left, right) => left.id - right.id)[0] || null;
|
|
489
|
+
return primaryHost;
|
|
490
|
+
};
|
|
470
491
|
const hide = (window) => {
|
|
492
|
+
if (window !== primaryHostWindow()) return;
|
|
471
493
|
const state = windows.get(window.id);
|
|
472
494
|
if (!state) return;
|
|
473
495
|
state.visible = false;
|
|
@@ -476,14 +498,14 @@ if (
|
|
|
476
498
|
deactivateNavigation();
|
|
477
499
|
};
|
|
478
500
|
const isPrimaryWindow = (window) => {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
return window.isVisible?.() !== false;
|
|
501
|
+
return isHostWindow(window) && window === primaryHostWindow() && window.isVisible?.() !== false;
|
|
502
|
+
};
|
|
503
|
+
const fallbackHostWindow = () => {
|
|
504
|
+
const window = primaryHostWindow();
|
|
505
|
+
return isPrimaryWindow(window) ? window : null;
|
|
485
506
|
};
|
|
486
507
|
const createView = (window) => {
|
|
508
|
+
if (!window || window !== primaryHostWindow() || !isHostWindow(window)) return null;
|
|
487
509
|
const state = stateFor(window);
|
|
488
510
|
if (state.view && !state.view.webContents.isDestroyed()) return state;
|
|
489
511
|
const view = new WebContentsView({
|
|
@@ -529,6 +551,7 @@ if (
|
|
|
529
551
|
};
|
|
530
552
|
const show = (window, bounds) => {
|
|
531
553
|
const state = createView(window);
|
|
554
|
+
if (!state) return;
|
|
532
555
|
state.bounds = bounds;
|
|
533
556
|
state.view.setBounds(bounds);
|
|
534
557
|
state.view.setVisible(true);
|
|
@@ -539,6 +562,7 @@ if (
|
|
|
539
562
|
record("board-shown");
|
|
540
563
|
};
|
|
541
564
|
const layoutBoard = (window, bounds = null) => {
|
|
565
|
+
if (window !== primaryHostWindow()) return;
|
|
542
566
|
const state = windows.get(window.id);
|
|
543
567
|
if (!state?.visible || !state.view || state.view.webContents.isDestroyed()) return;
|
|
544
568
|
if (bounds) state.bounds = bounds;
|
|
@@ -573,8 +597,12 @@ if (
|
|
|
573
597
|
const content = window.getContentBounds();
|
|
574
598
|
state.nativeNav.setBounds({ x: content.x + 12, y: content.y + Math.max(48, content.height - 92), width: Math.min(120, Math.max(80, content.width - 24)), height: 38 });
|
|
575
599
|
};
|
|
576
|
-
const ensureNativeNav = (
|
|
577
|
-
|
|
600
|
+
const ensureNativeNav = (probeWindow) => {
|
|
601
|
+
// Renderer probes finish independently. Only the canonical host may
|
|
602
|
+
// install the singleton fallback; an auxiliary renderer cannot redirect
|
|
603
|
+
// it or acquire its own authenticated board view.
|
|
604
|
+
const window = fallbackHostWindow();
|
|
605
|
+
if (!window || probeWindow !== window) return;
|
|
578
606
|
const state = stateFor(window);
|
|
579
607
|
if (state.nativeNav && !state.nativeNav.isDestroyed()) return;
|
|
580
608
|
// Auxiliary vendor windows (for example Codex's hidden avatar overlay)
|
|
@@ -621,11 +649,10 @@ if (
|
|
|
621
649
|
nativeNavOwners.delete(nativeNav.webContents);
|
|
622
650
|
try { nativeNav.destroy(); } catch {}
|
|
623
651
|
};
|
|
624
|
-
const ownerWindow = (contents) =>
|
|
625
|
-
|| contents.getOwnerBrowserWindow?.()
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|| null;
|
|
652
|
+
const ownerWindow = (contents) => {
|
|
653
|
+
const window = BrowserWindow.fromWebContents(contents) || contents.getOwnerBrowserWindow?.() || null;
|
|
654
|
+
return isHostWindow(window) ? window : null;
|
|
655
|
+
};
|
|
629
656
|
const attachContents = (contents) => {
|
|
630
657
|
if (!contents || contents.isDestroyed() || contents.__impelDesktopTasksAttached || internalContents.has(contents)) return;
|
|
631
658
|
contents.__impelDesktopTasksAttached = true;
|
|
@@ -634,7 +661,7 @@ if (
|
|
|
634
661
|
Promise.all((contents.mainFrame?.framesInSubtree || []).map((frame) => frame.executeJavaScript(navSource, true)))
|
|
635
662
|
.then((results) => {
|
|
636
663
|
const window = ownerWindow(contents);
|
|
637
|
-
if (!window) return;
|
|
664
|
+
if (!window || window !== primaryHostWindow()) return;
|
|
638
665
|
const state = stateFor(window);
|
|
639
666
|
if (results.some((result) => result?.anchor)) {
|
|
640
667
|
hasDomNavigation = true;
|
|
@@ -648,11 +675,15 @@ if (
|
|
|
648
675
|
contents.on("did-finish-load", () => inject());
|
|
649
676
|
contents.on("did-frame-finish-load", () => inject());
|
|
650
677
|
if (!contents.isLoadingMainFrame()) setTimeout(() => inject(), 0);
|
|
678
|
+
let handledNavigation = null;
|
|
651
679
|
const handleNavigation = (event, url) => {
|
|
652
680
|
if (!String(url).startsWith("impel-tasks://")) return;
|
|
653
681
|
event.preventDefault();
|
|
654
682
|
const window = ownerWindow(contents);
|
|
655
|
-
if (!window) return;
|
|
683
|
+
if (!window || window !== primaryHostWindow()) return;
|
|
684
|
+
if (handledNavigation === url) return;
|
|
685
|
+
handledNavigation = url;
|
|
686
|
+
queueMicrotask(() => { if (handledNavigation === url) handledNavigation = null; });
|
|
656
687
|
let action;
|
|
657
688
|
try { action = new URL(url).hostname; } catch { return; }
|
|
658
689
|
if (action === "hide") hide(window);
|
|
@@ -692,11 +723,13 @@ if (
|
|
|
692
723
|
});
|
|
693
724
|
window.on("closed", () => {
|
|
694
725
|
const state = windows.get(window.id);
|
|
695
|
-
if (
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
726
|
+
if (state) {
|
|
727
|
+
try { if (state.view) window.contentView.removeChildView(state.view); } catch {}
|
|
728
|
+
try { state.view?.webContents.close(); } catch {}
|
|
729
|
+
try { state.nativeNav?.destroy(); } catch {}
|
|
730
|
+
}
|
|
699
731
|
windows.delete(window.id);
|
|
732
|
+
if (primaryHost === window) primaryHost = null;
|
|
700
733
|
});
|
|
701
734
|
};
|
|
702
735
|
ipcMain.on(nativeShowChannel, (event) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Fleet generation shared by managed-profile writers and upstream clients.
|
|
2
2
|
// Keep this isolated from apps.js so latency-sensitive transports do not load
|
|
3
3
|
// desktop bundle machinery just to identify their managed config contract.
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
export const CURRENT_CONFIG_VERSION =
|
|
4
|
+
// v39 confines the authenticated desktop Tasks view to one canonical primary
|
|
5
|
+
// vendor window and forces every existing managed profile to receive the fix.
|
|
6
|
+
export const CURRENT_CONFIG_VERSION = 39;
|