@workbench-kit/electron-shell 0.0.2-prototype.0.2.32 → 0.0.2-prototype.0.2.33

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/README.md CHANGED
@@ -71,16 +71,22 @@ electronApp.on('before-quit', (event) => {
71
71
  ```
72
72
 
73
73
  Repeated quit events are coalesced into the active request. After save or discard,
74
- the guard rechecks dirty state and resumes only when clean. Errors, timeout, and a
75
- still-dirty recheck fail closed. `cancelPending()` aborts an obsolete request and
76
- invalidates any late guard completion; call it when the owning lifecycle is
77
- replaced. A callback that has already started must honor its `AbortSignal` to stop
78
- its own side effects.
74
+ the guard rechecks dirty state and resumes only when clean. It invokes `resumeQuit`
75
+ on the next event-loop task so Electron can finish dispatching the original
76
+ `before-quit` event first. Errors, timeout, and a still-dirty recheck fail closed.
77
+ `cancelPending()` aborts an obsolete request and invalidates any late guard
78
+ completion; call it when the owning lifecycle is replaced. A callback that has
79
+ already started must honor its `AbortSignal` to stop its own side effects.
79
80
  The injected `resumeQuit` port must re-enter the registered guard synchronously;
80
81
  an asynchronous wrapper is treated as a fresh request. OS shutdown paths that do
81
82
  not emit `before-quit`, and updater flows that emit it after windows close, remain
82
83
  host-owned lifecycle concerns outside this guard.
83
84
 
85
+ The repository also runs `pnpm test:electron-quit-guard` against Electron 41.5
86
+ with a live `BrowserWindow`. It verifies the deferred save path, a coalesced quit
87
+ attempt before the scheduled resume, and the normal window-close / application-quit
88
+ lifecycle after the permitted re-entry.
89
+
84
90
  For external links, keep the product allowlist outside the Kit and pass only an
85
91
  opaque link id into the generic helper:
86
92
 
@@ -38,6 +38,11 @@ function raceWithAbort(operation, signal, readAbortKind) {
38
38
  });
39
39
  });
40
40
  }
41
+ function waitForNextTask() {
42
+ return new Promise((resolve) => {
43
+ setTimeout(resolve, 0);
44
+ });
45
+ }
41
46
  /**
42
47
  * Coordinate an Electron-style before-quit boundary without importing Electron.
43
48
  * The host owns event registration, dirty state, prompts, persistence, and quit invocation.
@@ -76,7 +81,10 @@ function createApplicationQuitGuard(options) {
76
81
  }
77
82
  return resume(reason);
78
83
  };
79
- const resume = (reason) => {
84
+ const resume = async (reason) => {
85
+ // A Promise continuation can still run before Electron finishes dispatching the original
86
+ // before-quit event. Cross a task boundary before re-entering app.quit().
87
+ await run(waitForNextTask);
80
88
  // The decision is now irreversible. `cancelPending` must not report a cancellable request
81
89
  // while the host is synchronously re-entering before-quit.
82
90
  if (isCurrent()) {
@@ -99,7 +107,7 @@ function createApplicationQuitGuard(options) {
99
107
  throw new TypeError('Application quit guard isDirty must return a boolean.');
100
108
  }
101
109
  if (!dirty) {
102
- return resume('clean');
110
+ return await resume('clean');
103
111
  }
104
112
  const decision = await run(() => options.requestDecision(controller.signal));
105
113
  if (decision === 'cancel') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workbench-kit/electron-shell",
3
- "version": "0.0.2-prototype.0.2.32",
3
+ "version": "0.0.2-prototype.0.2.33",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "types": "./src/index.ts",
@@ -109,6 +109,12 @@ function raceWithAbort<Value>(
109
109
  });
110
110
  }
111
111
 
112
+ function waitForNextTask(): Promise<void> {
113
+ return new Promise((resolve) => {
114
+ setTimeout(resolve, 0);
115
+ });
116
+ }
117
+
112
118
  /**
113
119
  * Coordinate an Electron-style before-quit boundary without importing Electron.
114
120
  * The host owns event registration, dirty state, prompts, persistence, and quit invocation.
@@ -158,7 +164,13 @@ export function createApplicationQuitGuard(
158
164
  return resume(reason);
159
165
  };
160
166
 
161
- const resume = (reason: ApplicationQuitProceedReason): ApplicationQuitGuardResult => {
167
+ const resume = async (
168
+ reason: ApplicationQuitProceedReason,
169
+ ): Promise<ApplicationQuitGuardResult> => {
170
+ // A Promise continuation can still run before Electron finishes dispatching the original
171
+ // before-quit event. Cross a task boundary before re-entering app.quit().
172
+ await run(waitForNextTask);
173
+
162
174
  // The decision is now irreversible. `cancelPending` must not report a cancellable request
163
175
  // while the host is synchronously re-entering before-quit.
164
176
  if (isCurrent()) {
@@ -181,7 +193,7 @@ export function createApplicationQuitGuard(
181
193
  throw new TypeError('Application quit guard isDirty must return a boolean.');
182
194
  }
183
195
  if (!dirty) {
184
- return resume('clean');
196
+ return await resume('clean');
185
197
  }
186
198
 
187
199
  const decision = await run(() => options.requestDecision(controller.signal));