harden explicit click zoom suggestions

This commit is contained in:
webadderall
2026-05-02 20:38:07 +10:00
parent 676d01827f
commit dad073ebed
2 changed files with 39 additions and 9 deletions
@@ -6,8 +6,13 @@ import {
} from "./zoomSuggestionUtils";
import type { CursorTelemetryPoint } from "../types";
function makeClick(timeMs: number, cx = 0.5, cy = 0.5): CursorTelemetryPoint {
return { timeMs, cx, cy, interactionType: "click" };
function makeClick(
timeMs: number,
cx = 0.5,
cy = 0.5,
interactionType: CursorTelemetryPoint["interactionType"] = "click",
): CursorTelemetryPoint {
return { timeMs, cx, cy, interactionType };
}
function makeMove(timeMs: number, cx = 0.5, cy = 0.5): CursorTelemetryPoint {
@@ -57,6 +62,24 @@ describe("buildInteractionZoomSuggestions (click-cluster logic)", () => {
expect(result.suggestions).toHaveLength(1);
});
it.each(["right-click", "middle-click"] as const)(
"accepts %s telemetry like a standard click",
(interactionType) => {
const result = buildInteractionZoomSuggestions({
cursorTelemetry: withMoves([makeClick(5_000, 0.5, 0.5, interactionType)], TOTAL_MS),
totalMs: TOTAL_MS,
defaultDurationMs: 3_000,
});
expect(result.status).toBe("ok");
expect(result.suggestions).toHaveLength(1);
const [suggestion] = result.suggestions;
expect(suggestion.start).toBe(5_000 - CLICK_CLUSTER_PAD_MS);
expect(suggestion.end).toBe(5_000 + CLICK_CLUSTER_PAD_MS);
},
);
it("merges two clicks within 2500ms into one zoom track", () => {
const telemetry = withMoves(
[makeClick(4_000), makeClick(4_000 + CLICK_CLUSTER_MERGE_GAP_MS - 1)],
@@ -43,6 +43,18 @@ export interface InteractionZoomSuggestionResult {
export const CLICK_CLUSTER_MERGE_GAP_MS = 2500;
/** Padding added before the first click and after the last click in a cluster. */
export const CLICK_CLUSTER_PAD_MS = 500;
const EXPLICIT_CLICK_TYPES = new Set<NonNullable<CursorTelemetryPoint["interactionType"]>>([
"click",
"double-click",
"right-click",
"middle-click",
]);
function isExplicitClickType(
interactionType: CursorTelemetryPoint["interactionType"],
): interactionType is NonNullable<CursorTelemetryPoint["interactionType"]> {
return typeof interactionType === "string" && EXPLICIT_CLICK_TYPES.has(interactionType);
}
function normalizeTelemetrySample(
sample: CursorTelemetryPoint,
@@ -188,9 +200,7 @@ export function detectInteractionCandidates(
samples: CursorTelemetryPoint[],
): CursorInteractionCandidate[] {
// --- Phase 1: Explicit interaction events (from uiohook telemetry) ---
const clickEvents = samples.filter(
(s) => s.interactionType && s.interactionType !== "move" && s.interactionType !== "mouseup",
);
const clickEvents = samples.filter((sample) => isExplicitClickType(sample.interactionType));
const explicitInteractionCandidates: CursorInteractionCandidate[] = [];
@@ -354,10 +364,7 @@ export function buildInteractionZoomSuggestions(params: {
if (
normalizedSamples.length === 1 &&
normalizedSamples[0].interactionType !== "click" &&
normalizedSamples[0].interactionType !== "double-click" &&
normalizedSamples[0].interactionType !== "right-click" &&
normalizedSamples[0].interactionType !== "middle-click"
!isExplicitClickType(normalizedSamples[0].interactionType)
) {
return { status: "no-telemetry", suggestions: [] };
}