fix(desktop): repair e2e bridge for native unread and persona catalog

Teach the E2E Tauri mock the pack's new renderer<->backend contracts and
fix two production seams the repaired suites then exposed:

- Stateful observed-unread mock: scope open/ingest now derive real
  per-channel projections (count, badgeCount, appBadgeCount,
  topLevelUnread, highPriorityUnread) with monotonic channel_latest
  anchors, mirroring observed_unread.rs instead of returning empty rows.
- Persona catalog mock: add the fetch_persona_catalog case, with
  validation mirroring the Rust validator's emoji semantics (FE0F/ZWJ).
- unread_catch_up mock mirrors Rust authored-root discovery:
  self-authored top-level events are returned as discovered.authored so
  thread-activity membership matches the native contract; replies stay
  excluded.
- Production: suppress the onPruned notification for true empty
  projection deltas, breaking a marker-ingest -> no-op delta -> notify ->
  re-render -> re-ingest feedback loop that saturated the main thread
  (ingest sequence doubled to 8192 within ~5s). Snapshot and
  snapshotRequired paths still notify unconditionally; regression tests
  cover both the no-op suppression and snapshot recovery.
- Production: the desktop app-dot fallback now reads
  topLevelUnreadChannelIds, so thread-preview-only unread no longer
  lights the app badge while the sidebar thread indicators still do.

