refactor(web): extract preview target setup
This commit is contained in:
@@ -0,0 +1,121 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { createAllocationPreviewSession, createProjectPreviewSession } from "./timelinePreviewSession.js";
|
||||||
|
|
||||||
|
function createFakeElement({
|
||||||
|
left = "0px",
|
||||||
|
width = "0px",
|
||||||
|
transform = "",
|
||||||
|
closest = () => null,
|
||||||
|
}: {
|
||||||
|
left?: string;
|
||||||
|
width?: string;
|
||||||
|
transform?: string;
|
||||||
|
closest?: () => HTMLElement | null;
|
||||||
|
} = {}): HTMLElement {
|
||||||
|
return {
|
||||||
|
style: { left, width, transform },
|
||||||
|
closest,
|
||||||
|
} as unknown as HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFakeElement(value: EventTarget | null | undefined): value is HTMLElement {
|
||||||
|
return Boolean(value && typeof value === "object" && "style" in value);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("timelinePreviewSession", () => {
|
||||||
|
it("returns null when project preview lookup finds nothing and no HTMLElement fallback exists", () => {
|
||||||
|
const session = createProjectPreviewSession({
|
||||||
|
projectId: "proj_1",
|
||||||
|
currentTarget: null,
|
||||||
|
cellWidth: 32,
|
||||||
|
queryProjectTargets: () => [],
|
||||||
|
isElement: isFakeElement,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(session).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to the current target when project preview lookup returns no matching elements", () => {
|
||||||
|
const currentTarget = createFakeElement({ left: "12px", width: "48px" });
|
||||||
|
|
||||||
|
const session = createProjectPreviewSession({
|
||||||
|
projectId: "proj_1",
|
||||||
|
currentTarget,
|
||||||
|
cellWidth: 32,
|
||||||
|
queryProjectTargets: () => [],
|
||||||
|
isElement: isFakeElement,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(session).toMatchObject({
|
||||||
|
mode: "move",
|
||||||
|
cellWidth: 32,
|
||||||
|
pointerDeltaX: 0,
|
||||||
|
daysDelta: 0,
|
||||||
|
frame: null,
|
||||||
|
});
|
||||||
|
expect(session?.targets).toHaveLength(1);
|
||||||
|
expect(session?.targets[0]).toMatchObject({
|
||||||
|
element: currentTarget,
|
||||||
|
baseLeft: 12,
|
||||||
|
baseWidth: 48,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps queried project preview targets authoritative when they exist", () => {
|
||||||
|
const queriedTarget = createFakeElement({ left: "20px", width: "64px" });
|
||||||
|
const fallbackTarget = createFakeElement({ left: "99px", width: "99px" });
|
||||||
|
|
||||||
|
const session = createProjectPreviewSession({
|
||||||
|
projectId: "proj_1",
|
||||||
|
currentTarget: fallbackTarget,
|
||||||
|
cellWidth: 16,
|
||||||
|
queryProjectTargets: () => [queriedTarget],
|
||||||
|
isElement: isFakeElement,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(session?.targets).toHaveLength(1);
|
||||||
|
expect(session?.targets[0]).toMatchObject({
|
||||||
|
element: queriedTarget,
|
||||||
|
baseLeft: 20,
|
||||||
|
baseWidth: 64,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null when allocation preview root cannot be resolved", () => {
|
||||||
|
const currentTarget = createFakeElement();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
createAllocationPreviewSession({
|
||||||
|
currentTarget,
|
||||||
|
cellWidth: 24,
|
||||||
|
resolveAllocationRoot: () => null,
|
||||||
|
}),
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("captures the nearest allocation preview root for allocation sessions", () => {
|
||||||
|
const root = createFakeElement({ left: "8px", width: "40px" });
|
||||||
|
const child = createFakeElement();
|
||||||
|
|
||||||
|
const session = createAllocationPreviewSession({
|
||||||
|
currentTarget: child,
|
||||||
|
mode: "resize-end",
|
||||||
|
cellWidth: 24,
|
||||||
|
resolveAllocationRoot: () => root,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(session).toMatchObject({
|
||||||
|
mode: "resize-end",
|
||||||
|
cellWidth: 24,
|
||||||
|
pointerDeltaX: 0,
|
||||||
|
daysDelta: 0,
|
||||||
|
frame: null,
|
||||||
|
});
|
||||||
|
expect(session?.targets).toHaveLength(1);
|
||||||
|
expect(session?.targets[0]).toMatchObject({
|
||||||
|
element: root,
|
||||||
|
baseLeft: 8,
|
||||||
|
baseWidth: 40,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import {
|
||||||
|
captureLivePreviewTargets,
|
||||||
|
type LivePreviewMode,
|
||||||
|
type LivePreviewSession,
|
||||||
|
type LivePreviewTarget,
|
||||||
|
} from "./timelineLivePreview.js";
|
||||||
|
|
||||||
|
function isHTMLElement(value: EventTarget | null | undefined): value is HTMLElement {
|
||||||
|
return typeof HTMLElement !== "undefined" && value instanceof HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveAllocationPreviewRoot(currentTarget: EventTarget | null | undefined): HTMLElement | null {
|
||||||
|
return isHTMLElement(currentTarget)
|
||||||
|
? currentTarget.closest<HTMLElement>('[data-timeline-drag-preview~="allocation"]')
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLivePreviewSession(
|
||||||
|
mode: LivePreviewMode,
|
||||||
|
cellWidth: number,
|
||||||
|
targets: LivePreviewTarget[],
|
||||||
|
): LivePreviewSession | null {
|
||||||
|
if (targets.length === 0) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
mode,
|
||||||
|
cellWidth,
|
||||||
|
targets,
|
||||||
|
pointerDeltaX: 0,
|
||||||
|
daysDelta: 0,
|
||||||
|
frame: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createProjectPreviewSession({
|
||||||
|
projectId,
|
||||||
|
currentTarget,
|
||||||
|
cellWidth,
|
||||||
|
queryProjectTargets = (nextProjectId: string) =>
|
||||||
|
document.querySelectorAll<HTMLElement>(
|
||||||
|
`[data-timeline-drag-preview~="project-shift"][data-timeline-project-id="${nextProjectId}"]`,
|
||||||
|
),
|
||||||
|
isElement = isHTMLElement,
|
||||||
|
}: {
|
||||||
|
projectId: string;
|
||||||
|
currentTarget?: EventTarget | null | undefined;
|
||||||
|
cellWidth: number;
|
||||||
|
queryProjectTargets?: (projectId: string) => Iterable<HTMLElement>;
|
||||||
|
isElement?: (value: EventTarget | null | undefined) => value is HTMLElement;
|
||||||
|
}): LivePreviewSession | null {
|
||||||
|
const targets = captureLivePreviewTargets(queryProjectTargets(projectId));
|
||||||
|
if (targets.length === 0 && isElement(currentTarget)) {
|
||||||
|
targets.push(...captureLivePreviewTargets([currentTarget]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLivePreviewSession("move", cellWidth, targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createAllocationPreviewSession({
|
||||||
|
currentTarget,
|
||||||
|
mode = "move",
|
||||||
|
cellWidth,
|
||||||
|
resolveAllocationRoot = resolveAllocationPreviewRoot,
|
||||||
|
}: {
|
||||||
|
currentTarget?: EventTarget | null | undefined;
|
||||||
|
mode?: LivePreviewMode;
|
||||||
|
cellWidth: number;
|
||||||
|
resolveAllocationRoot?: (currentTarget: EventTarget | null | undefined) => HTMLElement | null;
|
||||||
|
}): LivePreviewSession | null {
|
||||||
|
const root = resolveAllocationRoot(currentTarget);
|
||||||
|
const targets = root ? captureLivePreviewTargets([root]) : [];
|
||||||
|
return createLivePreviewSession(mode, cellWidth, targets);
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import { trpc } from "~/lib/trpc/client.js";
|
|||||||
import { useInvalidateTimeline } from "./useInvalidatePlanningViews.js";
|
import { useInvalidateTimeline } from "./useInvalidatePlanningViews.js";
|
||||||
import { pixelsToDays } from "~/components/timeline/dragMath.js";
|
import { pixelsToDays } from "~/components/timeline/dragMath.js";
|
||||||
import {
|
import {
|
||||||
captureLivePreviewTargets,
|
|
||||||
clearLivePreview,
|
clearLivePreview,
|
||||||
preserveLivePreview,
|
preserveLivePreview,
|
||||||
scheduleLivePreview,
|
scheduleLivePreview,
|
||||||
@@ -39,6 +38,7 @@ import {
|
|||||||
} from "./timelineMultiSelect.js";
|
} from "./timelineMultiSelect.js";
|
||||||
import { beginCanvasMultiSelectSession } from "./timelineMultiSelectSession.js";
|
import { beginCanvasMultiSelectSession } from "./timelineMultiSelectSession.js";
|
||||||
import { reconcileOptimisticEntries } from "./timelineOptimisticAllocations.js";
|
import { reconcileOptimisticEntries } from "./timelineOptimisticAllocations.js";
|
||||||
|
import { createAllocationPreviewSession, createProjectPreviewSession } from "./timelinePreviewSession.js";
|
||||||
import { resolveRangeSelectionCancel, resolveRangeSelectionRelease } from "./timelineRangeRelease.js";
|
import { resolveRangeSelectionCancel, resolveRangeSelectionRelease } from "./timelineRangeRelease.js";
|
||||||
import { createRangeSelectionState, updateRangeSelectionDraft } from "./timelineRangeSelection.js";
|
import { createRangeSelectionState, updateRangeSelectionDraft } from "./timelineRangeSelection.js";
|
||||||
import { type TouchCanvasPointerEvent, type TouchMouseDownEvent } from "./timelineTouchAdapters.js";
|
import { type TouchCanvasPointerEvent, type TouchMouseDownEvent } from "./timelineTouchAdapters.js";
|
||||||
@@ -283,50 +283,20 @@ export function useTimelineDrag({
|
|||||||
|
|
||||||
const setProjectPreviewTargets = useCallback((projectId: string, currentTarget?: EventTarget | null) => {
|
const setProjectPreviewTargets = useCallback((projectId: string, currentTarget?: EventTarget | null) => {
|
||||||
clearLivePreview(projectPreviewRef.current);
|
clearLivePreview(projectPreviewRef.current);
|
||||||
|
projectPreviewRef.current = createProjectPreviewSession({
|
||||||
const projectTargets = captureLivePreviewTargets(
|
projectId,
|
||||||
document.querySelectorAll<HTMLElement>(
|
currentTarget,
|
||||||
`[data-timeline-drag-preview~="project-shift"][data-timeline-project-id="${projectId}"]`,
|
cellWidth: cellWidthRef.current,
|
||||||
),
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (projectTargets.length === 0 && currentTarget instanceof HTMLElement) {
|
|
||||||
projectTargets.push(...captureLivePreviewTargets([currentTarget]));
|
|
||||||
}
|
|
||||||
|
|
||||||
projectPreviewRef.current =
|
|
||||||
projectTargets.length > 0
|
|
||||||
? {
|
|
||||||
mode: "move",
|
|
||||||
cellWidth: cellWidthRef.current,
|
|
||||||
targets: projectTargets,
|
|
||||||
pointerDeltaX: 0,
|
|
||||||
daysDelta: 0,
|
|
||||||
frame: null,
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setAllocationPreviewTarget = useCallback((currentTarget?: EventTarget | null, mode: AllocDragMode = "move") => {
|
const setAllocationPreviewTarget = useCallback((currentTarget?: EventTarget | null, mode: AllocDragMode = "move") => {
|
||||||
clearLivePreview(allocPreviewRef.current);
|
clearLivePreview(allocPreviewRef.current);
|
||||||
|
allocPreviewRef.current = createAllocationPreviewSession({
|
||||||
const root =
|
currentTarget,
|
||||||
currentTarget instanceof HTMLElement
|
mode,
|
||||||
? currentTarget.closest<HTMLElement>('[data-timeline-drag-preview~="allocation"]')
|
cellWidth: cellWidthRef.current,
|
||||||
: null;
|
});
|
||||||
const targets = root ? captureLivePreviewTargets([root]) : [];
|
|
||||||
|
|
||||||
allocPreviewRef.current =
|
|
||||||
targets.length > 0
|
|
||||||
? {
|
|
||||||
mode,
|
|
||||||
cellWidth: cellWidthRef.current,
|
|
||||||
targets,
|
|
||||||
pointerDeltaX: 0,
|
|
||||||
daysDelta: 0,
|
|
||||||
frame: null,
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const updateLivePreview = useCallback(
|
const updateLivePreview = useCallback(
|
||||||
|
|||||||
@@ -255,6 +255,25 @@ export const rules = [
|
|||||||
],
|
],
|
||||||
forbidden: [],
|
forbidden: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
file: "apps/web/src/hooks/timelinePreviewSession.ts",
|
||||||
|
maxLines: 80,
|
||||||
|
required: [
|
||||||
|
{
|
||||||
|
pattern: /\bexport function createProjectPreviewSession\b/,
|
||||||
|
message: "timeline preview session helpers must keep project preview target resolution centralized",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: /\bexport function createAllocationPreviewSession\b/,
|
||||||
|
message: "timeline preview session helpers must keep allocation preview target resolution centralized",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: /from "\.\/timelineLivePreview\.js"/,
|
||||||
|
message: "timeline preview session helpers must keep target capture delegated to the live preview helper module",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
forbidden: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
file: "apps/web/src/hooks/timelineAllocationFinalize.ts",
|
file: "apps/web/src/hooks/timelineAllocationFinalize.ts",
|
||||||
maxLines: 100,
|
maxLines: 100,
|
||||||
@@ -492,6 +511,10 @@ export const rules = [
|
|||||||
pattern: /from "\.\/timelineOptimisticAllocations\.js"/,
|
pattern: /from "\.\/timelineOptimisticAllocations\.js"/,
|
||||||
message: "timeline drag must keep optimistic allocation reconciliation delegated to the extracted helper module",
|
message: "timeline drag must keep optimistic allocation reconciliation delegated to the extracted helper module",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
pattern: /from "\.\/timelinePreviewSession\.js"/,
|
||||||
|
message: "timeline drag must keep preview target setup delegated to the extracted helper module",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
pattern: /from "\.\/timelineDragCleanup\.js"/,
|
pattern: /from "\.\/timelineDragCleanup\.js"/,
|
||||||
message: "timeline drag must keep unmount teardown delegated to the extracted helper module",
|
message: "timeline drag must keep unmount teardown delegated to the extracted helper module",
|
||||||
@@ -594,6 +617,14 @@ export const rules = [
|
|||||||
pattern: /\bconst mutationInput = buildProjectShiftMutationInput\(finalDrag\)\b[\s\S]*applyShiftMutation\.(?:mutate|mutateAsync)\(/,
|
pattern: /\bconst mutationInput = buildProjectShiftMutationInput\(finalDrag\)\b[\s\S]*applyShiftMutation\.(?:mutate|mutateAsync)\(/,
|
||||||
message: "timeline drag must not re-inline extracted project drag finalize flow",
|
message: "timeline drag must not re-inline extracted project drag finalize flow",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
pattern: /\bdocument\.querySelectorAll<HTMLElement>\([\s\S]*data-timeline-project-id/,
|
||||||
|
message: "timeline drag must not re-inline extracted project preview target lookup",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: /\bcurrentTarget\.closest<HTMLElement>\('\[data-timeline-drag-preview~=\"allocation\"\]'\)/,
|
||||||
|
message: "timeline drag must not re-inline extracted allocation preview target lookup",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
pattern: /\bconst selection = finalizeRangeSelection\(/,
|
pattern: /\bconst selection = finalizeRangeSelection\(/,
|
||||||
message: "timeline drag must not re-inline extracted range release resolution",
|
message: "timeline drag must not re-inline extracted range release resolution",
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ describe("architecture guardrails", () => {
|
|||||||
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");
|
||||||
const rangeReleaseRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineRangeRelease.ts");
|
const rangeReleaseRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelineRangeRelease.ts");
|
||||||
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 previewSessionRule = rules.find((rule) => rule.file === "apps/web/src/hooks/timelinePreviewSession.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(
|
const allocationMultiDragSessionRule = rules.find(
|
||||||
@@ -110,6 +111,7 @@ describe("architecture guardrails", () => {
|
|||||||
assert.ok(rangeRule);
|
assert.ok(rangeRule);
|
||||||
assert.ok(rangeReleaseRule);
|
assert.ok(rangeReleaseRule);
|
||||||
assert.ok(optimisticRule);
|
assert.ok(optimisticRule);
|
||||||
|
assert.ok(previewSessionRule);
|
||||||
assert.ok(allocationFinalizeRule);
|
assert.ok(allocationFinalizeRule);
|
||||||
assert.ok(allocationMultiDragRule);
|
assert.ok(allocationMultiDragRule);
|
||||||
assert.ok(allocationMultiDragSessionRule);
|
assert.ok(allocationMultiDragSessionRule);
|
||||||
@@ -133,6 +135,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 range release and cancel delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep range release and cancel 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 preview target setup delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep unmount teardown delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep unmount teardown 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 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",
|
||||||
@@ -192,6 +195,11 @@ describe("architecture guardrails", () => {
|
|||||||
"apps/web/src/hooks/timelineOptimisticAllocations.ts: missing guardrail anchor: timeline optimistic helpers must keep server-reconciliation logic centralized",
|
"apps/web/src/hooks/timelineOptimisticAllocations.ts: missing guardrail anchor: timeline optimistic helpers must keep server-reconciliation logic centralized",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
assert.deepEqual(evaluateRule(previewSessionRule, "export function createProjectPreviewSession() {}\n"), [
|
||||||
|
"apps/web/src/hooks/timelinePreviewSession.ts: missing guardrail anchor: timeline preview session helpers must keep allocation preview target resolution centralized",
|
||||||
|
"apps/web/src/hooks/timelinePreviewSession.ts: missing guardrail anchor: timeline preview session helpers must keep target capture delegated to the live preview helper module",
|
||||||
|
]);
|
||||||
|
|
||||||
assert.deepEqual(evaluateRule(allocationFinalizeRule, "export function hasAllocationDateChange() {}\n"), [
|
assert.deepEqual(evaluateRule(allocationFinalizeRule, "export function hasAllocationDateChange() {}\n"), [
|
||||||
"apps/web/src/hooks/timelineAllocationFinalize.ts: missing guardrail anchor: timeline allocation finalize helpers must keep click-vs-drag classification centralized",
|
"apps/web/src/hooks/timelineAllocationFinalize.ts: missing guardrail anchor: timeline allocation finalize helpers must keep click-vs-drag classification centralized",
|
||||||
"apps/web/src/hooks/timelineAllocationFinalize.ts: missing guardrail anchor: timeline allocation finalize helpers must keep segment extraction rules centralized",
|
"apps/web/src/hooks/timelineAllocationFinalize.ts: missing guardrail anchor: timeline allocation finalize helpers must keep segment extraction rules centralized",
|
||||||
@@ -269,6 +277,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 range release and cancel delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep range release and cancel 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 preview target setup delegated to the extracted helper module",
|
||||||
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep unmount teardown delegated to the extracted helper module",
|
"apps/web/src/hooks/useTimelineDrag.ts: missing guardrail anchor: timeline drag must keep unmount teardown 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 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",
|
||||||
|
|||||||
Reference in New Issue
Block a user