mirror of
https://github.com/block/buzz.git
synced 2026-11-01 15:01:12 +00:00
## Summary - align the multi-community rail with the content surface and balance its visible 10px side gutters - center Mobile pairing, start sessions on demand, and keep retry states inside the QR area - reveal the QR code and copy action with 250ms motion and use the standard loading spinner The rail was centered within its own box, but the adjacent sidebar added another 11px to the visible right gap. Mobile pairing also started before user intent, which could leave an idle session waiting for EOSE. ## Validation - `pnpm -C desktop build:e2e` - `pnpm -C desktop test` — 3,516 passed - `pnpm -C desktop exec playwright test tests/e2e/community-rail.spec.ts --project=smoke` — 19 passed - `pnpm -C desktop exec playwright test tests/e2e/mobile-pairing-qr.spec.ts --project=smoke` — 1 passed `pnpm -C desktop check` is currently blocked by the existing `src-tauri/src/managed_agents/runtime.rs` file-size baseline (2,220 lines; limit 2,216).
133 lines
4.1 KiB
TypeScript
133 lines
4.1 KiB
TypeScript
/**
|
|
* Gate-aware relay operation boundaries.
|
|
*
|
|
* History REQ operations are issued here rather than inline in RelayClient so
|
|
* the gate-await + op-timeout pattern lives in one place and the op timeout
|
|
* budget starts only after the rate-limit window has cleared.
|
|
*/
|
|
import { collectWithConcurrency } from "@/shared/api/concurrency";
|
|
import { AUX_BACKFILL_CHUNK_SIZE } from "@/shared/api/relayChannelFilters";
|
|
import { waitForRateLimit } from "@/shared/api/relayRateLimitGate";
|
|
import type {
|
|
RelaySubscription,
|
|
RelaySubscriptionFilter,
|
|
} from "@/shared/api/relayClientShared";
|
|
import type { RelayEvent } from "@/shared/api/types";
|
|
|
|
const AUX_BACKFILL_CONCURRENCY = 4;
|
|
|
|
/**
|
|
* Issue a history REQ on `filter`, waiting for any active rate-limit gate
|
|
* before starting the subscription so the op timeout begins only after
|
|
* back-pressure has cleared.
|
|
*/
|
|
export async function requestHistoryGated(
|
|
subscriptions: Map<string, RelaySubscription>,
|
|
sendRaw: (payload: unknown[]) => Promise<void>,
|
|
closeSubscription: (subId: string) => Promise<void>,
|
|
filter: RelaySubscriptionFilter,
|
|
historyTimeoutMs: number,
|
|
): Promise<RelayEvent[]> {
|
|
// Await the gate before issuing REQ; op timeout starts after the wait.
|
|
await waitForRateLimit();
|
|
|
|
return new Promise<RelayEvent[]>((resolve, reject) => {
|
|
const subId = `history-${crypto.randomUUID()}`;
|
|
const timeout = window.setTimeout(() => {
|
|
subscriptions.delete(subId);
|
|
void closeSubscription(subId);
|
|
reject(new Error("Timed out while loading channel history."));
|
|
}, historyTimeoutMs);
|
|
|
|
subscriptions.set(subId, {
|
|
mode: "history",
|
|
events: [],
|
|
resolve,
|
|
reject,
|
|
timeout,
|
|
});
|
|
|
|
void sendRaw(["REQ", subId, filter]).catch((error) => {
|
|
window.clearTimeout(timeout);
|
|
subscriptions.delete(subId);
|
|
reject(
|
|
error instanceof Error
|
|
? error
|
|
: new Error("Failed to request channel history."),
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Issue a REQ that resolves as soon as its first matching event arrives.
|
|
*
|
|
* This keeps single-event lookups from waiting for EOSE while still resolving
|
|
* `null` when the relay completes an empty result set.
|
|
*/
|
|
export async function requestFirstEventGated(
|
|
subscriptions: Map<string, RelaySubscription>,
|
|
sendRaw: (payload: unknown[]) => Promise<void>,
|
|
closeSubscription: (subId: string) => Promise<void>,
|
|
filter: RelaySubscriptionFilter,
|
|
historyTimeoutMs: number,
|
|
): Promise<RelayEvent | null> {
|
|
await waitForRateLimit();
|
|
|
|
return new Promise<RelayEvent | null>((resolve, reject) => {
|
|
const subId = `first-${crypto.randomUUID()}`;
|
|
const timeout = window.setTimeout(() => {
|
|
subscriptions.delete(subId);
|
|
void closeSubscription(subId);
|
|
reject(new Error("Timed out while loading relay event."));
|
|
}, historyTimeoutMs);
|
|
|
|
subscriptions.set(subId, {
|
|
mode: "first",
|
|
onEvent: (event) => {
|
|
window.clearTimeout(timeout);
|
|
subscriptions.delete(subId);
|
|
void closeSubscription(subId);
|
|
resolve(event);
|
|
},
|
|
resolve,
|
|
reject,
|
|
timeout,
|
|
});
|
|
|
|
void sendRaw(["REQ", subId, filter]).catch((error) => {
|
|
window.clearTimeout(timeout);
|
|
subscriptions.delete(subId);
|
|
reject(
|
|
error instanceof Error
|
|
? error
|
|
: new Error("Failed to request relay event."),
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fetch relay history for event IDs in bounded, concurrent chunks and flatten
|
|
* the responses into one list. Returns an empty list without issuing a request
|
|
* when `eventIds` is empty.
|
|
*/
|
|
export async function fetchChunkedHistory(
|
|
eventIds: string[],
|
|
buildFilter: (eventIds: string[]) => RelaySubscriptionFilter,
|
|
fetchHistory: (filter: RelaySubscriptionFilter) => Promise<RelayEvent[]>,
|
|
): Promise<RelayEvent[]> {
|
|
if (eventIds.length === 0) return [];
|
|
|
|
const chunks: string[][] = [];
|
|
for (let i = 0; i < eventIds.length; i += AUX_BACKFILL_CHUNK_SIZE) {
|
|
chunks.push(eventIds.slice(i, i + AUX_BACKFILL_CHUNK_SIZE));
|
|
}
|
|
const batches = await collectWithConcurrency(
|
|
chunks,
|
|
AUX_BACKFILL_CONCURRENCY,
|
|
(ids) => fetchHistory(buildFilter(ids)),
|
|
);
|
|
return batches.flat();
|
|
}
|