Files
buzz/desktop/src/app/navigation/commitGuardedNavigation.ts
Max Lampert 6e9942f525 fix(desktop): guard the final paint frame; trace DM entry paths
Three review findings against the tracer's honesty contract:

- The readiness -> paint frame was unguarded. `awaitDeferredCommit` checks
  age, frame gap, and visibility on every frame it drives, but the one rAF
  it queues before `record()` checked neither age nor gap, so a suspension
  in that seam (App Nap fires no visibilitychange) recorded the whole
  absence as a clean switch.
- DM actions await `open_dm` before `goChannel`, so that relay round-trip
  sat outside the measurement; Pulse's note actions used raw `navigate` and
  produced no measurement at all. Callers now capture the click anchor and
  hand it to `goChannel`, and Pulse routes through `goChannel`.
- A `navigate()` rejection left the trace it opened active, so an untraced
  re-entry could settle the failed attempt. Cancellation is by trace
  identity, so a newer same-channel attempt is never erased.

Signed-off-by: Max Lampert <maxwell@squareup.com>
2026-08-26 15:31:32 -07:00

78 lines
2.7 KiB
TypeScript

import {
allowNavigation,
type GuardedNavigation,
} from "@/app/navigation/navigationGuard";
import {
beginChannelSwitchTrace,
cancelChannelSwitchTrace,
dropActiveChannelSwitchTrace,
} from "@/shared/lib/channelSwitchPerf";
/**
* commitGuardedNavigation runs the shared commit flow for app navigations:
* skip same-destination no-ops, consult the navigation guard, then navigate.
* `force` and `hasStateUpdate` both defeat the no-op skip — a same-href
* navigation that writes router state (setting or clearing the search
* highlight) must commit, or the state never lands. When `traceChannelId` is
* set, the channel-switch trace opens only after the guard accepts — a
* refused click must not leave an orphan trace that a later history
* navigation (deliberately untraced) would settle with the refused click's
* inflated wall time. When `leavesChannelSurface` is set, any active trace is
* dropped instead: the trace may be live with no channel screen mounted
* (route still resolving), so this is the only reliable exit hook. A
* `navigate()` rejection cancels the trace this call opened — by identity, so
* a newer same-channel attempt is never erased — because no destination
* committed and an untraced re-entry would otherwise settle the failed
* attempt. Returns whether the navigation was performed. `deps` exists for
* unit tests.
*/
export async function commitGuardedNavigation(
input: {
currentHref: string;
nextHref: string;
force?: boolean;
guardedTarget: GuardedNavigation;
hasStateUpdate?: boolean;
leavesChannelSurface?: boolean;
traceChannelId?: string;
/** Click-time anchor for callers that await before `goChannel`. */
traceStartedAt?: number;
navigate: () => Promise<unknown>;
},
deps: {
allow?: typeof allowNavigation;
beginTrace?: typeof beginChannelSwitchTrace;
cancelTrace?: typeof cancelChannelSwitchTrace;
dropActiveTrace?: typeof dropActiveChannelSwitchTrace;
} = {},
): Promise<boolean> {
const allow = deps.allow ?? allowNavigation;
const beginTrace = deps.beginTrace ?? beginChannelSwitchTrace;
const cancelTrace = deps.cancelTrace ?? cancelChannelSwitchTrace;
const dropActiveTrace = deps.dropActiveTrace ?? dropActiveChannelSwitchTrace;
if (
input.currentHref === input.nextHref &&
!input.force &&
!input.hasStateUpdate
) {
return false;
}
if (!allow(input.guardedTarget)) {
return false;
}
if (input.leavesChannelSurface) {
dropActiveTrace();
}
let handle = null;
if (input.traceChannelId !== undefined) {
handle = beginTrace(input.traceChannelId, input.traceStartedAt) ?? null;
}
try {
await input.navigate();
} catch (error) {
cancelTrace(handle);
throw error;
}
return true;
}