Enable roadmap structures to open, preview, and embed directly from chat and dashboard views. - Register roadmap views as native structures with chat-reference support. - Add composer previews, in-place dashboard embedding, and drag-safe interactions. - Document the native-structure plugin contract and add coverage across supported surfaces. Files changed: .changeset/fn-8859-roadmap-native-structure-embeds.md | 7 + docs/PLUGIN_AUTHORING.md | 1 + docs/dashboard-guide.md | 6 +- packages/dashboard/app/components/MessageComposer.tsx | 16 +- packages/dashboard/app/components/__tests__/MessageComposer.test.tsx | 102 ++++++++++- packages/dashboard/app/components/__tests__/overflowViewRegistry.test.tsx | 68 +++++++- packages/dashboard/app/components/dashboard/MainContent.tsx | 22 ++- packages/dashboard/app/components/dashboard/__tests__/MainContent.native-structure-roadmap.test.tsx | 188 +++++++++++++++++++++ packages/dashboard/app/components/nativeStructureChatRef.ts | 4 +- packages/dashboard/app/components/overflowViewRegistry.tsx | 34 ++-- packages/dashboard/app/plugins/types.ts | 10 +- packages/dashboard/app/utils/__tests__/nativeStructureDrag.test.ts | 46 +++-- packages/dashboard/app/utils/nativeStructureDrag.ts | 18 +- plugins/fusion-plugin-roadmap/src/dashboard-view.tsx | 7 +- plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.tsx | 22 ++- plugins/fusion-plugin-roadmap/src/dashboard/__tests__/RoadmapsView.test.tsx | 51 ++++++ 16 files changed, 560 insertions(+), 42 deletions(-) Fusion-Task-Id: FN-8859 Fusion-Task-Lineage: 99a7292b-2a06-4325-8131-ca74431214e3 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
67 lines
2.7 KiB
TypeScript
67 lines
2.7 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",
|
|
"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:<id> 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;
|
|
}
|