refactor(web): extract allocation release classification
This commit is contained in:
@@ -0,0 +1,114 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { resolveAllocationRelease } from "./timelineAllocationRelease.js";
|
||||||
|
|
||||||
|
const BASE_ALLOC = {
|
||||||
|
isActive: true,
|
||||||
|
mode: "move" as const,
|
||||||
|
pointerDeltaX: 0,
|
||||||
|
daysDelta: 0,
|
||||||
|
allocationId: "alloc-1",
|
||||||
|
mutationAllocationId: null,
|
||||||
|
projectId: "project-1",
|
||||||
|
projectName: "Project One",
|
||||||
|
scope: "allocation" as const,
|
||||||
|
allocationStartDate: new Date("2025-01-01"),
|
||||||
|
allocationEndDate: new Date("2025-01-03"),
|
||||||
|
originalStartDate: new Date("2025-01-01"),
|
||||||
|
originalEndDate: new Date("2025-01-03"),
|
||||||
|
currentStartDate: new Date("2025-01-01"),
|
||||||
|
currentEndDate: new Date("2025-01-03"),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("timelineAllocationRelease", () => {
|
||||||
|
it("ignores inactive drags", () => {
|
||||||
|
expect(resolveAllocationRelease({ ...BASE_ALLOC, isActive: false }, { clickThresholdPx: 5, wasShift: false })).toEqual({
|
||||||
|
kind: "ignore",
|
||||||
|
preservePreview: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("routes shift-click releases to multi-select toggling", () => {
|
||||||
|
expect(resolveAllocationRelease(BASE_ALLOC, { clickThresholdPx: 5, wasShift: true })).toEqual({
|
||||||
|
kind: "shift-click",
|
||||||
|
allocationId: "alloc-1",
|
||||||
|
preservePreview: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to reset when click info cannot be built", () => {
|
||||||
|
expect(
|
||||||
|
resolveAllocationRelease(
|
||||||
|
{
|
||||||
|
...BASE_ALLOC,
|
||||||
|
originalStartDate: null,
|
||||||
|
originalEndDate: null,
|
||||||
|
},
|
||||||
|
{ clickThresholdPx: 5, wasShift: false },
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
kind: "reset",
|
||||||
|
preservePreview: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("builds a mutation outcome when the allocation dates changed", () => {
|
||||||
|
const currentStartDate = new Date("2025-01-02");
|
||||||
|
const currentEndDate = new Date("2025-01-04");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveAllocationRelease(
|
||||||
|
{
|
||||||
|
...BASE_ALLOC,
|
||||||
|
daysDelta: 1,
|
||||||
|
pointerDeltaX: 20,
|
||||||
|
currentStartDate,
|
||||||
|
currentEndDate,
|
||||||
|
},
|
||||||
|
{ clickThresholdPx: 5, wasShift: false },
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
kind: "mutation",
|
||||||
|
preservePreview: true,
|
||||||
|
mutationPlan: {
|
||||||
|
activeAllocationId: "alloc-1",
|
||||||
|
currentStartDate,
|
||||||
|
currentEndDate,
|
||||||
|
baseMutationAllocationId: "alloc-1",
|
||||||
|
requiresExtraction: false,
|
||||||
|
pendingSnapshot: {
|
||||||
|
allocationId: "alloc-1",
|
||||||
|
mutationAllocationId: "alloc-1",
|
||||||
|
projectName: "Project One",
|
||||||
|
before: {
|
||||||
|
startDate: new Date("2025-01-01"),
|
||||||
|
endDate: new Date("2025-01-03"),
|
||||||
|
},
|
||||||
|
after: {
|
||||||
|
startDate: currentStartDate,
|
||||||
|
endDate: currentEndDate,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resets changed segment drags when no allocation id remains available", () => {
|
||||||
|
expect(
|
||||||
|
resolveAllocationRelease(
|
||||||
|
{
|
||||||
|
...BASE_ALLOC,
|
||||||
|
allocationId: null,
|
||||||
|
scope: "segment",
|
||||||
|
daysDelta: 1,
|
||||||
|
pointerDeltaX: 20,
|
||||||
|
currentStartDate: new Date("2025-01-02"),
|
||||||
|
currentEndDate: new Date("2025-01-04"),
|
||||||
|
},
|
||||||
|
{ clickThresholdPx: 5, wasShift: false },
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
kind: "reset",
|
||||||
|
preservePreview: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { buildAllocationBlockClickInfo, buildAllocationMutationPlan } from "./timelineAllocationActions.js";
|
||||||
|
import { hasAllocationDateChange, shouldTreatAllocationDragAsClick } from "./timelineAllocationFinalize.js";
|
||||||
|
|
||||||
|
type AllocationReleaseLike = {
|
||||||
|
isActive: boolean;
|
||||||
|
mode: "move" | "resize-start" | "resize-end";
|
||||||
|
pointerDeltaX: number;
|
||||||
|
daysDelta: number;
|
||||||
|
allocationId: string | null;
|
||||||
|
mutationAllocationId: string | null;
|
||||||
|
projectId: string | null;
|
||||||
|
projectName: string | null;
|
||||||
|
scope: "allocation" | "segment";
|
||||||
|
allocationStartDate: Date | null;
|
||||||
|
allocationEndDate: Date | null;
|
||||||
|
originalStartDate: Date | null;
|
||||||
|
originalEndDate: Date | null;
|
||||||
|
currentStartDate: Date | null;
|
||||||
|
currentEndDate: Date | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AllocationBlockClickInfo = ReturnType<typeof buildAllocationBlockClickInfo>;
|
||||||
|
type AllocationMutationPlan = NonNullable<ReturnType<typeof buildAllocationMutationPlan>>;
|
||||||
|
|
||||||
|
export type AllocationReleaseOutcome =
|
||||||
|
| { kind: "ignore"; preservePreview: false }
|
||||||
|
| { kind: "reset"; preservePreview: boolean }
|
||||||
|
| { kind: "shift-click"; allocationId: string; preservePreview: boolean }
|
||||||
|
| { kind: "click"; clickInfo: NonNullable<AllocationBlockClickInfo>; preservePreview: boolean }
|
||||||
|
| { kind: "mutation"; mutationPlan: AllocationMutationPlan; preservePreview: true };
|
||||||
|
|
||||||
|
export function resolveAllocationRelease(
|
||||||
|
alloc: AllocationReleaseLike,
|
||||||
|
{ clickThresholdPx, wasShift }: { clickThresholdPx: number; wasShift: boolean },
|
||||||
|
): AllocationReleaseOutcome {
|
||||||
|
if (!alloc.isActive) {
|
||||||
|
return { kind: "ignore", preservePreview: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const preservePreview = hasAllocationDateChange(alloc);
|
||||||
|
const shouldTreatAsClick = shouldTreatAllocationDragAsClick(alloc, clickThresholdPx);
|
||||||
|
|
||||||
|
if (shouldTreatAsClick && alloc.allocationId) {
|
||||||
|
if (wasShift) {
|
||||||
|
return { kind: "shift-click", allocationId: alloc.allocationId, preservePreview };
|
||||||
|
}
|
||||||
|
|
||||||
|
const clickInfo = buildAllocationBlockClickInfo(alloc);
|
||||||
|
if (clickInfo) {
|
||||||
|
return { kind: "click", clickInfo, preservePreview };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preservePreview && alloc.allocationId && alloc.currentStartDate && alloc.currentEndDate) {
|
||||||
|
const mutationPlan = buildAllocationMutationPlan(alloc);
|
||||||
|
if (mutationPlan) {
|
||||||
|
return { kind: "mutation", mutationPlan, preservePreview: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { kind: "reset", preservePreview };
|
||||||
|
}
|
||||||
@@ -11,18 +11,14 @@ import {
|
|||||||
scheduleLivePreview,
|
scheduleLivePreview,
|
||||||
type LivePreviewSession,
|
type LivePreviewSession,
|
||||||
} from "./timelineLivePreview.js";
|
} from "./timelineLivePreview.js";
|
||||||
import {
|
import { buildAllocationMovedSnapshot } from "./timelineAllocationFinalize.js";
|
||||||
buildAllocationMovedSnapshot,
|
|
||||||
hasAllocationDateChange,
|
|
||||||
shouldTreatAllocationDragAsClick,
|
|
||||||
} from "./timelineAllocationFinalize.js";
|
|
||||||
import { buildAllocationBlockClickInfo, buildAllocationMutationPlan } from "./timelineAllocationActions.js";
|
|
||||||
import {
|
import {
|
||||||
finalizeAllocationMultiDrag,
|
finalizeAllocationMultiDrag,
|
||||||
isAllocationMultiSelected,
|
isAllocationMultiSelected,
|
||||||
startAllocationMultiDrag,
|
startAllocationMultiDrag,
|
||||||
updateAllocationMultiDrag,
|
updateAllocationMultiDrag,
|
||||||
} from "./timelineAllocationMultiDrag.js";
|
} from "./timelineAllocationMultiDrag.js";
|
||||||
|
import { resolveAllocationRelease } from "./timelineAllocationRelease.js";
|
||||||
import { createAllocationDragState } from "./timelineAllocationDragState.js";
|
import { createAllocationDragState } from "./timelineAllocationDragState.js";
|
||||||
import { attachDocumentMouseDrag } from "./timelineDocumentDrag.js";
|
import { attachDocumentMouseDrag } from "./timelineDocumentDrag.js";
|
||||||
import { buildProjectShiftMutationInput, createProjectDragState } from "./timelineProjectDrag.js";
|
import { buildProjectShiftMutationInput, createProjectDragState } from "./timelineProjectDrag.js";
|
||||||
@@ -703,36 +699,24 @@ export function useTimelineDrag({
|
|||||||
allocDragCleanupRef.current = null;
|
allocDragCleanupRef.current = null;
|
||||||
updateAllocationDragPosition(ev.clientX);
|
updateAllocationDragPosition(ev.clientX);
|
||||||
const alloc = allocDragRef.current;
|
const alloc = allocDragRef.current;
|
||||||
if (!alloc.isActive) return;
|
const release = resolveAllocationRelease(alloc, {
|
||||||
const hasDateChange = hasAllocationDateChange(alloc);
|
clickThresholdPx: DRAG_CLICK_THRESHOLD_PX,
|
||||||
|
wasShift,
|
||||||
|
});
|
||||||
|
if (release.kind === "ignore") return;
|
||||||
|
|
||||||
if (hasDateChange) {
|
if (release.preservePreview) {
|
||||||
preserveLivePreview(allocPreviewRef.current);
|
preserveLivePreview(allocPreviewRef.current);
|
||||||
}
|
}
|
||||||
clearLivePreview(allocPreviewRef.current);
|
clearLivePreview(allocPreviewRef.current);
|
||||||
allocPreviewRef.current = null;
|
allocPreviewRef.current = null;
|
||||||
const shouldTreatAsClick = shouldTreatAllocationDragAsClick(alloc, DRAG_CLICK_THRESHOLD_PX);
|
|
||||||
|
|
||||||
if (shouldTreatAsClick && alloc.allocationId) {
|
|
||||||
// No movement → treat as click
|
|
||||||
if (wasShift) {
|
|
||||||
// Shift+Click → toggle multi-selection for this allocation
|
|
||||||
onShiftClickAllocRef.current?.(alloc.allocationId);
|
|
||||||
} else {
|
|
||||||
// Normal click → open alloc popover
|
|
||||||
const clickInfo = buildAllocationBlockClickInfo(alloc);
|
|
||||||
if (clickInfo) {
|
|
||||||
onBlockClickRef.current?.(clickInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (hasDateChange && alloc.allocationId && alloc.currentStartDate && alloc.currentEndDate) {
|
|
||||||
const mutationPlan = buildAllocationMutationPlan(alloc);
|
|
||||||
if (!mutationPlan) {
|
|
||||||
allocDragRef.current = INITIAL_ALLOC_DRAG;
|
|
||||||
setAllocDragState(INITIAL_ALLOC_DRAG);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (release.kind === "shift-click") {
|
||||||
|
onShiftClickAllocRef.current?.(release.allocationId);
|
||||||
|
} else if (release.kind === "click") {
|
||||||
|
onBlockClickRef.current?.(release.clickInfo);
|
||||||
|
} else if (release.kind === "mutation") {
|
||||||
|
const { mutationPlan } = release;
|
||||||
const {
|
const {
|
||||||
activeAllocationId,
|
activeAllocationId,
|
||||||
currentStartDate,
|
currentStartDate,
|
||||||
|
|||||||
@@ -240,6 +240,21 @@ export const rules = [
|
|||||||
],
|
],
|
||||||
forbidden: [],
|
forbidden: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
file: "apps/web/src/hooks/timelineAllocationRelease.ts",
|
||||||
|
maxLines: 90,
|
||||||
|
required: [
|
||||||
|
{
|
||||||
|
pattern: /\bexport function resolveAllocationRelease\b/,
|
||||||
|
message: "timeline allocation release helpers must keep release classification centralized",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: /from "\.\/timelineAllocationActions\.js"/,
|
||||||
|
message: "timeline allocation release helpers must keep click and mutation plan derivation delegated to allocation action helpers",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
forbidden: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
file: "apps/web/src/hooks/timelineDocumentDrag.ts",
|
file: "apps/web/src/hooks/timelineDocumentDrag.ts",
|
||||||
maxLines: 50,
|
maxLines: 50,
|
||||||
@@ -305,8 +320,8 @@ export const rules = [
|
|||||||
message: "timeline drag must keep allocation drag completion rules delegated to the extracted helper module",
|
message: "timeline drag must keep allocation drag completion rules delegated to the extracted helper module",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: /from "\.\/timelineAllocationActions\.js"/,
|
pattern: /from "\.\/timelineAllocationRelease\.js"/,
|
||||||
message: "timeline drag must keep allocation click and mutation plan derivation delegated to the extracted helper module",
|
message: "timeline drag must keep allocation release classification delegated to the extracted helper module",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: /from "\.\/timelineDocumentDrag\.js"/,
|
pattern: /from "\.\/timelineDocumentDrag\.js"/,
|
||||||
@@ -346,6 +361,10 @@ export const rules = [
|
|||||||
pattern: /\bfunction (?:buildAllocationBlockClickInfo|buildAllocationMutationPlan)\b/,
|
pattern: /\bfunction (?:buildAllocationBlockClickInfo|buildAllocationMutationPlan)\b/,
|
||||||
message: "timeline drag must not re-inline extracted allocation action helper implementations",
|
message: "timeline drag must not re-inline extracted allocation action helper implementations",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
pattern: /\bfunction resolveAllocationRelease\b/,
|
||||||
|
message: "timeline drag must not re-inline extracted allocation release helper implementations",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
pattern: /\bfunction attachDocumentMouseDrag\b/,
|
pattern: /\bfunction attachDocumentMouseDrag\b/,
|
||||||
message: "timeline drag must not re-inline extracted document listener helper implementations",
|
message: "timeline drag must not re-inline extracted document listener helper implementations",
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ describe("architecture guardrails", () => {
|
|||||||
const allocationFinalizeRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineAllocationFinalize.ts");
|
const allocationFinalizeRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineAllocationFinalize.ts");
|
||||||
const allocationMultiDragRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineAllocationMultiDrag.ts");
|
const allocationMultiDragRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineAllocationMultiDrag.ts");
|
||||||
const allocationActionsRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineAllocationActions.ts");
|
const allocationActionsRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineAllocationActions.ts");
|
||||||
|
const allocationReleaseRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineAllocationRelease.ts");
|
||||||
const documentDragRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineDocumentDrag.ts");
|
const documentDragRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineDocumentDrag.ts");
|
||||||
const allocationDragStateRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineAllocationDragState.ts");
|
const allocationDragStateRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineAllocationDragState.ts");
|
||||||
const projectDragRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineProjectDrag.ts");
|
const projectDragRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineProjectDrag.ts");
|
||||||
@@ -91,6 +92,7 @@ describe("architecture guardrails", () => {
|
|||||||
assert.ok(allocationFinalizeRule);
|
assert.ok(allocationFinalizeRule);
|
||||||
assert.ok(allocationMultiDragRule);
|
assert.ok(allocationMultiDragRule);
|
||||||
assert.ok(allocationActionsRule);
|
assert.ok(allocationActionsRule);
|
||||||
|
assert.ok(allocationReleaseRule);
|
||||||
assert.ok(documentDragRule);
|
assert.ok(documentDragRule);
|
||||||
assert.ok(allocationDragStateRule);
|
assert.ok(allocationDragStateRule);
|
||||||
assert.ok(projectDragRule);
|
assert.ok(projectDragRule);
|
||||||
@@ -102,7 +104,7 @@ describe("architecture guardrails", () => {
|
|||||||
"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",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep optimistic allocation reconciliation delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep optimistic allocation reconciliation delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation drag completion rules delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation drag completion rules delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation click and mutation plan derivation delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation release classification delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep document mouse listener lifecycle delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep document mouse listener lifecycle delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation multi-drag rules delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation multi-drag rules delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation drag bootstrap delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation drag bootstrap delegated to the extracted helper module",
|
||||||
@@ -148,6 +150,11 @@ describe("architecture guardrails", () => {
|
|||||||
"apps/web/src/hooks/timelineAllocationActions.ts: missing guardrail anchor: timeline allocation action helpers must keep mutation plan derivation centralized",
|
"apps/web/src/hooks/timelineAllocationActions.ts: missing guardrail anchor: timeline allocation action helpers must keep mutation plan derivation centralized",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
assert.deepEqual(evaluateRule(allocationReleaseRule, ""), [
|
||||||
|
"apps/web/src/hooks/timelineAllocationRelease.ts: missing guardrail anchor: timeline allocation release helpers must keep release classification centralized",
|
||||||
|
"apps/web/src/hooks/timelineAllocationRelease.ts: missing guardrail anchor: timeline allocation release helpers must keep click and mutation plan derivation delegated to allocation action helpers",
|
||||||
|
]);
|
||||||
|
|
||||||
assert.deepEqual(evaluateRule(documentDragRule, ""), [
|
assert.deepEqual(evaluateRule(documentDragRule, ""), [
|
||||||
"apps/web/src/hooks/timelineDocumentDrag.ts: missing guardrail anchor: timeline document drag helpers must keep document mouse listener wiring centralized",
|
"apps/web/src/hooks/timelineDocumentDrag.ts: missing guardrail anchor: timeline document drag helpers must keep document mouse listener wiring centralized",
|
||||||
]);
|
]);
|
||||||
@@ -171,7 +178,7 @@ describe("architecture guardrails", () => {
|
|||||||
"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",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep optimistic allocation reconciliation delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep optimistic allocation reconciliation delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation drag completion rules delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation drag completion rules delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation click and mutation plan derivation delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation release classification delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep document mouse listener lifecycle delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep document mouse listener lifecycle delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation multi-drag rules delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation multi-drag rules delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation drag bootstrap delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep allocation drag bootstrap delegated to the extracted helper module",
|
||||||
|
|||||||
Reference in New Issue
Block a user