refactor(web): extract allocation multi-drag session
This commit is contained in:
@@ -0,0 +1,128 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import { beginAllocationMultiDragSession } from "./timelineAllocationMultiDragSession.js";
|
||||||
|
|
||||||
|
type TestDragMode = "move" | "resize-end";
|
||||||
|
|
||||||
|
type TestMultiDragState = {
|
||||||
|
selectedAllocationIds: string[];
|
||||||
|
multiDragDaysDelta: number;
|
||||||
|
isMultiDragging: boolean;
|
||||||
|
multiDragMode: TestDragMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("timelineAllocationMultiDragSession", () => {
|
||||||
|
it("starts the session, updates on day-bucket changes, and completes with selected ids", () => {
|
||||||
|
const previousCleanup = vi.fn();
|
||||||
|
const setState = vi.fn();
|
||||||
|
const onComplete = vi.fn();
|
||||||
|
const cleanupRef = { current: previousCleanup as (() => void) | null };
|
||||||
|
const initialState: TestMultiDragState = {
|
||||||
|
selectedAllocationIds: ["a", "b"],
|
||||||
|
multiDragDaysDelta: 0,
|
||||||
|
isMultiDragging: false,
|
||||||
|
multiDragMode: "move",
|
||||||
|
};
|
||||||
|
const stateRef = {
|
||||||
|
current: initialState,
|
||||||
|
};
|
||||||
|
const handlers: {
|
||||||
|
move?: (event: MouseEvent) => void;
|
||||||
|
up?: (event: MouseEvent) => void;
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
beginAllocationMultiDragSession({
|
||||||
|
startMouseX: 100,
|
||||||
|
dragMode: "resize-end" as const,
|
||||||
|
documentTarget: {} as Document,
|
||||||
|
cleanupRef,
|
||||||
|
stateRef,
|
||||||
|
setState,
|
||||||
|
startState: (state, dragMode): TestMultiDragState => ({
|
||||||
|
...state,
|
||||||
|
isMultiDragging: true,
|
||||||
|
multiDragMode: dragMode,
|
||||||
|
multiDragDaysDelta: 0,
|
||||||
|
}),
|
||||||
|
updateState: (state, daysDelta) => (daysDelta === state.multiDragDaysDelta ? null : { ...state, multiDragDaysDelta: daysDelta }),
|
||||||
|
finalizeState: (state): TestMultiDragState => ({ ...state, isMultiDragging: false, multiDragDaysDelta: 0 }),
|
||||||
|
toDaysDelta: (deltaX) => Math.round(deltaX / 40),
|
||||||
|
onComplete,
|
||||||
|
attachDrag: (_documentTarget, onMove, onUp) => {
|
||||||
|
handlers.move = onMove;
|
||||||
|
handlers.up = onUp;
|
||||||
|
return vi.fn();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(previousCleanup).toHaveBeenCalledOnce();
|
||||||
|
expect(setState).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
expect.objectContaining({ isMultiDragging: true, multiDragMode: "resize-end", multiDragDaysDelta: 0 }),
|
||||||
|
);
|
||||||
|
|
||||||
|
handlers.move?.({ clientX: 180 } as MouseEvent);
|
||||||
|
expect(setState).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
expect.objectContaining({ isMultiDragging: true, multiDragDaysDelta: 2 }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const attachedCleanup = cleanupRef.current;
|
||||||
|
handlers.up?.({ clientX: 220 } as MouseEvent);
|
||||||
|
|
||||||
|
expect(attachedCleanup).toHaveBeenCalledOnce();
|
||||||
|
expect(cleanupRef.current).toBeNull();
|
||||||
|
expect(setState).toHaveBeenNthCalledWith(
|
||||||
|
3,
|
||||||
|
expect.objectContaining({ isMultiDragging: false, multiDragDaysDelta: 0 }),
|
||||||
|
);
|
||||||
|
expect(onComplete).toHaveBeenCalledWith(
|
||||||
|
3,
|
||||||
|
expect.objectContaining({ selectedAllocationIds: ["a", "b"], isMultiDragging: false, multiDragDaysDelta: 0 }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips redundant updates and suppresses completion callbacks for zero-delta releases", () => {
|
||||||
|
const setState = vi.fn();
|
||||||
|
const onComplete = vi.fn();
|
||||||
|
let upHandler: ((event: MouseEvent) => void) | undefined;
|
||||||
|
let moveHandler: ((event: MouseEvent) => void) | undefined;
|
||||||
|
const initialState: TestMultiDragState = {
|
||||||
|
selectedAllocationIds: ["a", "b"],
|
||||||
|
multiDragDaysDelta: 0,
|
||||||
|
isMultiDragging: false,
|
||||||
|
multiDragMode: "move",
|
||||||
|
};
|
||||||
|
|
||||||
|
beginAllocationMultiDragSession({
|
||||||
|
startMouseX: 100,
|
||||||
|
dragMode: "move" as const,
|
||||||
|
documentTarget: {} as Document,
|
||||||
|
cleanupRef: { current: null },
|
||||||
|
stateRef: {
|
||||||
|
current: initialState,
|
||||||
|
},
|
||||||
|
setState,
|
||||||
|
startState: (state, dragMode): TestMultiDragState => ({
|
||||||
|
...state,
|
||||||
|
isMultiDragging: true,
|
||||||
|
multiDragMode: dragMode,
|
||||||
|
multiDragDaysDelta: 0,
|
||||||
|
}),
|
||||||
|
updateState: (state, daysDelta) => (daysDelta === state.multiDragDaysDelta ? null : { ...state, multiDragDaysDelta: daysDelta }),
|
||||||
|
finalizeState: (state): TestMultiDragState => ({ ...state, isMultiDragging: false, multiDragDaysDelta: 0 }),
|
||||||
|
toDaysDelta: (deltaX) => Math.round(deltaX / 40),
|
||||||
|
onComplete,
|
||||||
|
attachDrag: (_documentTarget, onMove, onUp) => {
|
||||||
|
moveHandler = onMove;
|
||||||
|
upHandler = onUp;
|
||||||
|
return vi.fn();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
moveHandler?.({ clientX: 110 } as MouseEvent);
|
||||||
|
upHandler?.({ clientX: 110 } as MouseEvent);
|
||||||
|
|
||||||
|
expect(setState).toHaveBeenCalledTimes(2);
|
||||||
|
expect(onComplete).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
type MutableCurrent<T> = {
|
||||||
|
current: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AttachDocumentMouseDrag = (
|
||||||
|
documentTarget: Document,
|
||||||
|
onMove: (event: MouseEvent) => void,
|
||||||
|
onUp: (event: MouseEvent) => void,
|
||||||
|
) => () => void;
|
||||||
|
|
||||||
|
type MultiDragStateLike<TMode extends string = string> = {
|
||||||
|
selectedAllocationIds: string[];
|
||||||
|
multiDragDaysDelta: number;
|
||||||
|
isMultiDragging: boolean;
|
||||||
|
multiDragMode: TMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BeginAllocationMultiDragSessionParams<TState extends MultiDragStateLike, TMode extends string> = {
|
||||||
|
startMouseX: number;
|
||||||
|
dragMode: TMode;
|
||||||
|
documentTarget: Document;
|
||||||
|
cleanupRef: MutableCurrent<(() => void) | null>;
|
||||||
|
stateRef: MutableCurrent<TState>;
|
||||||
|
setState: (state: TState) => void;
|
||||||
|
startState: (state: TState, dragMode: TMode) => TState;
|
||||||
|
updateState: (state: TState, daysDelta: number) => TState | null;
|
||||||
|
finalizeState: (state: TState) => TState;
|
||||||
|
toDaysDelta: (deltaX: number) => number;
|
||||||
|
onComplete: (daysDelta: number, finalState: TState) => void;
|
||||||
|
attachDrag: AttachDocumentMouseDrag;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function beginAllocationMultiDragSession<TState extends MultiDragStateLike, TMode extends string>({
|
||||||
|
startMouseX,
|
||||||
|
dragMode,
|
||||||
|
documentTarget,
|
||||||
|
cleanupRef,
|
||||||
|
stateRef,
|
||||||
|
setState,
|
||||||
|
startState,
|
||||||
|
updateState,
|
||||||
|
finalizeState,
|
||||||
|
toDaysDelta,
|
||||||
|
onComplete,
|
||||||
|
attachDrag,
|
||||||
|
}: BeginAllocationMultiDragSessionParams<TState, TMode>) {
|
||||||
|
const initialState = startState(stateRef.current, dragMode);
|
||||||
|
stateRef.current = initialState;
|
||||||
|
setState(initialState);
|
||||||
|
cleanupRef.current?.();
|
||||||
|
|
||||||
|
function handleMove(event: MouseEvent) {
|
||||||
|
const updated = updateState(stateRef.current, toDaysDelta(event.clientX - startMouseX));
|
||||||
|
if (!updated) return;
|
||||||
|
|
||||||
|
stateRef.current = updated;
|
||||||
|
setState(updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUp(event: MouseEvent) {
|
||||||
|
cleanupRef.current?.();
|
||||||
|
cleanupRef.current = null;
|
||||||
|
|
||||||
|
const finalState = finalizeState(stateRef.current);
|
||||||
|
stateRef.current = finalState;
|
||||||
|
setState(finalState);
|
||||||
|
|
||||||
|
const finalDelta = toDaysDelta(event.clientX - startMouseX);
|
||||||
|
if (finalDelta !== 0) {
|
||||||
|
onComplete(finalDelta, finalState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupRef.current = attachDrag(documentTarget, handleMove, handleUp);
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
startAllocationMultiDrag,
|
startAllocationMultiDrag,
|
||||||
updateAllocationMultiDrag,
|
updateAllocationMultiDrag,
|
||||||
} from "./timelineAllocationMultiDrag.js";
|
} from "./timelineAllocationMultiDrag.js";
|
||||||
|
import { beginAllocationMultiDragSession } from "./timelineAllocationMultiDragSession.js";
|
||||||
import { resolveAllocationRelease } from "./timelineAllocationRelease.js";
|
import { resolveAllocationRelease } from "./timelineAllocationRelease.js";
|
||||||
import { createAllocationDragState } from "./timelineAllocationDragState.js";
|
import { createAllocationDragState } from "./timelineAllocationDragState.js";
|
||||||
import { cleanupTimelineDragState } from "./timelineDragCleanup.js";
|
import { cleanupTimelineDragState } from "./timelineDragCleanup.js";
|
||||||
@@ -593,43 +594,22 @@ export function useTimelineDrag({
|
|||||||
const isMultiSelected = isAllocationMultiSelected(ms, opts.allocationId);
|
const isMultiSelected = isAllocationMultiSelected(ms, opts.allocationId);
|
||||||
|
|
||||||
if (isMultiSelected) {
|
if (isMultiSelected) {
|
||||||
// ── Multi-drag: move/resize all selected allocations together ──
|
beginAllocationMultiDragSession({
|
||||||
const startMouseX = e.clientX;
|
startMouseX: e.clientX,
|
||||||
const dragMode = opts.mode;
|
dragMode: opts.mode,
|
||||||
const initialMultiDragState = startAllocationMultiDrag(ms, dragMode);
|
documentTarget: document,
|
||||||
|
cleanupRef: multiSelectCleanupRef,
|
||||||
setMultiSelectState(initialMultiDragState);
|
stateRef: multiSelectRef,
|
||||||
multiSelectRef.current = initialMultiDragState;
|
setState: setMultiSelectState,
|
||||||
multiSelectCleanupRef.current?.();
|
startState: startAllocationMultiDrag,
|
||||||
|
updateState: updateAllocationMultiDrag,
|
||||||
function handleMultiMove(ev: MouseEvent) {
|
finalizeState: finalizeAllocationMultiDrag,
|
||||||
const deltaX = ev.clientX - startMouseX;
|
toDaysDelta: (deltaX) => pixelsToDays(deltaX, cellWidthRef.current),
|
||||||
const daysDelta = pixelsToDays(deltaX, cellWidthRef.current);
|
onComplete: (daysDelta, finalized) => {
|
||||||
const updated = updateAllocationMultiDrag(multiSelectRef.current, daysDelta);
|
onMultiDragCompleteRef.current?.(daysDelta, opts.mode, finalized.selectedAllocationIds);
|
||||||
if (!updated) return;
|
},
|
||||||
|
attachDrag: attachDocumentMouseDrag,
|
||||||
setMultiSelectState(updated);
|
});
|
||||||
multiSelectRef.current = updated;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMultiUp(ev: MouseEvent) {
|
|
||||||
multiSelectCleanupRef.current?.();
|
|
||||||
multiSelectCleanupRef.current = null;
|
|
||||||
|
|
||||||
const finalDelta = pixelsToDays(ev.clientX - startMouseX, cellWidthRef.current);
|
|
||||||
const finalized = finalizeAllocationMultiDrag(multiSelectRef.current);
|
|
||||||
|
|
||||||
setMultiSelectState(finalized);
|
|
||||||
multiSelectRef.current = finalized;
|
|
||||||
|
|
||||||
if (finalDelta !== 0) {
|
|
||||||
// Pass IDs from ref to avoid stale closure in the callback
|
|
||||||
const ids = finalized.selectedAllocationIds;
|
|
||||||
onMultiDragCompleteRef.current?.(finalDelta, dragMode, ids);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
multiSelectCleanupRef.current = attachDocumentMouseDrag(document, handleMultiMove, handleMultiUp);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -251,6 +251,17 @@ export const rules = [
|
|||||||
],
|
],
|
||||||
forbidden: [],
|
forbidden: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
file: "apps/web/src/hooks/timelineAllocationMultiDragSession.ts",
|
||||||
|
maxLines: 90,
|
||||||
|
required: [
|
||||||
|
{
|
||||||
|
pattern: /\bexport function beginAllocationMultiDragSession\b/,
|
||||||
|
message: "timeline allocation multi-drag session helpers must keep document drag lifecycle centralized",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
forbidden: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
file: "apps/web/src/hooks/timelineAllocationActions.ts",
|
file: "apps/web/src/hooks/timelineAllocationActions.ts",
|
||||||
maxLines: 90,
|
maxLines: 90,
|
||||||
@@ -410,6 +421,10 @@ export const rules = [
|
|||||||
pattern: /from "\.\/timelineAllocationMultiDrag\.js"/,
|
pattern: /from "\.\/timelineAllocationMultiDrag\.js"/,
|
||||||
message: "timeline drag must keep allocation multi-drag rules delegated to the extracted helper module",
|
message: "timeline drag must keep allocation multi-drag rules delegated to the extracted helper module",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
pattern: /from "\.\/timelineAllocationMultiDragSession\.js"/,
|
||||||
|
message: "timeline drag must keep allocation multi-drag document session wiring delegated to the extracted helper module",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
pattern: /from "\.\/timelineAllocationDragState\.js"/,
|
pattern: /from "\.\/timelineAllocationDragState\.js"/,
|
||||||
message: "timeline drag must keep allocation drag bootstrap delegated to the extracted helper module",
|
message: "timeline drag must keep allocation drag bootstrap delegated to the extracted helper module",
|
||||||
@@ -460,6 +475,10 @@ export const rules = [
|
|||||||
pattern: /\bfunction (?:isAllocationMultiSelected|startAllocationMultiDrag|updateAllocationMultiDrag|finalizeAllocationMultiDrag)\b/,
|
pattern: /\bfunction (?:isAllocationMultiSelected|startAllocationMultiDrag|updateAllocationMultiDrag|finalizeAllocationMultiDrag)\b/,
|
||||||
message: "timeline drag must not re-inline extracted allocation multi-drag helper implementations",
|
message: "timeline drag must not re-inline extracted allocation multi-drag helper implementations",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
pattern: /\bfunction handleMulti(?:Move|Up)\b/,
|
||||||
|
message: "timeline drag must not re-inline extracted allocation multi-drag session handlers",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
pattern: /\bfunction createAllocationDragState\b/,
|
pattern: /\bfunction createAllocationDragState\b/,
|
||||||
message: "timeline drag must not re-inline extracted allocation drag bootstrap helpers",
|
message: "timeline drag must not re-inline extracted allocation drag bootstrap helpers",
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ describe("architecture guardrails", () => {
|
|||||||
const optimisticRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineOptimisticAllocations.ts");
|
const optimisticRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineOptimisticAllocations.ts");
|
||||||
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 allocationMultiDragSessionRule = rules.find(
|
||||||
|
(rule) => rule.file === "apps/web/src/hooks/timelineAllocationMultiDragSession.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 allocationReleaseRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineAllocationRelease.ts");
|
||||||
const cleanupRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineDragCleanup.ts");
|
const cleanupRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineDragCleanup.ts");
|
||||||
@@ -98,6 +101,7 @@ describe("architecture guardrails", () => {
|
|||||||
assert.ok(optimisticRule);
|
assert.ok(optimisticRule);
|
||||||
assert.ok(allocationFinalizeRule);
|
assert.ok(allocationFinalizeRule);
|
||||||
assert.ok(allocationMultiDragRule);
|
assert.ok(allocationMultiDragRule);
|
||||||
|
assert.ok(allocationMultiDragSessionRule);
|
||||||
assert.ok(allocationActionsRule);
|
assert.ok(allocationActionsRule);
|
||||||
assert.ok(allocationReleaseRule);
|
assert.ok(allocationReleaseRule);
|
||||||
assert.ok(cleanupRule);
|
assert.ok(cleanupRule);
|
||||||
@@ -121,6 +125,7 @@ describe("architecture guardrails", () => {
|
|||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep project and allocation drag position derivation delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep project and allocation drag position derivation 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 multi-drag document session wiring 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",
|
||||||
"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",
|
||||||
@@ -170,6 +175,10 @@ describe("architecture guardrails", () => {
|
|||||||
"apps/web/src/hooks/timelineAllocationMultiDrag.ts: missing guardrail anchor: timeline allocation multi-drag helpers must keep reset-on-release behavior centralized",
|
"apps/web/src/hooks/timelineAllocationMultiDrag.ts: missing guardrail anchor: timeline allocation multi-drag helpers must keep reset-on-release behavior centralized",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
assert.deepEqual(evaluateRule(allocationMultiDragSessionRule, ""), [
|
||||||
|
"apps/web/src/hooks/timelineAllocationMultiDragSession.ts: missing guardrail anchor: timeline allocation multi-drag session helpers must keep document drag lifecycle centralized",
|
||||||
|
]);
|
||||||
|
|
||||||
assert.deepEqual(evaluateRule(allocationActionsRule, "export function buildAllocationBlockClickInfo() {}\n"), [
|
assert.deepEqual(evaluateRule(allocationActionsRule, "export function buildAllocationBlockClickInfo() {}\n"), [
|
||||||
"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",
|
||||||
]);
|
]);
|
||||||
@@ -221,6 +230,7 @@ describe("architecture guardrails", () => {
|
|||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep project and allocation drag position derivation delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep project and allocation drag position derivation 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 multi-drag document session wiring 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",
|
||||||
"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",
|
||||||
|
|||||||
Reference in New Issue
Block a user