refactor(web): extract touch event forwarding
This commit is contained in:
@@ -0,0 +1,81 @@
|
|||||||
|
import type { TouchEvent } from "react";
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import {
|
||||||
|
forwardCanvasTouchEnd,
|
||||||
|
forwardCanvasTouchMove,
|
||||||
|
forwardTouchStartAsMouseDown,
|
||||||
|
} from "./timelineTouchEvents.js";
|
||||||
|
|
||||||
|
function createTouchEvent<TCurrentTarget>(
|
||||||
|
target: TCurrentTarget,
|
||||||
|
point: { clientX: number; clientY: number },
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
currentTarget: target,
|
||||||
|
preventDefault: vi.fn(),
|
||||||
|
touches: [point],
|
||||||
|
changedTouches: [point],
|
||||||
|
} as unknown as TouchEvent<TCurrentTarget>;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("timelineTouchEvents", () => {
|
||||||
|
it("forwards touch starts as mouse-down events and marks the decision state", () => {
|
||||||
|
const onMouseDown = vi.fn();
|
||||||
|
const touchStartRef = { current: { x: 0, y: 0, decided: false } };
|
||||||
|
const event = createTouchEvent({ id: "row" }, { clientX: 18, clientY: 42 });
|
||||||
|
|
||||||
|
forwardTouchStartAsMouseDown({
|
||||||
|
event,
|
||||||
|
touchStartRef,
|
||||||
|
decided: true,
|
||||||
|
onMouseDown,
|
||||||
|
opts: { allocationId: "alloc-1" },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(event.preventDefault).toHaveBeenCalledOnce();
|
||||||
|
expect(touchStartRef.current).toEqual({ x: 18, y: 42, decided: true });
|
||||||
|
expect(onMouseDown).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ clientX: 18, button: 0, currentTarget: { id: "row" } }),
|
||||||
|
{ allocationId: "alloc-1" },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps touch move in scroll mode until drag handling is approved", () => {
|
||||||
|
const onCanvasMouseMove = vi.fn();
|
||||||
|
const touchStartRef = { current: { x: 10, y: 10, decided: false } };
|
||||||
|
|
||||||
|
forwardCanvasTouchMove({
|
||||||
|
event: createTouchEvent({ id: "canvas" }, { clientX: 11, clientY: 28 }),
|
||||||
|
touchStartRef,
|
||||||
|
onCanvasMouseMove,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(onCanvasMouseMove).not.toHaveBeenCalled();
|
||||||
|
expect(touchStartRef.current).toEqual({ x: 10, y: 10, decided: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("forwards touch move once the touch policy resolves to drag handling", () => {
|
||||||
|
const onCanvasMouseMove = vi.fn();
|
||||||
|
const touchStartRef = { current: { x: 10, y: 10, decided: false } };
|
||||||
|
|
||||||
|
forwardCanvasTouchMove({
|
||||||
|
event: createTouchEvent({ id: "canvas" }, { clientX: 40, clientY: 12 }),
|
||||||
|
touchStartRef,
|
||||||
|
onCanvasMouseMove,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(onCanvasMouseMove).toHaveBeenCalledWith(expect.objectContaining({ clientX: 40, clientY: 12 }));
|
||||||
|
expect(touchStartRef.current).toEqual({ x: 10, y: 10, decided: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("awaits touch-end forwarding through the canvas mouse-up path", async () => {
|
||||||
|
const onCanvasMouseUp = vi.fn().mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await forwardCanvasTouchEnd({
|
||||||
|
event: createTouchEvent({ id: "canvas" }, { clientX: 55, clientY: 21 }),
|
||||||
|
onCanvasMouseUp,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(onCanvasMouseUp).toHaveBeenCalledWith(expect.objectContaining({ clientX: 55, clientY: 21 }));
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import type { TouchEvent } from "react";
|
||||||
|
import { createTouchCanvasPointerEvent, createTouchMouseDownEvent } from "./timelineTouchAdapters.js";
|
||||||
|
import { getTouchPoint, resolveTouchDragDecision } from "./timelineTouch.js";
|
||||||
|
|
||||||
|
type MutableCurrent<T> = { current: T };
|
||||||
|
type TouchDecisionState = { x: number; y: number; decided: boolean };
|
||||||
|
|
||||||
|
export function forwardTouchStartAsMouseDown<TCurrentTarget, TOptions>({
|
||||||
|
event,
|
||||||
|
touchStartRef,
|
||||||
|
decided,
|
||||||
|
onMouseDown,
|
||||||
|
opts,
|
||||||
|
}: {
|
||||||
|
event: TouchEvent<TCurrentTarget>;
|
||||||
|
touchStartRef: MutableCurrent<TouchDecisionState>;
|
||||||
|
decided: boolean;
|
||||||
|
onMouseDown: (event: ReturnType<typeof createTouchMouseDownEvent>, opts: TOptions) => void;
|
||||||
|
opts: TOptions;
|
||||||
|
}) {
|
||||||
|
event.preventDefault();
|
||||||
|
const point = getTouchPoint(event);
|
||||||
|
touchStartRef.current = { x: point.clientX, y: point.clientY, decided };
|
||||||
|
onMouseDown(createTouchMouseDownEvent(point, event.currentTarget), opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function forwardCanvasTouchMove<TCurrentTarget>({
|
||||||
|
event,
|
||||||
|
touchStartRef,
|
||||||
|
onCanvasMouseMove,
|
||||||
|
}: {
|
||||||
|
event: TouchEvent<TCurrentTarget>;
|
||||||
|
touchStartRef: MutableCurrent<TouchDecisionState>;
|
||||||
|
onCanvasMouseMove: (event: ReturnType<typeof createTouchCanvasPointerEvent>) => void;
|
||||||
|
}) {
|
||||||
|
const point = getTouchPoint(event);
|
||||||
|
const decision = resolveTouchDragDecision(touchStartRef.current, point);
|
||||||
|
touchStartRef.current = decision.nextState;
|
||||||
|
if (!decision.shouldHandleDrag) return;
|
||||||
|
onCanvasMouseMove(createTouchCanvasPointerEvent(point));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function forwardCanvasTouchEnd<TCurrentTarget>({
|
||||||
|
event,
|
||||||
|
onCanvasMouseUp,
|
||||||
|
}: {
|
||||||
|
event: TouchEvent<TCurrentTarget>;
|
||||||
|
onCanvasMouseUp: (event: ReturnType<typeof createTouchCanvasPointerEvent>) => Promise<void> | void;
|
||||||
|
}) {
|
||||||
|
return onCanvasMouseUp(createTouchCanvasPointerEvent(getTouchPoint(event)));
|
||||||
|
}
|
||||||
@@ -26,6 +26,11 @@ import { resolveAllocationDragPosition, resolveProjectDragPosition } from "./tim
|
|||||||
import { attachDocumentMouseDrag } from "./timelineDocumentDrag.js";
|
import { attachDocumentMouseDrag } from "./timelineDocumentDrag.js";
|
||||||
import { buildProjectShiftMutationInput, createProjectDragState } from "./timelineProjectDrag.js";
|
import { buildProjectShiftMutationInput, createProjectDragState } from "./timelineProjectDrag.js";
|
||||||
import { beginProjectDragSession } from "./timelineProjectDragSession.js";
|
import { beginProjectDragSession } from "./timelineProjectDragSession.js";
|
||||||
|
import {
|
||||||
|
forwardCanvasTouchEnd,
|
||||||
|
forwardCanvasTouchMove,
|
||||||
|
forwardTouchStartAsMouseDown,
|
||||||
|
} from "./timelineTouchEvents.js";
|
||||||
import {
|
import {
|
||||||
completeMultiSelectDraft,
|
completeMultiSelectDraft,
|
||||||
createMultiSelectState,
|
createMultiSelectState,
|
||||||
@@ -34,16 +39,7 @@ import {
|
|||||||
import { beginCanvasMultiSelectSession } from "./timelineMultiSelectSession.js";
|
import { beginCanvasMultiSelectSession } from "./timelineMultiSelectSession.js";
|
||||||
import { reconcileOptimisticEntries } from "./timelineOptimisticAllocations.js";
|
import { reconcileOptimisticEntries } from "./timelineOptimisticAllocations.js";
|
||||||
import { createRangeSelectionState, finalizeRangeSelection, updateRangeSelectionDraft } from "./timelineRangeSelection.js";
|
import { createRangeSelectionState, finalizeRangeSelection, updateRangeSelectionDraft } from "./timelineRangeSelection.js";
|
||||||
import {
|
import { type TouchCanvasPointerEvent, type TouchMouseDownEvent } from "./timelineTouchAdapters.js";
|
||||||
createTouchCanvasPointerEvent,
|
|
||||||
createTouchMouseDownEvent,
|
|
||||||
type TouchCanvasPointerEvent,
|
|
||||||
type TouchMouseDownEvent,
|
|
||||||
} from "./timelineTouchAdapters.js";
|
|
||||||
import {
|
|
||||||
getTouchPoint,
|
|
||||||
resolveTouchDragDecision,
|
|
||||||
} from "./timelineTouch.js";
|
|
||||||
|
|
||||||
const DRAG_CLICK_THRESHOLD_PX = 5;
|
const DRAG_CLICK_THRESHOLD_PX = 5;
|
||||||
|
|
||||||
@@ -796,10 +792,13 @@ export function useTimelineDrag({
|
|||||||
endDate: Date;
|
endDate: Date;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
e.preventDefault();
|
forwardTouchStartAsMouseDown({
|
||||||
const point = getTouchPoint(e);
|
event: e,
|
||||||
touchStartRef.current = { x: point.clientX, y: point.clientY, decided: true };
|
touchStartRef,
|
||||||
onProjectBarMouseDown(createTouchMouseDownEvent(point, e.currentTarget), opts);
|
decided: true,
|
||||||
|
onMouseDown: onProjectBarMouseDown,
|
||||||
|
opts,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[onProjectBarMouseDown],
|
[onProjectBarMouseDown],
|
||||||
);
|
);
|
||||||
@@ -821,10 +820,13 @@ export function useTimelineDrag({
|
|||||||
scope?: AllocDragScope;
|
scope?: AllocDragScope;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
e.preventDefault();
|
forwardTouchStartAsMouseDown({
|
||||||
const point = getTouchPoint(e);
|
event: e,
|
||||||
touchStartRef.current = { x: point.clientX, y: point.clientY, decided: true };
|
touchStartRef,
|
||||||
onAllocMouseDown(createTouchMouseDownEvent(point, e.currentTarget), opts);
|
decided: true,
|
||||||
|
onMouseDown: onAllocMouseDown,
|
||||||
|
opts,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[onAllocMouseDown],
|
[onAllocMouseDown],
|
||||||
);
|
);
|
||||||
@@ -838,33 +840,31 @@ export function useTimelineDrag({
|
|||||||
suggestedProjectId?: string;
|
suggestedProjectId?: string;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
e.preventDefault();
|
forwardTouchStartAsMouseDown({
|
||||||
const point = getTouchPoint(e);
|
event: e,
|
||||||
touchStartRef.current = { x: point.clientX, y: point.clientY, decided: false };
|
touchStartRef,
|
||||||
onRowMouseDown(createTouchMouseDownEvent(point, e.currentTarget), opts);
|
decided: false,
|
||||||
|
onMouseDown: onRowMouseDown,
|
||||||
|
opts,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[onRowMouseDown],
|
[onRowMouseDown],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onCanvasTouchMove = useCallback(
|
const onCanvasTouchMove = useCallback(
|
||||||
(e: React.TouchEvent) => {
|
(e: React.TouchEvent) => {
|
||||||
const point = getTouchPoint(e);
|
forwardCanvasTouchMove({
|
||||||
|
event: e,
|
||||||
// Scroll vs drag disambiguation: once decided, stick with the decision
|
touchStartRef,
|
||||||
const decision = resolveTouchDragDecision(touchStartRef.current, point);
|
onCanvasMouseMove,
|
||||||
touchStartRef.current = decision.nextState;
|
});
|
||||||
if (!decision.shouldHandleDrag) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
onCanvasMouseMove(createTouchCanvasPointerEvent(point));
|
|
||||||
},
|
},
|
||||||
[onCanvasMouseMove],
|
[onCanvasMouseMove],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onCanvasTouchEnd = useCallback(
|
const onCanvasTouchEnd = useCallback(
|
||||||
async (e: React.TouchEvent) => {
|
async (e: React.TouchEvent) => {
|
||||||
await onCanvasMouseUp(createTouchCanvasPointerEvent(getTouchPoint(e)));
|
await forwardCanvasTouchEnd({ event: e, onCanvasMouseUp });
|
||||||
},
|
},
|
||||||
[onCanvasMouseUp],
|
[onCanvasMouseUp],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -149,6 +149,33 @@ export const rules = [
|
|||||||
],
|
],
|
||||||
forbidden: [],
|
forbidden: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
file: "apps/web/src/hooks/timelineTouchEvents.ts",
|
||||||
|
maxLines: 80,
|
||||||
|
required: [
|
||||||
|
{
|
||||||
|
pattern: /\bexport function forwardTouchStartAsMouseDown\b/,
|
||||||
|
message: "timeline touch event helpers must keep touch-start forwarding centralized",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: /\bexport function forwardCanvasTouchMove\b/,
|
||||||
|
message: "timeline touch event helpers must keep touch-move forwarding centralized",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: /\bexport function forwardCanvasTouchEnd\b/,
|
||||||
|
message: "timeline touch event helpers must keep touch-end forwarding centralized",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: /from "\.\/timelineTouch\.js"/,
|
||||||
|
message: "timeline touch event helpers must keep touch policy delegated to the extracted helper module",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: /from "\.\/timelineTouchAdapters\.js"/,
|
||||||
|
message: "timeline touch event helpers must keep touch adapter wiring delegated to the extracted helper module",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
forbidden: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
file: "apps/web/src/hooks/timelineMultiSelect.ts",
|
file: "apps/web/src/hooks/timelineMultiSelect.ts",
|
||||||
maxLines: 90,
|
maxLines: 90,
|
||||||
@@ -404,12 +431,8 @@ export const rules = [
|
|||||||
message: "timeline drag must keep live preview behavior delegated to the extracted helper module",
|
message: "timeline drag must keep live preview behavior delegated to the extracted helper module",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: /from "\.\/timelineTouch\.js"/,
|
pattern: /from "\.\/timelineTouchEvents\.js"/,
|
||||||
message: "timeline drag must keep touch fallback and drag disambiguation delegated to the extracted helper module",
|
message: "timeline drag must keep touch event forwarding delegated to the extracted helper module",
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: /from "\.\/timelineTouchAdapters\.js"/,
|
|
||||||
message: "timeline drag must keep touch pointer adapter wiring delegated to the extracted helper module",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: /from "\.\/timelineMultiSelect\.js"/,
|
pattern: /from "\.\/timelineMultiSelect\.js"/,
|
||||||
@@ -474,12 +497,8 @@ export const rules = [
|
|||||||
message: "timeline drag must not re-inline live preview helper implementations",
|
message: "timeline drag must not re-inline live preview helper implementations",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: /\bfunction toClientX\b/,
|
pattern: /\b(?:getTouchPoint|resolveTouchDragDecision|createTouchCanvasPointerEvent|createTouchMouseDownEvent)\b/,
|
||||||
message: "timeline drag must not re-inline touch coordinate fallback helpers",
|
message: "timeline drag must not re-inline extracted touch event forwarding dependencies",
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: /as (?:unknown as )?React\.MouseEvent/,
|
|
||||||
message: "timeline drag must not re-inline synthetic touch pointer adapters",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: /\bfunction (?:hasAllocationDateChange|shouldTreatAllocationDragAsClick|requiresAllocationFragmentExtraction|buildAllocationMovedSnapshot|reconcileOptimisticEntries)\b/,
|
pattern: /\bfunction (?:hasAllocationDateChange|shouldTreatAllocationDragAsClick|requiresAllocationFragmentExtraction|buildAllocationMovedSnapshot|reconcileOptimisticEntries)\b/,
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ describe("architecture guardrails", () => {
|
|||||||
const livePreviewRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineLivePreview.ts");
|
const livePreviewRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineLivePreview.ts");
|
||||||
const touchRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineTouch.ts");
|
const touchRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineTouch.ts");
|
||||||
const touchAdaptersRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineTouchAdapters.ts");
|
const touchAdaptersRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineTouchAdapters.ts");
|
||||||
|
const touchEventsRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineTouchEvents.ts");
|
||||||
const multiSelectRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineMultiSelect.ts");
|
const multiSelectRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineMultiSelect.ts");
|
||||||
const multiSelectSessionRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineMultiSelectSession.ts");
|
const multiSelectSessionRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineMultiSelectSession.ts");
|
||||||
const rangeRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineRangeSelection.ts");
|
const rangeRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineRangeSelection.ts");
|
||||||
@@ -99,6 +100,7 @@ describe("architecture guardrails", () => {
|
|||||||
assert.ok(livePreviewRule);
|
assert.ok(livePreviewRule);
|
||||||
assert.ok(touchRule);
|
assert.ok(touchRule);
|
||||||
assert.ok(touchAdaptersRule);
|
assert.ok(touchAdaptersRule);
|
||||||
|
assert.ok(touchEventsRule);
|
||||||
assert.ok(multiSelectRule);
|
assert.ok(multiSelectRule);
|
||||||
assert.ok(multiSelectSessionRule);
|
assert.ok(multiSelectSessionRule);
|
||||||
assert.ok(rangeRule);
|
assert.ok(rangeRule);
|
||||||
@@ -119,8 +121,7 @@ describe("architecture guardrails", () => {
|
|||||||
|
|
||||||
assert.deepEqual(evaluateRule(dragRule, "function clearLivePreview() {}\n"), [
|
assert.deepEqual(evaluateRule(dragRule, "function clearLivePreview() {}\n"), [
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep live preview behavior delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep live preview behavior delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep touch fallback and drag disambiguation delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep touch event forwarding delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep touch pointer adapter wiring delegated to the extracted helper module",
|
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep multi-select rectangle lifecycle delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep multi-select rectangle lifecycle delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep multi-select document session wiring delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep multi-select document session wiring delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep range preview and finalization delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep range preview and finalization delegated to the extracted helper module",
|
||||||
@@ -152,6 +153,14 @@ describe("architecture guardrails", () => {
|
|||||||
"apps/web/src/hooks/timelineTouchAdapters.ts: missing guardrail anchor: timeline touch adapter helpers must keep canvas pointer adapter wiring centralized",
|
"apps/web/src/hooks/timelineTouchAdapters.ts: missing guardrail anchor: timeline touch adapter helpers must keep canvas pointer adapter wiring centralized",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
assert.deepEqual(evaluateRule(touchEventsRule, ""), [
|
||||||
|
"apps/web/src/hooks/timelineTouchEvents.ts: missing guardrail anchor: timeline touch event helpers must keep touch-start forwarding centralized",
|
||||||
|
"apps/web/src/hooks/timelineTouchEvents.ts: missing guardrail anchor: timeline touch event helpers must keep touch-move forwarding centralized",
|
||||||
|
"apps/web/src/hooks/timelineTouchEvents.ts: missing guardrail anchor: timeline touch event helpers must keep touch-end forwarding centralized",
|
||||||
|
"apps/web/src/hooks/timelineTouchEvents.ts: missing guardrail anchor: timeline touch event helpers must keep touch policy delegated to the extracted helper module",
|
||||||
|
"apps/web/src/hooks/timelineTouchEvents.ts: missing guardrail anchor: timeline touch event helpers must keep touch adapter wiring delegated to the extracted helper module",
|
||||||
|
]);
|
||||||
|
|
||||||
assert.deepEqual(evaluateRule(multiSelectRule, "export function createMultiSelectState() {}\n"), [
|
assert.deepEqual(evaluateRule(multiSelectRule, "export function createMultiSelectState() {}\n"), [
|
||||||
"apps/web/src/hooks/timelineMultiSelect.ts: missing guardrail anchor: timeline multi-select helpers must keep minimal-drag reset logic centralized",
|
"apps/web/src/hooks/timelineMultiSelect.ts: missing guardrail anchor: timeline multi-select helpers must keep minimal-drag reset logic centralized",
|
||||||
"apps/web/src/hooks/timelineMultiSelect.ts: missing guardrail anchor: timeline multi-select helpers must keep right-click release completion centralized",
|
"apps/web/src/hooks/timelineMultiSelect.ts: missing guardrail anchor: timeline multi-select helpers must keep right-click release completion centralized",
|
||||||
@@ -231,11 +240,11 @@ describe("architecture guardrails", () => {
|
|||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
evaluateRule(
|
evaluateRule(
|
||||||
dragRule,
|
dragRule,
|
||||||
'import { getTouchPoint } from "./timelineTouch.js";\nconst e = {} as unknown as React.MouseEvent;\n',
|
'import { getTouchPoint } from "./timelineTouch.js";\n',
|
||||||
),
|
),
|
||||||
[
|
[
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep live preview behavior delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep live preview behavior delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep touch pointer adapter wiring delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep touch event forwarding delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep multi-select rectangle lifecycle delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep multi-select rectangle lifecycle delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep multi-select document session wiring delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep multi-select document session wiring delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep range preview and finalization delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep range preview and finalization delegated to the extracted helper module",
|
||||||
@@ -250,7 +259,7 @@ describe("architecture guardrails", () => {
|
|||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation release side effects delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation release side effects delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep project drag bootstrap and mutation gating delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep project drag bootstrap and mutation gating delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep project drag document session wiring delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep project drag document session wiring delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: forbidden pattern matched: timeline drag must not re-inline synthetic touch pointer adapters",
|
"apps/web/src/hooks/useTimelineDrag.ts: forbidden pattern matched: timeline drag must not re-inline extracted touch event forwarding dependencies",
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user