diff --git a/mcp-server/src/state.js b/mcp-server/src/state.js index d71ac7c..abadc65 100644 --- a/mcp-server/src/state.js +++ b/mcp-server/src/state.js @@ -330,7 +330,7 @@ export class FlowState { // ── Connection Operations ─────────────────── addConnection(options) { - const { fromScreenId, toScreenId, label, action, hotspotId, connectionPath, condition, conditionGroupId, transitionType, transitionLabel } = options; + const { fromScreenId, toScreenId, label, action, hotspotId, connectionPath, condition, conditionGroupId, transitionType, transitionLabel, dataFlow } = options; if (!this.getScreen(fromScreenId)) throw new Error(`Source screen not found: ${fromScreenId}`); if (!this.getScreen(toScreenId)) throw new Error(`Target screen not found: ${toScreenId}`); @@ -355,6 +355,7 @@ export class FlowState { conditionGroupId: conditionGroupId || null, transitionType: transitionType || null, transitionLabel: transitionLabel || "", + dataFlow: dataFlow || [], }; this.connections.push(conn); @@ -368,7 +369,7 @@ export class FlowState { const allowed = [ "label", "action", "fromScreenId", "toScreenId", "connectionPath", "condition", "conditionGroupId", - "transitionType", "transitionLabel", + "transitionType", "transitionLabel", "dataFlow", ]; for (const key of allowed) { if (updates[key] !== undefined) { diff --git a/mcp-server/src/tools/connection-tools.js b/mcp-server/src/tools/connection-tools.js index 86b9bcc..d1041fa 100644 --- a/mcp-server/src/tools/connection-tools.js +++ b/mcp-server/src/tools/connection-tools.js @@ -12,6 +12,19 @@ export const connectionTools = [ condition: { type: "string", description: "Condition text for conditional connections" }, conditionGroupId: { type: "string", description: "Group ID for conditional branch connections" }, transitionType: { type: "string", description: "Transition animation type" }, + data_flow: { + type: "array", + description: "Data items passed along this connection", + items: { + type: "object", + properties: { + name: { type: "string", description: "Parameter name (e.g. 'productId')" }, + type: { type: "string", description: "Data type (String, Int, Bool, Object, Array, Date, ID, or custom)" }, + description: { type: "string", description: "What this parameter represents" }, + }, + required: ["name"], + }, + }, }, required: ["fromScreenId", "toScreenId"], }, @@ -28,6 +41,19 @@ export const connectionTools = [ condition: { type: "string" }, transitionType: { type: "string" }, transitionLabel: { type: "string" }, + data_flow: { + type: "array", + description: "Data items passed along this connection", + items: { + type: "object", + properties: { + name: { type: "string", description: "Parameter name" }, + type: { type: "string", description: "Data type" }, + description: { type: "string", description: "What this parameter represents" }, + }, + required: ["name"], + }, + }, }, required: ["connectionId"], }, @@ -53,16 +79,29 @@ export const connectionTools = [ }, ]; +function convertDataFlow(dataFlowSnake) { + if (!Array.isArray(dataFlowSnake)) return undefined; + return dataFlowSnake.map(item => ({ + id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + name: item.name || "", + type: item.type || "String", + description: item.description || "", + })); +} + export function handleConnectionTool(name, args, state) { switch (name) { case "create_connection": { - const conn = state.addConnection(args); + const { data_flow, ...rest } = args; + const dataFlow = convertDataFlow(data_flow); + const conn = state.addConnection({ ...rest, ...(dataFlow ? { dataFlow } : {}) }); return { connectionId: conn.id, fromScreenId: conn.fromScreenId, toScreenId: conn.toScreenId }; } case "update_connection": { - const { connectionId, ...updates } = args; - const conn = state.updateConnection(connectionId, updates); + const { connectionId, data_flow, ...updates } = args; + const dataFlow = convertDataFlow(data_flow); + const conn = state.updateConnection(connectionId, { ...updates, ...(dataFlow ? { dataFlow } : {}) }); return { success: true, connectionId: conn.id }; } diff --git a/src/components/ConnectionEditModal.jsx b/src/components/ConnectionEditModal.jsx index 84f6f54..984d81a 100644 --- a/src/components/ConnectionEditModal.jsx +++ b/src/components/ConnectionEditModal.jsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { COLORS, styles } from "../styles/theme"; import { generateId } from "../utils/generateId"; +import { DataFlowEditor } from "./DataFlowEditor"; export function ConnectionEditModal({ connection, groupConnections, screens, fromScreen, onSave, onDelete, onClose }) { const isConditional = groupConnections.length > 1 || !!connection.conditionGroupId; @@ -10,6 +11,7 @@ export function ConnectionEditModal({ connection, groupConnections, screens, fro const [targetId, setTargetId] = useState(connection.toScreenId || ""); const [transitionType, setTransitionType] = useState(connection.transitionType || ""); const [transitionLabel, setTransitionLabel] = useState(connection.transitionLabel || ""); + const [dataFlow, setDataFlow] = useState(connection.dataFlow || []); const [conditions, setConditions] = useState(() => { if (isConditional) { @@ -17,9 +19,10 @@ export function ConnectionEditModal({ connection, groupConnections, screens, fro id: c.id, label: c.condition || c.label || "", targetScreenId: c.toScreenId || "", + dataFlow: c.dataFlow || [], })); } - return [{ id: generateId(), label: "", targetScreenId: connection.toScreenId || "" }]; + return [{ id: generateId(), label: "", targetScreenId: connection.toScreenId || "", dataFlow: [] }]; }); const otherScreens = screens.filter((s) => s.id !== fromScreen.id); @@ -35,6 +38,7 @@ export function ConnectionEditModal({ connection, groupConnections, screens, fro conditionGroupId: connection.conditionGroupId || null, transitionType: transitionType || null, transitionLabel: transitionType === "custom" ? transitionLabel : "", + dataFlow: mode === "navigate" ? dataFlow : [], }); }; @@ -103,6 +107,8 @@ export function ConnectionEditModal({ connection, groupConnections, screens, fro ))} + + )} @@ -126,65 +132,76 @@ export function ConnectionEditModal({ connection, groupConnections, screens, fro {conditions.map((cond, i) => ( -
-
+ ))} + + + + ); +} diff --git a/src/components/HotspotModal.jsx b/src/components/HotspotModal.jsx index 4265e19..0ce96f6 100644 --- a/src/components/HotspotModal.jsx +++ b/src/components/HotspotModal.jsx @@ -1,9 +1,10 @@ import { useState, useEffect } from "react"; import { COLORS, FONTS, styles } from "../styles/theme"; import { generateId } from "../utils/generateId"; +import { DataFlowEditor } from "./DataFlowEditor"; function FollowUpSection({ title, titleColor, action, setAction, targetId, setTargetId, - customDesc, setCustomDesc, otherScreens }) { + customDesc, setCustomDesc, otherScreens, dataFlow, onDataFlowChange }) { return (
{(action === "navigate" || action === "modal") && ( - + <> + + {dataFlow && onDataFlowChange && ( +
+ +
+ )} + )} {action === "custom" && ( @@ -105,6 +113,11 @@ export function HotspotModal({ screen, hotspot, connection, screens, documents = const [onErrorTargetId, setOnErrorTargetId] = useState(hotspot?.onErrorTargetId || ""); const [onErrorCustomDesc, setOnErrorCustomDesc] = useState(hotspot?.onErrorCustomDesc || ""); + // Data flow fields + const [dataFlow, setDataFlow] = useState(hotspot?.dataFlow || []); + const [onSuccessDataFlow, setOnSuccessDataFlow] = useState(hotspot?.onSuccessDataFlow || []); + const [onErrorDataFlow, setOnErrorDataFlow] = useState(hotspot?.onErrorDataFlow || []); + // Conditional branching fields const [conditions, setConditions] = useState( hotspot?.conditions?.length > 0 @@ -276,6 +289,9 @@ export function HotspotModal({ screen, hotspot, connection, screens, documents = onErrorAction, onErrorTargetId: onErrorTargetId || null, onErrorCustomDesc, + dataFlow: (action === "navigate" || action === "modal") ? dataFlow : [], + onSuccessDataFlow: action === "api" ? onSuccessDataFlow : [], + onErrorDataFlow: action === "api" ? onErrorDataFlow : [], conditions: action === "conditional" ? conditions : [], x, y, w, h, transitionType, @@ -365,65 +381,76 @@ export function HotspotModal({ screen, hotspot, connection, screens, documents =
{conditions.map((cond, i) => ( -
-