impel-cli 0.20.47 → 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 +19 -0
- package/package.json +1 -1
- package/src/desktopTasks.js +53 -27
- package/src/managedProfileVersion.js +3 -3
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
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
|
+
|
|
3
22
|
## 0.20.47 — Keep Tasks on the primary app window
|
|
4
23
|
|
|
5
24
|
- Makes the native Tasks fallback choose the earliest surviving primary host
|
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,17 +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
|
-
const fallbackHostWindow = () => BrowserWindow.getAllWindows()
|
|
487
|
-
.filter(isPrimaryWindow)
|
|
488
|
-
.sort((left, right) => left.id - right.id)[0] || null;
|
|
489
507
|
const createView = (window) => {
|
|
508
|
+
if (!window || window !== primaryHostWindow() || !isHostWindow(window)) return null;
|
|
490
509
|
const state = stateFor(window);
|
|
491
510
|
if (state.view && !state.view.webContents.isDestroyed()) return state;
|
|
492
511
|
const view = new WebContentsView({
|
|
@@ -532,6 +551,7 @@ if (
|
|
|
532
551
|
};
|
|
533
552
|
const show = (window, bounds) => {
|
|
534
553
|
const state = createView(window);
|
|
554
|
+
if (!state) return;
|
|
535
555
|
state.bounds = bounds;
|
|
536
556
|
state.view.setBounds(bounds);
|
|
537
557
|
state.view.setVisible(true);
|
|
@@ -542,6 +562,7 @@ if (
|
|
|
542
562
|
record("board-shown");
|
|
543
563
|
};
|
|
544
564
|
const layoutBoard = (window, bounds = null) => {
|
|
565
|
+
if (window !== primaryHostWindow()) return;
|
|
545
566
|
const state = windows.get(window.id);
|
|
546
567
|
if (!state?.visible || !state.view || state.view.webContents.isDestroyed()) return;
|
|
547
568
|
if (bounds) state.bounds = bounds;
|
|
@@ -576,12 +597,12 @@ if (
|
|
|
576
597
|
const content = window.getContentBounds();
|
|
577
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 });
|
|
578
599
|
};
|
|
579
|
-
const ensureNativeNav = () => {
|
|
580
|
-
// Renderer probes finish independently.
|
|
581
|
-
//
|
|
582
|
-
//
|
|
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.
|
|
583
604
|
const window = fallbackHostWindow();
|
|
584
|
-
if (!window) return;
|
|
605
|
+
if (!window || probeWindow !== window) return;
|
|
585
606
|
const state = stateFor(window);
|
|
586
607
|
if (state.nativeNav && !state.nativeNav.isDestroyed()) return;
|
|
587
608
|
// Auxiliary vendor windows (for example Codex's hidden avatar overlay)
|
|
@@ -628,11 +649,10 @@ if (
|
|
|
628
649
|
nativeNavOwners.delete(nativeNav.webContents);
|
|
629
650
|
try { nativeNav.destroy(); } catch {}
|
|
630
651
|
};
|
|
631
|
-
const ownerWindow = (contents) =>
|
|
632
|
-
|| contents.getOwnerBrowserWindow?.()
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|| null;
|
|
652
|
+
const ownerWindow = (contents) => {
|
|
653
|
+
const window = BrowserWindow.fromWebContents(contents) || contents.getOwnerBrowserWindow?.() || null;
|
|
654
|
+
return isHostWindow(window) ? window : null;
|
|
655
|
+
};
|
|
636
656
|
const attachContents = (contents) => {
|
|
637
657
|
if (!contents || contents.isDestroyed() || contents.__impelDesktopTasksAttached || internalContents.has(contents)) return;
|
|
638
658
|
contents.__impelDesktopTasksAttached = true;
|
|
@@ -641,25 +661,29 @@ if (
|
|
|
641
661
|
Promise.all((contents.mainFrame?.framesInSubtree || []).map((frame) => frame.executeJavaScript(navSource, true)))
|
|
642
662
|
.then((results) => {
|
|
643
663
|
const window = ownerWindow(contents);
|
|
644
|
-
if (!window) return;
|
|
664
|
+
if (!window || window !== primaryHostWindow()) return;
|
|
645
665
|
const state = stateFor(window);
|
|
646
666
|
if (results.some((result) => result?.anchor)) {
|
|
647
667
|
hasDomNavigation = true;
|
|
648
668
|
state.hasDomNavigation = true;
|
|
649
669
|
for (const owner of BrowserWindow.getAllWindows()) removeNativeNav(owner);
|
|
650
670
|
} else if (attempt < 3) setTimeout(() => inject(attempt + 1), 1000);
|
|
651
|
-
else if (!state.hasDomNavigation && !hasDomNavigation) ensureNativeNav();
|
|
671
|
+
else if (!state.hasDomNavigation && !hasDomNavigation) ensureNativeNav(window);
|
|
652
672
|
})
|
|
653
673
|
.catch(() => record("navigation-injection-failed"));
|
|
654
674
|
};
|
|
655
675
|
contents.on("did-finish-load", () => inject());
|
|
656
676
|
contents.on("did-frame-finish-load", () => inject());
|
|
657
677
|
if (!contents.isLoadingMainFrame()) setTimeout(() => inject(), 0);
|
|
678
|
+
let handledNavigation = null;
|
|
658
679
|
const handleNavigation = (event, url) => {
|
|
659
680
|
if (!String(url).startsWith("impel-tasks://")) return;
|
|
660
681
|
event.preventDefault();
|
|
661
682
|
const window = ownerWindow(contents);
|
|
662
|
-
if (!window) return;
|
|
683
|
+
if (!window || window !== primaryHostWindow()) return;
|
|
684
|
+
if (handledNavigation === url) return;
|
|
685
|
+
handledNavigation = url;
|
|
686
|
+
queueMicrotask(() => { if (handledNavigation === url) handledNavigation = null; });
|
|
663
687
|
let action;
|
|
664
688
|
try { action = new URL(url).hostname; } catch { return; }
|
|
665
689
|
if (action === "hide") hide(window);
|
|
@@ -699,11 +723,13 @@ if (
|
|
|
699
723
|
});
|
|
700
724
|
window.on("closed", () => {
|
|
701
725
|
const state = windows.get(window.id);
|
|
702
|
-
if (
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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
|
+
}
|
|
706
731
|
windows.delete(window.id);
|
|
732
|
+
if (primaryHost === window) primaryHost = null;
|
|
707
733
|
});
|
|
708
734
|
};
|
|
709
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;
|