mirror of
https://github.com/block/buzz.git
synced 2026-10-31 14:31:02 +00:00
## Summary - defer foreground resume work until the activation task has returned, a frame has painted, and a trailing task gets a turn - centralize app-focus subscribers and remove the broad TanStack `refetchOnWindowFocus` fan-out - coalesce relay recovery and preserve an explicit deferred refresh only for workflow data without a polling/push freshness path - defer the notification permission native check while keeping blur and cheap correctness signals immediate ## Why Buzz Desktop 0.5.10 can spend roughly 1.5 seconds in the WebKit window-focus listener/microtask checkpoint before returning to the run loop. Focus currently fans out into query refetches, React polling updates, relay reconnect/replay, and native work in one activation turn. This patch establishes an interaction-first foreground boundary rather than letting those consumers compete with the activating input and first paint. ## Validation - focused foreground/workflow/relay tests: 18/18 passed before commit - `pnpm --dir desktop typecheck`: passed before commit - pre-commit desktop check and file-size gate: passed - pre-push desktop check, typecheck, and full desktop unit suite: 4,743/4,743 passed at `704e7b4b6618fafce655bb2b07c7a9fe0fc8c643` - Princess Donut independent adversarial review: PASS after two lifecycle/freshness blockers were resolved ## Manual test 1. Install the PR build and use Buzz long enough to populate channels, home, workflows, agents, and other polling surfaces. 2. Switch to another app for 30-60 seconds. 3. Return by clicking Buzz and immediately click a channel or scroll. 4. Confirm the first interaction and paint are prompt, then confirm channels/home/workflows refresh and a degraded relay reconnects after the activation boundary. 5. Repeat while rapidly switching away again to verify no resume work starts after focus has been lost. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
142 lines
3.9 KiB
JavaScript
142 lines
3.9 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { afterEach, describe, it } from "node:test";
|
|
|
|
import { installRelayResumeTriggers } from "./useRelayResumeTriggers.ts";
|
|
|
|
const originalDocument = globalThis.document;
|
|
const originalWindow = globalThis.window;
|
|
|
|
afterEach(() => {
|
|
if (originalDocument === undefined) delete globalThis.document;
|
|
else globalThis.document = originalDocument;
|
|
if (originalWindow === undefined) delete globalThis.window;
|
|
else globalThis.window = originalWindow;
|
|
});
|
|
|
|
function installEventStub() {
|
|
let visibilityState = "visible";
|
|
const documentListeners = new Map();
|
|
const windowListeners = new Map();
|
|
const add = (listeners, type, listener) => {
|
|
const values = listeners.get(type) ?? new Set();
|
|
values.add(listener);
|
|
listeners.set(type, values);
|
|
};
|
|
const remove = (listeners, type, listener) => {
|
|
const values = listeners.get(type);
|
|
values?.delete(listener);
|
|
if (values?.size === 0) listeners.delete(type);
|
|
};
|
|
globalThis.document = {
|
|
get visibilityState() {
|
|
return visibilityState;
|
|
},
|
|
addEventListener: (type, listener) =>
|
|
add(documentListeners, type, listener),
|
|
removeEventListener: (type, listener) =>
|
|
remove(documentListeners, type, listener),
|
|
};
|
|
globalThis.window = {
|
|
addEventListener: (type, listener) => add(windowListeners, type, listener),
|
|
removeEventListener: (type, listener) =>
|
|
remove(windowListeners, type, listener),
|
|
};
|
|
return {
|
|
documentListeners,
|
|
windowListeners,
|
|
emitDocument(type) {
|
|
for (const listener of documentListeners.get(type) ?? []) listener();
|
|
},
|
|
emitWindow(type) {
|
|
for (const listener of windowListeners.get(type) ?? []) listener();
|
|
},
|
|
setVisibility(value) {
|
|
visibilityState = value;
|
|
},
|
|
};
|
|
}
|
|
|
|
describe("relay resume triggers", () => {
|
|
it("defers and coalesces foreground signals", () => {
|
|
const stub = installEventStub();
|
|
const scheduled = [];
|
|
let attempts = 0;
|
|
const dispose = installRelayResumeTriggers(
|
|
() => attempts++,
|
|
(callback) => {
|
|
const pending = { callback, cancelled: false };
|
|
scheduled.push(pending);
|
|
return () => {
|
|
pending.cancelled = true;
|
|
};
|
|
},
|
|
);
|
|
|
|
stub.emitWindow("focus");
|
|
stub.emitWindow("online");
|
|
stub.emitDocument("visibilitychange");
|
|
assert.equal(attempts, 0, "resume is not attempted in the event turn");
|
|
assert.equal(scheduled.length, 1, "foreground burst is coalesced");
|
|
|
|
scheduled[0].callback();
|
|
assert.equal(attempts, 1);
|
|
dispose();
|
|
assert.equal(stub.documentListeners.size, 0);
|
|
assert.equal(stub.windowListeners.size, 0);
|
|
});
|
|
|
|
it("cancels a pending resume when hidden", () => {
|
|
const stub = installEventStub();
|
|
let pending;
|
|
let attempts = 0;
|
|
installRelayResumeTriggers(
|
|
() => attempts++,
|
|
(callback) => {
|
|
pending = {
|
|
cancelled: false,
|
|
run() {
|
|
if (!this.cancelled) callback();
|
|
},
|
|
};
|
|
return () => {
|
|
pending.cancelled = true;
|
|
};
|
|
},
|
|
);
|
|
|
|
stub.emitWindow("focus");
|
|
stub.setVisibility("hidden");
|
|
stub.emitDocument("visibilitychange");
|
|
assert.equal(pending.cancelled, true);
|
|
pending.run();
|
|
assert.equal(attempts, 0, "hidden state suppresses pending resume");
|
|
});
|
|
|
|
it("cancels a pending resume on dispose", () => {
|
|
const stub = installEventStub();
|
|
let pending;
|
|
let attempts = 0;
|
|
const dispose = installRelayResumeTriggers(
|
|
() => attempts++,
|
|
(callback) => {
|
|
pending = {
|
|
cancelled: false,
|
|
run() {
|
|
if (!this.cancelled) callback();
|
|
},
|
|
};
|
|
return () => {
|
|
pending.cancelled = true;
|
|
};
|
|
},
|
|
);
|
|
|
|
stub.emitWindow("focus");
|
|
dispose();
|
|
pending.run();
|
|
assert.equal(attempts, 0);
|
|
assert.equal(stub.documentListeners.size, 0);
|
|
assert.equal(stub.windowListeners.size, 0);
|
|
});
|
|
});
|