Marker ingests for genuine projection changes remain fire-per-effect-run;
this is bounded now that the no-op cycle is broken.

Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
This commit is contained in:
Wren
2026-08-16 10:44:53 -04:00
committed by Eva
co-authored by Tyler Longwell
parent b955f528b6
commit 625ee7b72a
5 changed files with 445 additions and 24 deletions
+1 -1
View File
@@ -641,7 +641,7 @@ export function AppShell() {
useAppShellLifecycleEffects({
desktopBadgeEnabled: !isHuddleRoom,
homeBadgeCountExcludingHighPriority,
unreadChannelIds,
topLevelUnreadChannelIds,
unreadChannelNotificationCount,
});
// Dispatch `buzz://` deep links only from the main window; the companion is dedicated to its active Huddle route.
@@ -8,14 +8,14 @@ import { useRelayResumeTriggers } from "@/shared/api/useRelayResumeTriggers";
type AppShellLifecycleEffectsOptions = {
desktopBadgeEnabled: boolean;
homeBadgeCountExcludingHighPriority: number;
unreadChannelIds: ReadonlySet<string>;
topLevelUnreadChannelIds: ReadonlySet<string>;
unreadChannelNotificationCount: number;
};
export function useAppShellLifecycleEffects({
desktopBadgeEnabled,
homeBadgeCountExcludingHighPriority,
unreadChannelIds,
topLevelUnreadChannelIds,
unreadChannelNotificationCount,
}: AppShellLifecycleEffectsOptions) {
// Event-driven reconnect: network online / focus / visibility short-circuit
@@ -82,12 +82,12 @@ export function useAppShellLifecycleEffects({
void setDesktopAppBadge(
count
? { kind: "count", count }
: { kind: unreadChannelIds.size ? "dot" : "none" },
: { kind: topLevelUnreadChannelIds.size ? "dot" : "none" },
);
}, [
desktopBadgeEnabled,
homeBadgeCountExcludingHighPriority,
unreadChannelIds,
topLevelUnreadChannelIds,
unreadChannelNotificationCount,
]);
}
@@ -52,6 +52,86 @@ async function settle() {
});
}
test("native no-op marker delta does not notify the renderer", async () => {
installFreshStorage();
let harness;
let notifications = 0;
const rig = installNativeRig();
try {
harness = await mountHook(
{
...DEFAULT_PROPS,
pubkey: "pk-no-op-marker",
getTs: () => NOW_S,
onPruned: () => {
notifications += 1;
},
},
makeRefs(),
);
await settle();
assert.equal(notifications, 1, "opening the native snapshot notifies once");
harness.api.syncMarkers(["channel-empty"]);
await settle();
assert.equal(
rig.requests("observed_unread_ingest").length,
1,
"the marker must still advance the native revision and ack sequence",
);
assert.equal(
notifications,
1,
"an empty projection delta must not trigger a renderer feedback render",
);
} finally {
await harness?.unmount();
rig.restore();
}
});
test("native snapshotRequired response still reopens and notifies", async () => {
installFreshStorage();
let harness;
let notifications = 0;
const scope = { pubkey: "pk-snapshot-required", relayUrl: RELAY };
const rig = installNativeRig();
try {
harness = await mountHook(
{
...DEFAULT_PROPS,
pubkey: scope.pubkey,
getTs: () => NOW_S,
onPruned: () => {
notifications += 1;
},
},
makeRefs(),
);
await settle();
rig.scope(scope).lastSequence = -1;
harness.api.syncMarkers(["channel-gap"]);
await settle();
await settle();
assert.equal(
rig.requests("observed_unread_open_scope").length,
2,
"a sequence gap must reopen the scope even when it carries no projection rows",
);
assert.equal(
notifications,
2,
"the replacement snapshot must still notify the renderer",
);
} finally {
await harness?.unmount();
rig.restore();
}
});
// ── Entry: the boundary that makes every other test meaningful ────────────────
test("native mode is ENTERED: the hook opens the scope over the bridge", async () => {
@@ -170,7 +170,9 @@ export function useObservedUnreadPersistence(
revision: response.revision,
sequence: response.ackedSequence,
};
optionsRef.current.onPruned?.();
if (response.removed.length > 0 || response.upserts.length > 0) {
optionsRef.current.onPruned?.();
}
},
[reopen],
);
+357 -18
View File
@@ -10,6 +10,10 @@ import {
handleDeleteCustomHarness,
} from "./e2eBridgeCustomHarnesses.ts";
import type {
ObservedUnreadProjection,
ObservedUnreadResponse,
} from "@/shared/api/tauriObservedUnread";
import type { UnreadCatchUpChannelResult } from "@/shared/api/tauriUnreadCatchUp";
import { relayClient } from "@/shared/api/relayClient";
import { activateRateLimit } from "@/shared/api/relayRateLimitGate";
@@ -3078,6 +3082,111 @@ type MockSaveSubscriptionRow = {
};
let mockSaveSubscriptions: MockSaveSubscriptionRow[] = [];
type MockObservedUnreadScope = {
generation: string;
revision: number;
lastSequence: number;
migrationComplete: boolean;
events: Map<
string,
{
channelId: string;
id: string;
createdAt: number;
rootId: string | null;
highPriority: boolean;
countsTowardBadge: boolean;
countsTowardAppBadge: boolean;
}
>;
channelLatest: Map<string, number>;
markers: Map<string, number>;
};
const mockObservedUnreadScopes = new Map<string, MockObservedUnreadScope>();
function mockObservedUnreadScopeKey(scope: {
pubkey: string;
relayUrl: string;
}) {
return `${scope.pubkey.trim().toLowerCase()}:${scope.relayUrl
.trim()
.replace(/\/+$/, "")}`;
}
function getMockObservedUnreadScope(scope: {
pubkey: string;
relayUrl: string;
}) {
const key = mockObservedUnreadScopeKey(scope);
const existing = mockObservedUnreadScopes.get(key);
if (existing) return existing;
const created: MockObservedUnreadScope = {
generation: "e2e",
revision: 0,
lastSequence: 0,
migrationComplete: false,
events: new Map(),
channelLatest: new Map(),
markers: new Map(),
};
mockObservedUnreadScopes.set(key, created);
return created;
}
function mockObservedUnreadProjections(
scope: MockObservedUnreadScope,
): ObservedUnreadProjection[] {
const channels = new Map<string, ObservedUnreadProjection>();
for (const [channelId, latest] of scope.channelLatest) {
channels.set(channelId, {
channelId,
latest,
count: 0,
badgeCount: 0,
appBadgeCount: 0,
topLevelUnread: false,
highPriorityUnread: false,
});
}
for (const event of scope.events.values()) {
let readAt = Math.max(
scope.markers.get(event.channelId) ?? 0,
scope.markers.get(`msg:${event.id}`) ?? 0,
);
if (event.rootId) {
readAt = Math.max(
readAt,
scope.markers.get(`thread:${event.rootId}`) ?? 0,
);
}
if (event.createdAt <= readAt) continue;
const projection = channels.get(event.channelId) ?? {
channelId: event.channelId,
latest: 0,
count: 0,
badgeCount: 0,
appBadgeCount: 0,
topLevelUnread: false,
highPriorityUnread: false,
};
projection.latest = Math.max(projection.latest, event.createdAt);
projection.count += 1;
projection.badgeCount += event.countsTowardBadge ? 1 : 0;
projection.appBadgeCount += event.countsTowardAppBadge ? 1 : 0;
projection.topLevelUnread ||= event.rootId === null;
projection.highPriorityUnread ||= event.highPriority;
channels.set(event.channelId, projection);
}
return [...channels.values()].sort((left, right) =>
left.channelId.localeCompare(right.channelId),
);
}
function resetMockObservedUnread() {
mockObservedUnreadScopes.clear();
}
function resetMockSaveSubscriptions(config: E2eConfig | undefined) {
mockSaveSubscriptions = (config?.mock?.saveSubscriptions ?? []).map((s) => ({
...s,
@@ -3094,6 +3203,123 @@ function resetMockPersonaCatalogEvents(config: E2eConfig | undefined) {
}
}
function mockPersonaCatalogPublications() {
const publications = [];
const claimed = new Set<string>();
for (const event of [...mockPersonaEvents].sort(
(a, b) => b.created_at - a.created_at || a.id.localeCompare(b.id),
)) {
const dTags = event.tags.filter((tag) => tag[0] === "d");
if (dTags.length !== 1 || !dTags[0]?.[1]) continue;
const sourcePersonaId = dTags[0][1];
const ownerPubkey = event.pubkey.toLowerCase();
const coordinate = `${ownerPubkey}:${sourcePersonaId}`;
if (claimed.has(coordinate)) continue;
claimed.add(coordinate);
if (!personaHasExactSharedTag(event)) continue;
let content: Record<string, unknown>;
try {
content = JSON.parse(event.content) as Record<string, unknown>;
} catch {
continue;
}
const displayName = content.display_name;
const systemPrompt = content.system_prompt ?? "";
const optionalString = (value: unknown) =>
typeof value === "string" && value.trim() ? value : null;
const isExtendedPictographic = (value: string) =>
/^\p{Extended_Pictographic}$/u.test(value);
const hasValidVisibleText = (value: string, allowLayout: boolean) => {
const characters = [...value];
return characters.every((character, index) => {
if (allowLayout && (character === "\n" || character === "\t"))
return true;
if (/\p{Control}/u.test(character)) return false;
const codepoint = character.codePointAt(0) ?? 0;
const defaultIgnorable =
codepoint === 0x00ad ||
codepoint === 0x034f ||
codepoint === 0x061c ||
(codepoint >= 0x115f && codepoint <= 0x1160) ||
(codepoint >= 0x17b4 && codepoint <= 0x17b5) ||
(codepoint >= 0x180b && codepoint <= 0x180f) ||
(codepoint >= 0x200b && codepoint <= 0x200f) ||
(codepoint >= 0x202a && codepoint <= 0x202e) ||
(codepoint >= 0x2060 && codepoint <= 0x206f) ||
codepoint === 0x3164 ||
(codepoint >= 0xfe00 && codepoint <= 0xfe0f) ||
codepoint === 0xfeff ||
codepoint === 0xffa0 ||
(codepoint >= 0xfff0 && codepoint <= 0xfff8) ||
(codepoint >= 0x1bca0 && codepoint <= 0x1bca3) ||
(codepoint >= 0x1d173 && codepoint <= 0x1d17a) ||
(codepoint >= 0xe0000 && codepoint <= 0xe0fff);
if (!defaultIgnorable) return true;
if (character === "\ufe0f") {
const previous = characters[index - 1];
return (
previous !== undefined &&
(/^[#*0-9]$/u.test(previous) || isExtendedPictographic(previous))
);
}
if (character !== "\u200d") return false;
let previousIndex = index - 1;
while (
previousIndex >= 0 &&
(characters[previousIndex] === "\ufe0f" ||
/[\u{1f3fb}-\u{1f3ff}]/u.test(characters[previousIndex] ?? ""))
) {
previousIndex -= 1;
}
return (
previousIndex >= 0 &&
isExtendedPictographic(characters[previousIndex] ?? "") &&
isExtendedPictographic(characters[index + 1] ?? "")
);
});
};
if (
typeof displayName !== "string" ||
!displayName.trim() ||
[...displayName].length > 128 ||
typeof systemPrompt !== "string" ||
new TextEncoder().encode(systemPrompt).length > 64 * 1024 ||
!hasValidVisibleText(displayName, false) ||
!hasValidVisibleText(systemPrompt, true)
)
continue;
publications.push({
eventId: event.id,
ownerPubkey,
sourcePersonaId,
createdAt: event.created_at,
agent: {
displayName,
avatarUrl: optionalString(content.avatar_url),
systemPrompt,
runtime: optionalString(content.runtime),
model: optionalString(content.model),
provider: optionalString(content.provider),
namePool: Array.isArray(content.name_pool)
? content.name_pool.filter(
(value): value is string => typeof value === "string",
)
: [],
respondTo:
content.respond_to === "allowlist"
? "owner-only"
: content.respond_to === "owner-only" ||
content.respond_to === "anyone"
? content.respond_to
: null,
parallelism:
typeof content.parallelism === "number" ? content.parallelism : null,
},
});
}
return publications;
}
// Mesh-compute mock state — TEST-ONLY.
//
// This entire module (e2eBridge.ts) is loaded only when `window.__BUZZ_E2E__`
@@ -10267,6 +10493,7 @@ export function maybeInstallE2eTauriMocks() {
resetMockMesh();
resetMockUserStatuses();
resetMockPersonaCatalogEvents(config);
resetMockObservedUnread();
resetMockSaveSubscriptions(config);
resetMockPendingCommunityDeepLinks(config);
resetMockPendingNavigationDeepLinks(config);
@@ -13377,43 +13604,144 @@ export function maybeInstallE2eTauriMocks() {
case "start_archive_sync":
case "stop_archive_sync":
return null;
case "fetch_persona_catalog":
return mockPersonaCatalogPublications();
case "observed_unread_open_scope": {
const request = payload as {
request: { scope: { pubkey: string; relayUrl: string } };
request: {
scope: { pubkey: string; relayUrl: string };
legacyPayload?: {
eventsByChannel?: Record<
string,
Array<{
id: string;
createdAt: number;
rootId?: string | null;
highPriority: boolean;
countsTowardBadge: boolean;
countsTowardAppBadge: boolean;
}>
>;
};
};
};
const scope = getMockObservedUnreadScope(request.request.scope);
if (!scope.migrationComplete) {
for (const [channelId, events] of Object.entries(
request.request.legacyPayload?.eventsByChannel ?? {},
)) {
for (const event of events) {
if (!scope.events.has(event.id)) {
scope.events.set(event.id, {
channelId,
...event,
rootId: event.rootId ?? null,
});
}
}
}
scope.migrationComplete = true;
}
return {
kind: "snapshot",
scope: request.request.scope,
generation: "e2e",
revision: 0,
lastAckedSequence: 0,
migrationComplete: true,
generation: scope.generation,
revision: scope.revision,
lastAckedSequence: scope.lastSequence,
migrationComplete: scope.migrationComplete,
membershipSeeded: true,
channels: [],
};
channels: mockObservedUnreadProjections(scope),
} satisfies ObservedUnreadResponse;
}
case "observed_unread_ingest": {
const request = payload as {
const { request } = payload as {
request: {
scope: { pubkey: string; relayUrl: string };
sequence: number;
baseRevision: number;
events: Array<{
channelId: string;
id: string;
createdAt: number;
rootId: string | null;
highPriority: boolean;
countsTowardBadge: boolean;
countsTowardAppBadge: boolean;
}>;
channelLatest: Array<{ channelId: string; createdAt: number }>;
markers: Array<{ contextId: string; readAt: number | null }>;
membership: Array<{
kind: string;
value: string;
present: boolean;
}>;
clearChannels: string[];
clearAll: boolean;
};
};
const scope = getMockObservedUnreadScope(request.scope);
const before = new Map(
mockObservedUnreadProjections(scope).map((item) => [
item.channelId,
item,
]),
);
if (request.clearAll) {
scope.events.clear();
scope.channelLatest.clear();
}
for (const channelId of request.clearChannels) {
scope.channelLatest.delete(channelId);
for (const [id, event] of scope.events) {
if (event.channelId === channelId) scope.events.delete(id);
}
}
for (const latest of request.channelLatest ?? []) {
scope.channelLatest.set(
latest.channelId,
Math.max(
scope.channelLatest.get(latest.channelId) ?? 0,
latest.createdAt,
),
);
}
for (const event of request.events ?? []) {
if (!scope.events.has(event.id)) scope.events.set(event.id, event);
}
for (const marker of request.markers ?? []) {
if (marker.readAt === null) scope.markers.delete(marker.contextId);
else
scope.markers.set(
marker.contextId,
Math.max(scope.markers.get(marker.contextId) ?? 0, marker.readAt),
);
}
const after = mockObservedUnreadProjections(scope);
const afterIds = new Set(after.map((item) => item.channelId));
const baseRevision = scope.revision;
scope.revision += 1;
scope.lastSequence = request.sequence;
return {
kind: "delta",
scope: request.request.scope,
generation: "e2e",
baseRevision: request.request.baseRevision,
revision: request.request.baseRevision + 1,
ackedSequence: request.request.sequence,
upserts: [],
removed: [],
};
scope: request.scope,
generation: scope.generation,
baseRevision,
revision: scope.revision,
ackedSequence: request.sequence,
upserts: after.filter(
(item) =>
JSON.stringify(before.get(item.channelId)) !==
JSON.stringify(item),
),
removed: [...before.keys()].filter((id) => !afterIds.has(id)),
} satisfies ObservedUnreadResponse;
}
case "unread_catch_up": {
const request = payload as {
request: { channels: Array<{ id: string }> };
request: {
channels: Array<{ id: string }>;
selfPubkey: string;
};
};
const results: UnreadCatchUpChannelResult[] =
request.request.channels.map((channel) => ({
@@ -13422,7 +13750,18 @@ export function maybeInstallE2eTauriMocks() {
observedEvents: [],
maxTrigger: 0,
activityRows: [],
discovered: { participated: [], authored: [], mentioned: [] },
discovered: {
participated: [],
authored: getMockMessageStore(channel.id)
.filter(
(event) =>
event.pubkey === request.request.selfPubkey &&
getThreadReferenceFromTags(event.tags).parentEventId ===
null,
)
.map((event) => event.id),
mentioned: [],
},
}));
// Keep this mock aligned with the complete Rust serde shape pinned by
// `serialized_response_matches_the_typescript_contract`.