land: area/client-reliability 683cedf0bf (client-reliability)

This commit is contained in:
thePR0M3TH3AN committed 2026-10-19 16:29:12 -04:00
commit de6918cee8
18 files changed
+308 -32

No files matched your search

+75
View File
@@ -588,3 +588,78 @@ test_integrity_note:
did_relax_any_assertion: false did_relax_any_assertion: false
if_true_explain_spec_basis: "" if_true_explain_spec_basis: ""
``` ```
---
# client-reliability-1
```yaml
test_integrity_note:
change_type: ["flake_fix", "new_tests"]
scenarios:
- id: SCN-profile-focus-style-contract
given: "modal controls include visible, disabled, CSS-hidden, inherited-hidden, aria-hidden, hidden-input, and hidden-attribute cases"
when: "the real controller collects focus targets using fixture and native JSDOM styles, and the user tabs from the last control"
then: "both resolvers retain only visible enabled controls and Tab wraps to the first"
observable_outcomes:
- "focus target control IDs match the strict expected list"
- "Tab is prevented and document.activeElement moves to the first control"
- "all 26 existing modal scenarios preserve their assertions and complete naturally"
determinism_controls:
- "stylesheet-free full-markup fixture virtualizes only display and visibility"
- "small contract scenario runs the same assertions against native JSDOM computed styles"
- "explicit test stylesheet, local services, real DOM focus and event dispatch; no network"
anti_cheat_rationale:
prevents:
- "hard-coded focus list: varied hidden and visible controls"
- "over-mocking controller behavior: real updateFocusTrap and keyboard listener"
- "style resolver rubber-stamping: native resolver must satisfy the same scenario"
- "retry/sleep-based flake masking: no timeouts, retries, or sleeps added"
relaxation:
did_relax_any_assertion: false
if_true_explain_spec_basis: ""
```
Mutation evidence: removing the product display/visibility exclusion causes this scenario to fail with the hidden control IDs in its focus list (exit 1, 1 failed/0 passed). Mutant reverted. This fixes expensive JSDOM style evaluation in the test harness, not a demonstrated browser defect. Application CSS/layout remains outside this unit fixture's scope.
Fixture mutation: omitting native hidden-attribute filtering also fails the contract (exit 1, 1 failed/0 passed); restored before final checks.
---
# client-reliability-2
```yaml
test_integrity_note:
change_type: ["flake_fix", "new_tests"]
scenarios:
- id: SCN-concurrent-init-hermetic-subscription
given: "two init callers share a pool initialization held at a deterministic barrier, with a virtual successful relay connection"
when: "pool initialization is released and the second caller opens then closes a video subscription"
then: "the second caller waits for pool readiness, real L1 feed setup reaches the fake pool, and unsubscribe closes the transport exactly once with no reconnect timer"
observable_outcomes:
- "second init promise remains pending until the pool barrier is released"
- "pool is ready after awaiting the second caller; both init promises settle"
- "post-init subscription requests video events from a nonempty relay set"
- "subscription transport closes once, including repeated unsubscribe"
- "the process exits naturally with no public relay IO or reconnect retries"
determinism_controls:
- "deferred pool barrier replaces the old 50ms delay"
- "fake pool at the actual transport boundary; real init, L1, and feed cleanup"
- "manager connection boundary reports success rather than triggering background reconnect"
- "local-data/signer restore are virtualized; teardown awaits init, closes feed and resets reconnect state even after failure"
anti_cheat_rationale:
prevents:
- "early-return init mutant fails while pool initialization is held"
- "no-op subscription cannot satisfy the observed transport request"
- "no-op cleanup cannot satisfy exact close count"
- "failure fixture cannot hide a reconnect timer behind a passing assertion"
- "retry/sleep/forced-exit masking: no timeout changes or forced exit"
relaxation:
did_relax_any_assertion: false
if_true_explain_spec_basis: ""
```
Original concurrent-promise, pool-ready and post-init no-throw assertions remain. The old empty connection result accidentally modeled failure; init correctly scheduled manager-owned retries that bypassed its client-level stub. The fixture now models successful init at the manager boundary and releases its feed subscription. This is harness cleanup, not a Nostr product change.
Mutants: a test-local historical early-return init variant fails the pending-barrier assertion (exit1, 1 failed); a connection-failure fixture variant fails the no-retry assertion (exit1, 1 failed). Both exit naturally and are reverted. No product file was edited for either mutant.
@@ -0,0 +1,3 @@
- Goal: classify and fix profile-modal-controller suite hang at current HEAD 9e6c310d (unchanged from filedAt 8b204687).
- Scope: owned test and narrow modal lifecycle helpers only. Preserve assertions; no retries, sleeps, timeout increases, quarantine or forced success exit.
- Done: deterministic regression, failing mutant, isolated natural exit, full 60s/file unit run, lint; build/browser if product changes; fleet commit, merge unstable, checks, queue and durable landing.
@@ -0,0 +1,5 @@
- Goal: make tests/race/nostr-client-init-race.test.mjs hermetic and naturally exit while preserving concurrent init, pool readiness, and post-init subscription coverage.
- Temporary ownership granted by dispatch via landed RULE-CHANGES/prompt and owner resume instruction; no Nostr product/shared helper/global runner edits.
- Revalidated unchanged from filedAt f303ab0e72; prior isolated assertion60.91ms passes but process timeout10s/exit124, full gate timeout60s at file227.
- Plan: inspect NIP01 and real init/L1 subscription lifecycle; replace delay with deferred pool barrier; virtualize current pool boundary, close subscription; add observable boundary/cleanup assertions; mutant-check, run both isolated suites, commit separately, merge unstable, lint and full60s/file gates, land normally or route further blockers.
- Preserve prior profile-modal commits and all assertions. No browser, real wallet, signer, external relay or deploy operations.
@@ -0,0 +1,9 @@
# client-reliability-1: classify the profile-modal timeout
- Product/test source unchanged between filedAt 8b204687 and startup HEAD 9e6c310d.
- Clean-environment 60s/file run reproduced timeout. Per-scenario tracing then completed all 26 scenarios naturally in 57041.55ms; no blocked await or retained resource was found. Timing near the bound varies with cold/warm JSDOM style caches.
- CPU profile of the Escape scenario points to CSS color/border normalization and computed-style resolution. The full markup has many controls; updateFocusTrap asks JSDOM for a full computed declaration for each control on repeated pane refreshes.
- Fix only the stylesheet-free test fixture: virtualize display/visibility, with explicit fixture CSS and a new native-JSDOM contract scenario. Preserve every existing assertion and real controller, focus events, DOM and pane operations. No timeout/retry/sleep/runner or product change.
- Regression compares fixture/native style filtering for visible, disabled, CSS-hidden, inherited-visibility-hidden, aria-hidden, and hidden-type controls, then checks actual Tab event focus wrapping.
- Limits: fixture does not model application CSS/layout. This suite never loaded those styles; browser and build checks are not required for this test-only fix. No release, browser, signer, network or wallet qualification claimed.
- Slowdown: initial cold worktree lacked dependencies; cached offline install resolved it. Native DOM assertion diffs made the first mutant diagnostic expensive; comparing control IDs yields a bounded useful failure. Tool improvement: show per-scenario start/end timestamps when a file exceeds its diagnostic bound, distinguishing slow progress from a blocked await.
@@ -0,0 +1,8 @@
# client-reliability-2: hermetic concurrent init
- NIP01 REQ/CLOSE and docs/architecture-refactor.md L1 contract require callers to close subscriptions. Retain real init/L1/feed behavior; fake only IO boundaries.
- Root cause: original client.connectToRelays stub returned [], explicitly signaling initial connection failure. init schedules ConnectionManager's timer, whose callback calls manager.connectToRelays (not the client stub). That path attempts configured public relays and retains retries. Original subscription was also never unsubscribed.
- Replace 50ms mock sleep with deferred entry/release barriers. Successful manager-level connection fixture prevents retries without disabling product retry logic. Pin timer absence with assertion; teardown resets as failure-path hygiene, not a success substitute.
- Add stricter outcomes: pending second caller, observed pool feed filter, nonempty relays, and exactly-once transport closure. Preserve all original expectations and await both callers.
- Mutate via test-local IO/init overrides only; product Nostr/shared helpers/global runner remain untouched. Both early-return and failure-connection variants fail naturally.
- Prior profile-modal commits preserved separately. No actual signer/wallet/relay/browser/build/deploy qualification. Normal full gates required before either item lands.
+4
View File
@@ -11,3 +11,7 @@
## client-reliability ## client-reliability
- 2026-10-19: client-reliability-1 assigned: reproduce/classify and fix profile-modal suite hang without weakening assertions. Initial evidence: three isolated 60s timeouts (one clean environment), full baseline stopped at file 220/425; read docs/agents/prompts/client-reliability.md and claim the item. Scope excludes app.js and unrelated subsystems. - 2026-10-19: client-reliability-1 assigned: reproduce/classify and fix profile-modal suite hang without weakening assertions. Initial evidence: three isolated 60s timeouts (one clean environment), full baseline stopped at file 220/425; read docs/agents/prompts/client-reliability.md and claim the item. Scope excludes app.js and unrelated subsystems.
- 2026-10-19: client-reliability-1 fix committed at dbdac18d (test-only fixture, original assertions preserved; native focus contract and both mutants fail when broken). Isolated27/27 natural exit2.44s, lint11/11. Full60s/file run passed226/425 files including profile-modal; file227 tests/race/nostr-client-init-race.test.mjs timed out;198 later files untested. Await dispatch routing for this unowned blocker before normal landing/close. No product/browser/build qualification or landing claimed. See test_logs/TEST_LOG_2026-10-19_20-13-00_client-reliability-final.md.
- 2026-10-19: client-reliability-1 and client-reliability-2 ready for normal landing at181eae95: profile27/27 and race1/1 natural exit; all425 unit files passed with60s/file bound; lint11/11. Four deliberately broken variants fail and are reverted. Dispatch routing unblocked the second item; no product Nostr/shared-runner changes. Remaining: durable normal queue landing, unstable verification, then backlog close. Browser/build and real IO unverified (test-only scope). See test_logs/TEST_LOG_2026-10-19_20-35-00_client-reliability-both.md.
@@ -1,7 +1,7 @@
{ {
"id": "client-reliability-1", "id": "client-reliability-1",
"area": "client-reliability", "area": "client-reliability",
"status": "open", "status": "in-progress",
"title": "Diagnose and fix profile-modal suite hang without weakening assertions", "title": "Diagnose and fix profile-modal suite hang without weakening assertions",
"blockedOn": null, "blockedOn": null,
"addedOn": "2026-10-19", "addedOn": "2026-10-19",
@@ -1,7 +1,7 @@
{ {
"id": "client-reliability-2", "id": "client-reliability-2",
"area": "client-reliability", "area": "client-reliability",
"status": "open", "status": "in-progress",
"title": "Make Nostr initialization race test hermetic and release retained relay work", "title": "Make Nostr initialization race test hermetic and release retained relay work",
"blockedOn": null, "blockedOn": null,
"addedOn": "2026-10-19", "addedOn": "2026-10-19",
@@ -0,0 +1,13 @@
# client-reliability-1 initial evidence (startup HEAD 9e6c310d)
- `npm ci --ignore-scripts --offline --cache /tmp/client-reliability-npm-cache`: exit 1, ENOTCACHED. Default-cache `npm ci --ignore-scripts --offline`: exit 0, 441 packages installed, zero audit vulnerabilities.
- GitHub PR-overlap API read: exit 6, DNS unavailable; no PR opened, fleet assignment reserves scope.
- Initial direct node run: exit 1, missing jsdom before install.
- `env -u NODE_TEST_CONTEXT UNIT_TEST_TIMEOUT_MS=60000 node scripts/run-unit-tests.mjs profile-modal-controller.test.mjs`: exit 1, timed out at 60000ms.
- Unbounded direct node baseline with temporary start/end tracing: exit 0 naturally, 26 tests passed, 0 failed/cancelled/skipped, 57041.55ms. Tracing removed. An earlier unbounded diagnostic was interrupted (130), no result claimed.
- CPU-profiled Escape scenario: exit 0, 1 test passed, 3703.35ms total. Largest sampled costs include CSS color serialization/border normalization; no client defect established.
- With final CSS fixture, direct node isolated suite: exit 0 naturally, 27 passed, 0 failed/cancelled/skipped, 2840.18ms.
- Deliberate product mutant removing display/visibility filtering: final regression exits 1, 1 failed/0 passed, hidden control IDs appear in actual focus list. Product restored byte-for-byte. Initial DOM-object diff diagnostic interrupted (130); replaced with control-ID diagnostics, no assertion relaxation.
- Initial sandbox lint: exit 2, git subprocess EPERM. Unrestricted lint then exposed forbidden direct style assignments in test fixture; corrected to fixture stylesheet classes. `npm run lint` unrestricted final run: exit 0, all 11 stages passed.
- Early full-unit diagnostic interrupted (130) before completion while final fixture was being prepared; no full pass claimed. Final committed/merged-tree checks follow in a separate log.
- Merged-tree isolated check at d0224078: exit 0, 27/27, 2700.15ms; lint 11/11 exit 0. Full diagnostic stopped before completion (130) for a fixture correction: preserve native `[hidden]` file-input display filtering. Added this control to the same contract scenario. Corrected isolated suite exits 0, 27/27. Fixture mutant omitting `[hidden]` filtering exits 1, 1 failed/0 passed; fixture restored. The full suite will restart on the final committed tree.
@@ -0,0 +1,10 @@
# client-reliability-1 final-tree validation: dbdac18df1a4957cf15f47afdb205e28d7cb1724
- Branch contains unstable e106a15e, merged at d0224078; final `git merge unstable` reported already up to date.
- `env -u NODE_TEST_CONTEXT NODE_ENV=test node --import ./tests/test-helpers/setup-localstorage.mjs --test-reporter=spec tests/profile-modal-controller.test.mjs`: exit 0 naturally; 27 tests passed, 0 failed/cancelled/skipped/todo; 2442.95ms.
- `npm run lint` (unrestricted because sandbox git subprocess EPERM): exit 0, all 11 lint stages passed.
- `env -u NODE_TEST_CONTEXT UNIT_TEST_TIMEOUT_MS=60000 npm test`: exit 1. 227/425 files started; first 226 exited 0 including tests/profile-modal-controller.test.mjs; file 227 tests/race/nostr-client-init-race.test.mjs aborted after 60000ms. 198 files not run. Full suite is NOT green. Runner reports file outcomes, not an aggregate scenario count.
- Product filtering mutant: exit 1, 1 failed/0 passed. Fixture hidden-attribute mutant: exit 1, 1 failed/0 passed. Both restored. No product source changes remain.
- Not verified: later 198 unit files, browser behavior, full application CSS/layout, signer/wallet/network behavior, build, release/deployment. Test-only change; no browser/build qualification claimed.
- No queue submission while full gate is blocked. Dispatch notified via mailbox 20261019T201304-client-reliability-a7c828.md of the newly exposed unowned race-suite timeout; waiting for routing/scope decision.
- Read-only isolated race diagnostic: `env -u NODE_TEST_CONTEXT NODE_ENV=test timeout 10s node --import ./tests/test-helpers/setup-localstorage.mjs --test-reporter=spec tests/race/nostr-client-init-race.test.mjs`: exit124; its single test reports PASS in60.91ms, then process retains background relay retries. This is a retained-resource/harness-cleanup boundary, distinct from the profile suite's synchronous style cost. No race or Nostr source changed.
@@ -0,0 +1,10 @@
# client-reliability-2 pre-commit checks
- Baseline race unchanged from filedAt f303ab0e72 through merged3968a979; prior full run failed60s/file after226/425 passed and isolated scenario passed60.91ms but process timed out10s/124. Dispatch independently confirmed62ms/124.
- Fixed direct isolated race run: exit0 naturally, 1 passed, 0 failed/cancelled/skipped; scenario9.43ms, total15.69ms (before failure-path teardown await refinement).
- Test-local historical early-return init mutant: exit1 naturally, 1 failed/0 passed; pending second caller assertion catches early settlement.
- Test-local failure connection mutant: exit1 naturally, 1 failed/0 passed; no-retry assertion catches the reconnect timer. Both mutations restored. Product files untouched.
- Final isolated/profile/lint and merged full gates recorded next. No full pass or landing claimed yet.
- Final direct isolated race file: exit0 naturally, 1 passed/0 failed/cancelled/skipped, scenario10.32ms / total14.86ms.
- Preserved profile-modal file: exit0 naturally, 27 passed/0 failed/cancelled/skipped, total2635.71ms.
- `npm run lint` unrestricted: exit0, all11 stages passed. `git diff --check`: exit0.
@@ -0,0 +1,11 @@
# Both reliability fixes: final validation at181eae95d9bb9626cfbca9904652731fe7cd49aa
- `git merge unstable`: already up to date (contains dispatch routing and profile fix).
- `env -u NODE_TEST_CONTEXT NODE_ENV=test node --import ./tests/test-helpers/setup-localstorage.mjs --test-reporter=spec tests/race/nostr-client-init-race.test.mjs`: exit0 naturally; 1 passed/0 failed/cancelled/skipped, scenario10.32ms, total14.86ms.
- Same direct command for tests/profile-modal-controller.test.mjs: exit0 naturally;27 passed/0 failed/cancelled/skipped,2635.71ms.
- `npm run lint` unrestricted: exit0,11/11 stages. Sandbox git subprocess restrictions were the reason for unrestricted lint, not a code failure.
- `env -u NODE_TEST_CONTEXT UNIT_TEST_TIMEOUT_MS=60000 npm test`: exit0, all425/425 files completed,0 quarantined. Runner emits file outcomes, no aggregate scenario count. The previous profile file220 and race file227 both completed; all198 files beyond the prior blocker were reached.
- Profile mutants: product visibility exclusion removed ->1 failed; fixture hidden-attribute omission ->1 failed. Race mutants: historical early-return init ->1 failed; fixture connection failure ->1 failed. All exited1 and were reverted; no product Nostr files edited. See prior integrity/evidence entries.
- Scope: two test files plus notes/backlog only. No product changes, timeouts/retries/sleeps/forced exits/runner changes. No browser/build/real-signer/real-wallet/production or deploy verification claimed.
- Slowdown: baseline full-suite cutoffs hid the second resource leak; a scenario pass was insufficient to prove process completion. Improvement: a readable per-scenario reporter plus pending-resource diagnostics would distinguish style CPU cost from retained relay retries earlier.
- Normal landing queue submission follows; neither item is closed before durable landing plus unstable verification.
+63
View File
@@ -19,6 +19,7 @@ const defaultActorHex = 'a'.repeat(64);
let dom; let dom;
let container; let container;
let nativeGetComputedStyle;
async function waitForAnimationFrame(window, cycles = 1) { async function waitForAnimationFrame(window, cycles = 1) {
for (let i = 0; i < cycles; i += 1) { for (let i = 0; i < cycles; i += 1) {
@@ -125,6 +126,21 @@ beforeEach(() => {
global.requestAnimationFrame = window.requestAnimationFrame; global.requestAnimationFrame = window.requestAnimationFrame;
global.cancelAnimationFrame = window.cancelAnimationFrame; global.cancelAnimationFrame = window.cancelAnimationFrame;
// This fixture loads markup without application stylesheets. Virtualize the
// two computed properties consumed by the focus trap rather than asking JSDOM
// to resolve every CSS property for every control on each pane refresh.
const focusStyles = document.createElement('style');
focusStyles.textContent = `
.focus-test-display-none { display: none; }
.focus-test-visibility-hidden { visibility: hidden; }
`;
document.head.appendChild(focusStyles);
nativeGetComputedStyle = window.getComputedStyle.bind(window);
window.getComputedStyle = (element) => ({
display: element.matches('.focus-test-display-none, [hidden], input[type="hidden"]') ? 'none' : 'block',
visibility: element.closest('.focus-test-visibility-hidden') ? 'hidden' : 'visible',
});
window.confirm = () => true; window.confirm = () => true;
global.confirm = window.confirm; global.confirm = window.confirm;
@@ -181,6 +197,7 @@ afterEach(() => {
delete window.__matchMediaMocks; delete window.__matchMediaMocks;
} }
container = null; container = null;
nativeGetComputedStyle = null;
}); });
function normalizeTestTag(value) { function normalizeTestTag(value) {
@@ -1062,6 +1079,52 @@ test('load() injects markup and caches expected elements', async () => {
assert.ok(controller.walletController.walletStatusText instanceof window.HTMLElement); assert.ok(controller.walletController.walletStatusText instanceof window.HTMLElement);
}); });
test('focus trap excludes hidden and disabled controls with fixture and native styles', () => {
const panel = document.createElement('div');
container.appendChild(panel);
const addControl = (id, configure = () => {}) => {
const control = document.createElement('input');
control.id = id;
configure(control);
panel.appendChild(control);
return control;
};
const visible = addControl('visible');
addControl('disabled', (control) => { control.disabled = true; });
addControl('display-none', (control) => { control.classList.add('focus-test-display-none'); });
addControl('visibility-hidden', (control) => { control.classList.add('focus-test-visibility-hidden'); });
addControl('aria-hidden', (control) => { control.setAttribute('aria-hidden', 'true'); });
addControl('hidden-input', (control) => { control.type = 'hidden'; });
addControl('hidden-attribute', (control) => { control.hidden = true; });
const inherited = addControl('inherited-visibility');
const hiddenParent = document.createElement('div');
hiddenParent.classList.add('focus-test-visibility-hidden');
panel.appendChild(hiddenParent);
hiddenParent.appendChild(inherited);
const last = addControl('last');
const controller = createController();
controller.profileModalPanel = panel;
const fixtureGetComputedStyle = window.getComputedStyle;
try {
for (const resolveStyle of [fixtureGetComputedStyle, nativeGetComputedStyle]) {
window.getComputedStyle = resolveStyle;
controller.updateFocusTrap();
assert.deepEqual(controller.focusableElements.map((control) => control.id), ['visible', 'last']);
last.focus();
const tab = new window.KeyboardEvent('keydown', {
key: 'Tab', bubbles: true, cancelable: true,
});
last.dispatchEvent(tab);
assert.equal(tab.defaultPrevented, true);
assert.strictEqual(document.activeElement, visible);
}
} finally {
window.getComputedStyle = fixtureGetComputedStyle;
controller.hide({ silent: true });
}
});
test('show()/hide() toggle panes, trap focus, and refresh the wallet pane', async () => { test('show()/hide() toggle panes, trap focus, and refresh the wallet pane', async () => {
const controller = createController(); const controller = createController();
await controller.load(); await controller.load();
+72 -30
View File
@@ -1,4 +1,3 @@
import "../test-helpers/setup-localstorage.mjs"; import "../test-helpers/setup-localstorage.mjs";
import assert from "node:assert/strict"; import assert from "node:assert/strict";
import test from "node:test"; import test from "node:test";
@@ -6,54 +5,97 @@ import { NostrClient } from "../../js/nostr/client.js";
// Mock global.window if missing // Mock global.window if missing
if (typeof globalThis.window === "undefined") { if (typeof globalThis.window === "undefined") {
globalThis.window = {}; globalThis.window = {};
}
function deferred() {
let resolve;
const promise = new Promise((done) => { resolve = done; });
return { promise, resolve };
} }
test("NostrClient initialization race condition: concurrent init() calls", async (t) => { test("NostrClient initialization race condition: concurrent init() calls", async (t) => {
const client = new NostrClient(); const client = new NostrClient();
const relay = "wss://race.example.invalid";
// Mock ensurePool to simulate a slow initialization client.relays = [relay];
const poolEntered = deferred();
const releasePool = deferred();
let poolReady = false; let poolReady = false;
let subscription;
let initPromiseA;
const subscriptions = [];
// We mock connectionManager.ensurePool via the instance // Teardown runs even if an assertion fails before init or subscription setup.
client.connectionManager.ensurePool = async () => { t.after(async () => {
// Delay to simulate async work releasePool.resolve();
await new Promise(resolve => setTimeout(resolve, 50)); try {
await initPromiseA;
} finally {
subscription?.unsub();
client.resetRelayReconnectState();
}
});
// Create a fake pool // Virtualize the real pool transport boundary; keep init, L1 subscription
const fakePool = { // management and the returned feed subscription's cleanup real.
sub: () => ({ on: () => {}, unsub: () => {} }), const fakePool = {
get: async () => null, sub: (relays, filters) => {
list: async () => [], assert.equal(poolReady, true, "Subscription must use the initialized pool");
ensureRelay: async () => {}, const record = { relays, filters, closed: 0 };
}; subscriptions.push(record);
return {
on: () => {},
unsub: () => { record.closed += 1; },
};
},
get: async () => null,
list: async () => [],
ensureRelay: async () => {},
};
t.mock.method(client.connectionManager, "ensurePool", async () => {
poolEntered.resolve();
await releasePool.promise;
client.connectionManager.pool = fakePool; client.connectionManager.pool = fakePool;
poolReady = true; poolReady = true;
return fakePool; return fakePool;
}; });
// An empty result means connection failure and starts real reconnect timers.
// Model a successful connection at the manager boundary used by init/retry.
t.mock.method(client.connectionManager, "connectToRelays", async () => [
{ url: relay, success: true },
]);
t.mock.method(client, "restoreLocalData", async () => true);
t.mock.method(client, "scheduleStoredRemoteSignerRestore", async () => {});
// Also mock connectToRelays to avoid network calls initPromiseA = client.init();
client.connectToRelays = async () => [];
client.restoreLocalData = async () => true;
// ACT: Call init() (Caller A)
const initPromiseA = client.init();
// ACT: Call init() again immediately (Caller B)
const initPromiseB = client.init(); const initPromiseB = client.init();
// ASSERT: Caller B should receive a promise that resolves when initialization is complete.
// In the buggy version, initPromiseB is undefined because it returns early.
assert.ok(initPromiseB instanceof Promise, "Second init() call should return a Promise, not undefined"); assert.ok(initPromiseB instanceof Promise, "Second init() call should return a Promise, not undefined");
await poolEntered.promise;
let secondInitSettled = false;
void initPromiseB.then(() => { secondInitSettled = true; });
await Promise.resolve();
assert.equal(secondInitSettled, false, "Second caller must wait for pool initialization");
releasePool.resolve();
await initPromiseB; await initPromiseB;
assert.equal(poolReady, true, "Pool should be ready after awaiting second init() call"); assert.equal(poolReady, true, "Pool should be ready after awaiting second init() call");
await initPromiseA;
assert.equal(client.connectionManager.relayReconnectTimer, null, "Successful fixture initialization must not schedule relay retries");
// Verify we can use the client
try { try {
client.subscribeVideos(() => {}); subscription = client.subscribeVideos(() => {});
} catch (e) { } catch (e) {
assert.fail("Should be able to subscribe after awaiting init()"); assert.fail("Should be able to subscribe after awaiting init()");
} }
assert.equal(subscriptions.length, 1, "Post-init feed subscription must reach the pool transport");
assert.ok(subscriptions[0].relays.length > 0, "Feed must subscribe to a nonempty relay set");
assert.ok(subscriptions[0].filters.some((filter) =>
filter.kinds.includes(30078) && filter["#t"].includes("video")),
"Feed must request video events");
subscription.unsub();
assert.equal(subscriptions[0].closed, 1, "Feed cleanup must close its transport subscription");
subscription.unsub();
assert.equal(subscriptions[0].closed, 1, "Feed cleanup must be idempotent");
}); });
@@ -0,0 +1,6 @@
- [x] Read rules, mailbox, known issues and integrity standard; claim client-reliability-1.
- [ ] Install worktree dependencies and reproduce at actual boundary.
- [ ] Diagnose and make minimal deterministic fix; preserve expectations.
- [ ] Prove scenario regression with mutant and run required checks.
- [ ] Commit, merge unstable, validate, submit, verify landing and close.
- GitHub overlap check failed DNS; no PR being opened. Fleet reserves this scope.
@@ -0,0 +1,6 @@
- [x] Claim/revalidate client-reliability-1 at startup HEAD9e6c310d.
- [x] Classify timeout as expensive JSDOM computed styles; baseline26/26 natural completion57.04s.
- [x] Implement test-only fixture; preserve all assertions; native-JSDOM contract and two failing mutants.
- [x] Commit, merge unstable and verify isolated27/27 and lint11/11 on dbdac18d.
- [ ] Full unit gate:226 files passed; file227 Nostr-client race timed out60s;198 untested.
- [ ] Dispatch routing of unowned race-suite blocker, then normal full gate, queue submission, durable landing and close.
@@ -0,0 +1,6 @@
- [x] Read routing/mailbox/rules, merge unstable, claim client-reliability-2, revalidate unchanged test against filedAt.
- [x] Read NIP01 and subscription/lifecycle docs, classify manager retry escape and missing unsubscribe.
- [x] Add hermetic fixture, deferred pool barrier and real subscription/close assertions without relaxing existing checks.
- [x] Prove test-local early-return and failure-connection mutants each fail naturally.
- [ ] Run both isolated files, lint, commit separately, merge unstable and run full60s/file gate.
- [ ] Submit normal landing queue; verify durable state and unstable; close both backlog items after landing.
@@ -0,0 +1,5 @@
- [x] client-reliability-1: preserve26 existing scenarios, add native style contract, mutation checks, profile27/27 natural exit.
- [x] client-reliability-2: deterministic pool barrier, successful IO fixture, subscription cleanup, preserve original assertions; race1/1 natural exit and both mutants fail.
- [x] Separate commits, merge unstable, lint11/11 and full425/425 file exits0 on181eae95.
- [ ] Unchanged normal queue gate and durable landing; verify unstable ancestry, then close both backlog items.
- Not verified: browser/build/application layout, real network/signers/wallets/production/deploy; test-only changes.