mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-26 07:45:34 +00:00
tighten click-cluster zoom suggestions
This commit is contained in:
@@ -10,16 +10,19 @@ function makeClick(timeMs: number, cx = 0.5, cy = 0.5): CursorTelemetryPoint {
|
||||
return { timeMs, cx, cy, interactionType: "click" };
|
||||
}
|
||||
|
||||
/** Wraps a list of click samples with surrounding move events so the telemetry
|
||||
* passes the `normalizedSamples.length < 2` guard. */
|
||||
function makeMove(timeMs: number, cx = 0.5, cy = 0.5): CursorTelemetryPoint {
|
||||
return { timeMs, cx, cy, interactionType: "move" };
|
||||
}
|
||||
|
||||
/** Wraps click samples with surrounding move events to mimic real mixed telemetry. */
|
||||
function withMoves(
|
||||
clicks: CursorTelemetryPoint[],
|
||||
totalMs: number,
|
||||
): CursorTelemetryPoint[] {
|
||||
return [
|
||||
{ timeMs: 0, cx: 0.5, cy: 0.5, interactionType: "move" },
|
||||
makeMove(0),
|
||||
...clicks,
|
||||
{ timeMs: totalMs, cx: 0.5, cy: 0.5, interactionType: "move" },
|
||||
makeMove(totalMs),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -43,6 +46,17 @@ describe("buildInteractionZoomSuggestions (click-cluster logic)", () => {
|
||||
expect(s.end).toBe(5_000 + CLICK_CLUSTER_PAD_MS);
|
||||
});
|
||||
|
||||
it("accepts a single explicit click sample without needing surrounding moves", () => {
|
||||
const result = buildInteractionZoomSuggestions({
|
||||
cursorTelemetry: [makeClick(5_000)],
|
||||
totalMs: TOTAL_MS,
|
||||
defaultDurationMs: 3_000,
|
||||
});
|
||||
|
||||
expect(result.status).toBe("ok");
|
||||
expect(result.suggestions).toHaveLength(1);
|
||||
});
|
||||
|
||||
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)],
|
||||
@@ -123,6 +137,24 @@ describe("buildInteractionZoomSuggestions (click-cluster logic)", () => {
|
||||
expect(result.suggestions).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("ignores dwell-derived click-like heuristics when there are no explicit clicks", () => {
|
||||
const telemetry: CursorTelemetryPoint[] = [
|
||||
makeMove(0, 0.5, 0.5),
|
||||
makeMove(200, 0.5005, 0.5005),
|
||||
makeMove(400, 0.5008, 0.5008),
|
||||
makeMove(600, 0.501, 0.501),
|
||||
];
|
||||
|
||||
const result = buildInteractionZoomSuggestions({
|
||||
cursorTelemetry: telemetry,
|
||||
totalMs: TOTAL_MS,
|
||||
defaultDurationMs: 3_000,
|
||||
});
|
||||
|
||||
expect(result.status).toBe("no-interactions");
|
||||
expect(result.suggestions).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("skips clusters that overlap reserved spans", () => {
|
||||
const click = 5_000;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface CursorInteractionCandidate extends ZoomDwellCandidate {
|
||||
| "dropdown-open"
|
||||
| "text-selection"
|
||||
| "text-field-click";
|
||||
source: "explicit" | "heuristic";
|
||||
}
|
||||
|
||||
export interface SuggestedZoomRegion {
|
||||
@@ -213,6 +214,7 @@ export function detectInteractionCandidates(
|
||||
focus: { cx: clickSample.cx, cy: clickSample.cy },
|
||||
strength: baseStrength,
|
||||
kind,
|
||||
source: "explicit",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -220,12 +222,12 @@ export function detectInteractionCandidates(
|
||||
const dwellCandidates = detectZoomDwellCandidates(samples).map<CursorInteractionCandidate>(
|
||||
(candidate) => {
|
||||
if (candidate.strength >= 1100) {
|
||||
return { ...candidate, kind: "text-focus-like" };
|
||||
return { ...candidate, kind: "text-focus-like", source: "heuristic" };
|
||||
}
|
||||
if (candidate.strength <= 800) {
|
||||
return { ...candidate, kind: "click-like" };
|
||||
return { ...candidate, kind: "click-like", source: "heuristic" };
|
||||
}
|
||||
return { ...candidate, kind: "dwell" };
|
||||
return { ...candidate, kind: "dwell", source: "heuristic" };
|
||||
},
|
||||
);
|
||||
|
||||
@@ -249,6 +251,7 @@ export function detectInteractionCandidates(
|
||||
},
|
||||
strength: prev.strength + curr.strength + 500,
|
||||
kind: "double-click-like",
|
||||
source: "heuristic",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -345,13 +348,23 @@ export function buildInteractionZoomSuggestions(params: {
|
||||
}
|
||||
|
||||
const normalizedSamples = normalizeCursorTelemetry(cursorTelemetry, totalMs);
|
||||
if (normalizedSamples.length < 2) {
|
||||
if (normalizedSamples.length === 0) {
|
||||
return { status: "no-telemetry", suggestions: [] };
|
||||
}
|
||||
|
||||
if (
|
||||
normalizedSamples.length === 1 &&
|
||||
normalizedSamples[0].interactionType !== "click" &&
|
||||
normalizedSamples[0].interactionType !== "double-click" &&
|
||||
normalizedSamples[0].interactionType !== "right-click" &&
|
||||
normalizedSamples[0].interactionType !== "middle-click"
|
||||
) {
|
||||
return { status: "no-telemetry", suggestions: [] };
|
||||
}
|
||||
|
||||
// Only use explicit click events (uiohook telemetry) – ignore dwell heuristics
|
||||
const clickCandidates = detectInteractionCandidates(normalizedSamples).filter(
|
||||
(c) => c.kind === "click-like" || c.kind === "double-click-like" || c.kind === "dropdown-open" || c.kind === "text-field-click" || c.kind === "text-selection",
|
||||
(candidate) => candidate.source === "explicit",
|
||||
);
|
||||
|
||||
if (clickCandidates.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user