Files
fusion/packages/dashboard/app/utils/nativeStructureDrag.ts
gsxdsm e30f6d851f FN-8293: add native structure drag-and-draft mail composition
Mail composers can attach native structures by drag-and-drop and generate report narratives with AI.

- Add a shared native-structure drag protocol and attachable previews across Mail source views.
- Add a disposable compose-chat panel that drafts narratives around attached embeds.
- Cover drag behavior and compose-chat session lifecycle, and document the Mail interaction.

Files changed:
 .changeset/fn-8293-mail-drag-compose-chat.md       |   7 ++
 docs/dashboard-guide.md                            |   3 +
 .../dashboard/app/components/ComposeChatPanel.css  |  33 ++++++
 .../dashboard/app/components/ComposeChatPanel.tsx  | 117 +++++++++++++++++++++
 packages/dashboard/app/components/EvalsView.tsx    |   3 +-
 packages/dashboard/app/components/GoalsView.tsx    |   6 ++
 packages/dashboard/app/components/InsightsView.tsx |   8 +-
 .../dashboard/app/components/MessageComposer.css   |  22 ++++
 .../dashboard/app/components/MessageComposer.tsx   |  92 ++++++++++++++--
 .../dashboard/app/components/MissionManager.tsx    |  11 +-
 .../components/__tests__/ComposeChatPanel.test.tsx | 102 ++++++++++++++++++
 .../components/__tests__/MessageComposer.test.tsx  |  25 +++++
 .../utils/__tests__/nativeStructureDrag.test.ts    |  56 ++++++++++
 .../dashboard/app/utils/nativeStructureDrag.ts     |  54 ++++++++++
 14 files changed, 525 insertions(+), 14 deletions(-)

Fusion-Task-Id: FN-8293
Fusion-Task-Lineage: 9864e76c-2a0d-41f6-b7f2-de8dd63710b5
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-18 20:30:42 -07:00

55 lines
2.2 KiB
TypeScript

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",
];
/**
* FNXC:NativeStructureEmbed 2026-07-22-10:30:
* Mail and every native-structure owner exchange this one DataTransfer protocol rather than
* inventing per-view payloads. It intentionally accepts only the five persisted preview kinds:
* mission, milestone, goal, research-finding, and eval-result.
*/
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";
}
/** 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;
}