import type { NativeStructureRef } from "@fusion/core"; export const NATIVE_STRUCTURE_DRAG_MIME = "application/x-fusion-native-structure"; const NATIVE_STRUCTURE_KINDS: readonly NativeStructureRef["kind"][] = [ "mission", "milestone", "goal", "research-finding", "eval-result", "roadmap-item", ]; /** * FNXC:NativeStructureEmbed 2026-08-09-05:13: * Mail and every native-structure owner exchange this one DataTransfer protocol rather than * inventing per-view payloads. It accepts every persisted preview kind, including roadmap items. */ export function serializeNativeStructureRef(dataTransfer: DataTransfer, ref: NativeStructureRef): void { dataTransfer.setData(NATIVE_STRUCTURE_DRAG_MIME, JSON.stringify(ref)); dataTransfer.setData("text/plain", `fusion://${ref.kind}/${ref.id}`); dataTransfer.effectAllowed = "copy"; } /** * FNXC:RoadmapNativeStructureDrag 2026-08-09-05:13: * Roadmap feature rows already own text/plain for feature: reordering, so this helper writes * only the host MIME and preserves their payload and effectAllowed. Its result lets a row that is * draggable for reordering widen effectAllowed only when a native structure payload was attached. */ export function attachNativeStructureRefToDrag(dataTransfer: DataTransfer, ref: NativeStructureRef): boolean { if (!isNativeStructureDragEnabled()) return false; dataTransfer.setData(NATIVE_STRUCTURE_DRAG_MIME, JSON.stringify(ref)); return true; } /** Reads and validates an untrusted browser drag payload. */ export function readNativeStructureRef(dataTransfer: DataTransfer): NativeStructureRef | null { try { const parsed: unknown = JSON.parse(dataTransfer.getData(NATIVE_STRUCTURE_DRAG_MIME)); if ( typeof parsed !== "object" || parsed === null || !NATIVE_STRUCTURE_KINDS.includes((parsed as NativeStructureRef).kind) || typeof (parsed as NativeStructureRef).id !== "string" || !(parsed as NativeStructureRef).id.trim() || ((parsed as NativeStructureRef).projectId !== undefined && typeof (parsed as NativeStructureRef).projectId !== "string") ) { return null; } const { kind, id, projectId } = parsed as NativeStructureRef; return projectId === undefined ? { kind, id } : { kind, id, projectId }; } catch { return null; } } /** True only for drags emitted by serializeNativeStructureRef. */ export function hasNativeStructureDrag(dataTransfer: DataTransfer): boolean { return Array.from(dataTransfer.types).includes(NATIVE_STRUCTURE_DRAG_MIME); } /** Native HTML dragging on touch-primary devices competes with scrolling; use the composer picker there. */ export function isNativeStructureDragEnabled(): boolean { return typeof window === "undefined" || !window.matchMedia("(pointer: coarse)").matches; }