Files
fusion/packages/dashboard/app/utils/__tests__/nativeStructureDrag.test.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

57 lines
2.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { NativeStructureRef } from "@fusion/core";
import { hasNativeStructureDrag, NATIVE_STRUCTURE_DRAG_MIME, readNativeStructureRef, serializeNativeStructureRef } from "../nativeStructureDrag";
function transfer(): DataTransfer {
const data = new Map<string, string>();
return {
types: [] as unknown as DOMStringList,
effectAllowed: "none",
setData(type: string, value: string) {
data.set(type, value);
(this.types as unknown as string[]) = [...data.keys()];
},
getData(type: string) {
return data.get(type) ?? "";
},
} as unknown as DataTransfer;
}
const refs: NativeStructureRef[] = [
{ kind: "mission", id: "M-1" },
{ kind: "milestone", id: "MS-1" },
{ kind: "goal", id: "G-1" },
{ kind: "research-finding", id: "INS-1" },
{ kind: "eval-result", id: "E-1" },
];
describe("nativeStructureDrag", () => {
it.each(refs)("round-trips $kind references", (ref) => {
const dataTransfer = transfer();
serializeNativeStructureRef(dataTransfer, ref);
expect(readNativeStructureRef(dataTransfer)).toEqual(ref);
expect(dataTransfer.getData("text/plain")).toBe(`fusion://${ref.kind}/${ref.id}`);
expect(dataTransfer.effectAllowed).toBe("copy");
});
it("rejects missing or malformed payloads", () => {
const missing = transfer();
const malformed = transfer();
malformed.setData(NATIVE_STRUCTURE_DRAG_MIME, "not json");
const unsupported = transfer();
unsupported.setData(NATIVE_STRUCTURE_DRAG_MIME, JSON.stringify({ kind: "roadmap-item", id: "R-1" }));
expect(readNativeStructureRef(missing)).toBeNull();
expect(readNativeStructureRef(malformed)).toBeNull();
expect(readNativeStructureRef(unsupported)).toBeNull();
});
it("guards only its own custom MIME type", () => {
const nativeDrag = transfer();
nativeDrag.setData(NATIVE_STRUCTURE_DRAG_MIME, JSON.stringify(refs[0]));
const fileDrag = transfer();
fileDrag.setData("text/plain", "file");
expect(hasNativeStructureDrag(nativeDrag)).toBe(true);
expect(hasNativeStructureDrag(fileDrag)).toBe(false);
});
});