Skip to main content

🎯 N8N Killer Implementation Plan - EXECUTE THE VISION

Mission: Transform Workflow Studio into the most powerful workflow automation platform by implementing content picking, data mapping, and real-time validation.


βœ… Phase 1: Backend Foundation (Week 1)​

Day 1-2: Core Services​

Task 1.1: WorkflowIntegrationRegistryService​

File: /valkyrai/src/main/java/com/valkyrlabs/workflow/service/WorkflowIntegrationRegistryService.java

@Service
public class WorkflowIntegrationRegistryService {
@Autowired
private WorkflowRepository workflowRepo;

@Autowired
private IntegrationAccountRepository integrationAccountRepo;

/**
* Get all IntegrationAccounts referenced by workflow's ExecModules
*/
public List<IntegrationAccount> getWorkflowIntegrationAccounts(UUID workflowId) {
Workflow workflow = workflowRepo.findById(workflowId)
.orElseThrow(() -> new ResourceNotFoundException("Workflow not found"));

Set<IntegrationAccount> accounts = new HashSet<>();

for (Task task : workflow.getTasks()) {
if (task.getModules() != null) {
for (ExecModule module : task.getModules()) {
if (module.getIntegrationAccount() != null) {
accounts.add(module.getIntegrationAccount());
}
}
}
}

return new ArrayList<>(accounts);
}

/**
* Get all API/data sources defined in workflow's OpenAPI spec
*/
public List<ApiSource> getWorkflowApiSources(UUID workflowId) {
Workflow workflow = workflowRepo.findById(workflowId)
.orElseThrow(() -> new ResourceNotFoundException("Workflow not found"));

if (workflow.getSpecs() == null || workflow.getSpecs().isEmpty()) {
return Collections.emptyList();
}

// Parse first spec (main API definition)
OasOpenAPISpec spec = workflow.getSpecs().get(0);
List<ApiSource> sources = new ArrayList<>();

if (spec.getPaths() != null) {
for (OasPath path : spec.getPaths()) {
// Extract operations from path
List<ApiOperation> operations = new ArrayList<>();

if (path.getGet() != null) {
operations.add(createApiOperation("GET", path.getGet()));
}
if (path.getPost() != null) {
operations.add(createApiOperation("POST", path.getPost()));
}
if (path.getPut() != null) {
operations.add(createApiOperation("PUT", path.getPut()));
}
if (path.getDelete() != null) {
operations.add(createApiOperation("DELETE", path.getDelete()));
}

sources.add(new ApiSource(
path.getPath(),
path.getSummary(),
path.getDescription(),
operations
));
}
}

return sources;
}

private ApiOperation createApiOperation(String method, OasOperation operation) {
return new ApiOperation(
method,
operation.getOperationId(),
operation.getSummary(),
operation.getDescription(),
extractResponseSchema(operation)
);
}

private String extractResponseSchema(OasOperation operation) {
// Extract JSON schema from 200 response
if (operation.getResponses() != null) {
for (OasResponse response : operation.getResponses()) {
if ("200".equals(response.getStatusCode())) {
// Return schema as JSON string
return response.getContent() != null ?
JSON.stringify(response.getContent()) : null;
}
}
}
return null;
}
}

// DTO Classes
public class ApiSource {
private String path;
private String summary;
private String description;
private List<ApiOperation> operations;

// constructors, getters, setters
}

public class ApiOperation {
private String method;
private String operationId;
private String summary;
private String description;
private String responseSchema;

// constructors, getters, setters
}

Status: ⬜ Not Started


Task 1.2: WorkflowValidationService​

File: /valkyrai/src/main/java/com/valkyrlabs/workflow/service/WorkflowValidationService.java

@Service
public class WorkflowValidationService {
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{\\{([^}]+)\\}\\}");

@Autowired
private ExecModuleCatalog catalog;

/**
* Validate workflow and return actionable errors
*/
public WorkflowValidationResult validate(Workflow workflow) {
List<ValidationError> errors = new ArrayList<>();

// Rule 1: Missing IntegrationAccount
errors.addAll(validateIntegrationAccounts(workflow));

// Rule 2: Invalid variable references
errors.addAll(validateVariableReferences(workflow));

// Rule 3: Missing required fields
errors.addAll(validateRequiredFields(workflow));

// Rule 4: Type mismatches
errors.addAll(validateTypeMismatches(workflow));

// Rule 5: Unreachable tasks
errors.addAll(validateTaskReachability(workflow));

// Rule 6: Circular dependencies
errors.addAll(validateCircularDependencies(workflow));

return new WorkflowValidationResult(
errors.stream().filter(e -> "error".equals(e.getSeverity())).toList(),
errors.stream().filter(e -> "warning".equals(e.getSeverity())).toList(),
generateSuggestions(workflow)
);
}

private List<ValidationError> validateIntegrationAccounts(Workflow workflow) {
List<ValidationError> errors = new ArrayList<>();

for (Task task : workflow.getTasks()) {
if (task.getModules() != null) {
for (ExecModule module : task.getModules()) {
ExecModuleMetadata meta = catalog.getMetadata(module.getClassName());

if (meta != null && meta.requiresIntegrationAccount()) {
if (module.getIntegrationAccount() == null) {
errors.add(ValidationError.error(
task.getId(),
module.getId(),
"integrationAccount",
"Module '" + (module.getName() != null ? module.getName() : meta.getTitle()) +
"' requires an IntegrationAccount",
createSelectAccountQuickFix(module)
));
}
}
}
}
}

return errors;
}

private List<ValidationError> validateVariableReferences(Workflow workflow) {
List<ValidationError> errors = new ArrayList<>();

// Build map of available module outputs
Map<String, Set<String>> moduleOutputs = buildModuleOutputsMap(workflow);

for (Task task : workflow.getTasks()) {
if (task.getModules() != null) {
for (ExecModule module : task.getModules()) {
String moduleData = module.getModuleData();
if (moduleData != null) {
List<String> refs = extractVariableReferences(moduleData);

for (String ref : refs) {
if (!isValidReference(ref, moduleOutputs)) {
errors.add(ValidationError.error(
task.getId(),
module.getId(),
"moduleData",
"Invalid variable reference: {{" + ref + "}}",
createPickVariableQuickFix(module, ref)
));
}
}
}
}
}
}

return errors;
}

/**
* Extract {{variable}} references from JSON string
*/
public List<String> extractVariableReferences(String json) {
List<String> refs = new ArrayList<>();
Matcher matcher = VARIABLE_PATTERN.matcher(json);

while (matcher.find()) {
refs.add(matcher.group(1));
}

return refs;
}

private boolean isValidReference(String ref, Map<String, Set<String>> moduleOutputs) {
// Parse reference: "moduleName.outputField" or "moduleName.output.nested.field"
String[] parts = ref.split("\\.", 2);
if (parts.length < 2) {
return false;
}

String moduleName = parts[0];
String fieldPath = parts[1];

// Check if module exists and has the output field
return moduleOutputs.containsKey(moduleName) &&
moduleOutputs.get(moduleName).contains(fieldPath.split("\\.")[0]);
}

private Map<String, Set<String>> buildModuleOutputsMap(Workflow workflow) {
Map<String, Set<String>> outputs = new HashMap<>();

for (Task task : workflow.getTasks()) {
if (task.getModules() != null) {
for (ExecModule module : task.getModules()) {
String moduleName = module.getName() != null ?
module.getName() : module.getClassName();

ExecModuleMetadata meta = catalog.getMetadata(module.getClassName());
if (meta != null && meta.getOutputs() != null) {
outputs.put(moduleName, meta.getOutputs().keySet());
}
}
}
}

return outputs;
}

// Other validation methods...
}

// DTO Classes
public class WorkflowValidationResult {
private List<ValidationError> errors;
private List<ValidationError> warnings;
private List<String> suggestions;

public boolean isValid() {
return errors == null || errors.isEmpty();
}

// constructors, getters, setters
}

public class ValidationError {
private String severity; // "error", "warning", "info"
private UUID taskId;
private UUID moduleId;
private String field;
private String message;
private QuickFix quickFix;

public static ValidationError error(UUID taskId, UUID moduleId, String field, String message, QuickFix quickFix) {
return new ValidationError("error", taskId, moduleId, field, message, quickFix);
}

// constructors, getters, setters
}

public class QuickFix {
private String label;
private String action; // Action identifier for frontend
private Map<String, Object> params;

// constructors, getters, setters
}

Status: ⬜ Not Started


Task 1.3: WorkflowVariableResolver​

File: /valkyrai/src/main/java/com/valkyrlabs/workflow/service/WorkflowVariableResolver.java

@Service
public class WorkflowVariableResolver {
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{\\{([^}]+)\\}\\}");

/**
* Resolve {{variable}} references in moduleData
*/
public String resolveVariables(String moduleData, Map<String, Object> workflowState) {
Matcher matcher = VARIABLE_PATTERN.matcher(moduleData);
StringBuffer result = new StringBuffer();

while (matcher.find()) {
String varPath = matcher.group(1);
Object value = resolveVariablePath(varPath, workflowState);
String replacement = value != null ? String.valueOf(value) : "";
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(result);

return result.toString();
}

/**
* Resolve nested variable path (e.g., "moduleName.output.field")
*/
public Object resolveVariablePath(String path, Map<String, Object> workflowState) {
String[] parts = path.split("\\.");
Object current = workflowState;

for (String part : parts) {
if (current instanceof Map) {
current = ((Map<?, ?>) current).get(part);
} else if (current != null) {
// Try to access as object property via reflection
try {
Field field = current.getClass().getDeclaredField(part);
field.setAccessible(true);
current = field.get(current);
} catch (Exception e) {
return null;
}
} else {
return null;
}
}

return current;
}

/**
* Build workflow state from WorkflowState entities
*/
public Map<String, Object> buildStateMap(List<WorkflowState> states) {
Map<String, Object> stateMap = new HashMap<>();

for (WorkflowState state : states) {
String name = state.getName();
String value = state.getStateValue();

// Parse nested paths (e.g., "ImageGen.output.url")
String[] parts = name.split("\\.");
Map<String, Object> current = stateMap;

for (int i = 0; i < parts.length - 1; i++) {
String part = parts[i];
if (!current.containsKey(part)) {
current.put(part, new HashMap<String, Object>());
}
current = (Map<String, Object>) current.get(part);
}

// Set final value (try to parse as JSON first)
try {
current.put(parts[parts.length - 1], JSON.parse(value));
} catch (Exception e) {
current.put(parts[parts.length - 1], value);
}
}

return stateMap;
}
}

Status: ⬜ Not Started


Day 3-4: REST API Endpoints​

Task 1.4: WorkflowStudioController​

File: /valkyrai/src/main/java/com/valkyrlabs/workflow/controller/WorkflowStudioController.java

@RestController
@RequestMapping("/v1/workflow")
public class WorkflowStudioController {
@Autowired
private WorkflowService workflowService;

@Autowired
private WorkflowValidationService validationService;

@Autowired
private WorkflowIntegrationRegistryService registryService;

/**
* Validate workflow
*/
@PostMapping("/{id}/validate")
public ResponseEntity<WorkflowValidationResult> validateWorkflow(@PathVariable UUID id) {
Workflow workflow = workflowService.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Workflow not found"));

WorkflowValidationResult result = validationService.validate(workflow);
return ResponseEntity.ok(result);
}

/**
* Get all IntegrationAccounts used by workflow
*/
@GetMapping("/{id}/integrations")
public ResponseEntity<List<IntegrationAccount>> getWorkflowIntegrations(@PathVariable UUID id) {
List<IntegrationAccount> accounts = registryService.getWorkflowIntegrationAccounts(id);
return ResponseEntity.ok(accounts);
}

/**
* Get all API sources defined in workflow's OpenAPI spec
*/
@GetMapping("/{id}/api-sources")
public ResponseEntity<List<ApiSource>> getWorkflowApiSources(@PathVariable UUID id) {
List<ApiSource> sources = registryService.getWorkflowApiSources(id);
return ResponseEntity.ok(sources);
}

/**
* Get module outputs for variable picker
*/
@GetMapping("/{id}/module-outputs")
public ResponseEntity<Map<String, ModuleOutputSchema>> getModuleOutputs(@PathVariable UUID id) {
Workflow workflow = workflowService.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Workflow not found"));

Map<String, ModuleOutputSchema> outputs = new HashMap<>();

for (Task task : workflow.getTasks()) {
if (task.getModules() != null) {
for (ExecModule module : task.getModules()) {
String moduleName = module.getName() != null ?
module.getName() : module.getClassName();

ExecModuleMetadata meta = catalog.getMetadata(module.getClassName());
if (meta != null && meta.getOutputs() != null) {
outputs.put(moduleName, new ModuleOutputSchema(
moduleName,
meta.getTitle(),
meta.getOutputs()
));
}
}
}
}

return ResponseEntity.ok(outputs);
}
}

// DTO
public class ModuleOutputSchema {
private String moduleName;
private String displayName;
private Map<String, FieldSchema> fields;

// constructors, getters, setters
}

public class FieldSchema {
private String type;
private String description;

// constructors, getters, setters
}

Status: ⬜ Not Started


Day 5: Testing​

Task 1.5: Integration Tests​

File: /valkyrai/src/test/java/com/valkyrlabs/workflow/WorkflowStudioIntegrationTest.java

@SpringBootTest
@AutoConfigureMockMvc
public class WorkflowStudioIntegrationTest {
@Autowired
private MockMvc mockMvc;

@Autowired
private WorkflowRepository workflowRepo;

@Test
public void testValidateWorkflow() throws Exception {
// Create test workflow with invalid variable reference
Workflow workflow = createTestWorkflow();

mockMvc.perform(post("/v1/workflow/" + workflow.getId() + "/validate"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.valid").value(false))
.andExpect(jsonPath("$.errors[0].message").exists());
}

@Test
public void testGetWorkflowIntegrations() throws Exception {
Workflow workflow = createTestWorkflow();

mockMvc.perform(get("/v1/workflow/" + workflow.getId() + "/integrations"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray());
}

// More tests...
}

Status: ⬜ Not Started


βœ… Phase 2: Content Picker System (Week 2)​

Day 1-2: TypeScript Services​

Task 2.1: WorkflowStudioService​

File: /web/typescript/valkyr_labs_com/src/services/WorkflowStudioService.ts

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import {
Workflow,
WorkflowValidationResult,
IntegrationAccount,
ApiSource,
ModuleOutputSchema,
} from "@thorapi/model";

export const workflowStudioApi = createApi({
reducerPath: "workflowStudio",
baseQuery: fetchBaseQuery({ baseUrl: "/v1/workflow" }),
endpoints: (builder) => ({
validateWorkflow: builder.mutation<WorkflowValidationResult, string>({
query: (workflowId) => ({
url: `/${workflowId}/validate`,
method: "POST",
}),
}),

getWorkflowIntegrations: builder.query<IntegrationAccount[], string>({
query: (workflowId) => `/${workflowId}/integrations`,
}),

getWorkflowApiSources: builder.query<ApiSource[], string>({
query: (workflowId) => `/${workflowId}/api-sources`,
}),

getModuleOutputs: builder.query<Record<string, ModuleOutputSchema>, string>(
{
query: (workflowId) => `/${workflowId}/module-outputs`,
}
),
}),
});

export const {
useValidateWorkflowMutation,
useGetWorkflowIntegrationsQuery,
useGetWorkflowApiSourcesQuery,
useGetModuleOutputsQuery,
} = workflowStudioApi;

Status: ⬜ Not Started


Day 3-5: ContentPicker Component​

Task 2.2: ContentPicker.tsx​

File: /web/typescript/valkyr_labs_com/src/components/WorkflowStudio/ContentPicker.tsx

/**
* ContentPicker.tsx
*
* Universal content picker for:
* - Module outputs (existing VariablePicker)
* - API endpoints (from workflow OpenAPI spec)
* - Integration resources (from IntegrationAccounts)
*/

import React, { useState } from "react";
import { Workflow, ExecModule, IntegrationAccount } from "@thorapi/model";
import VariablePicker from "./VariablePicker";
import {
useGetWorkflowApiSourcesQuery,
useGetWorkflowIntegrationsQuery,
} from "../../services/WorkflowStudioService";

export interface ContentPickerProps {
show: boolean;
workflow: Workflow;
contentType?: "all" | "url" | "template" | "list" | "file";
accountType?: string; // Filter by integration type
onSelect: (content: PickedContent) => void;
onCancel: () => void;
multiSelect?: boolean;
}

export interface PickedContent {
source: "module-output" | "api-endpoint" | "integration-resource";
path: string;
label: string;
description?: string;
preview?: any;
}

export const ContentPicker: React.FC<ContentPickerProps> = ({
show,
workflow,
contentType = "all",
accountType,
onSelect,
onCancel,
multiSelect = false,
}) => {
const [activeTab, setActiveTab] = useState<
"modules" | "apis" | "integrations"
>("modules");

// Fetch data sources
const { data: apiSources = [] } = useGetWorkflowApiSourcesQuery(workflow.id!);
const { data: integrationAccounts = [] } = useGetWorkflowIntegrationsQuery(
workflow.id!
);

// Get upstream modules for variable picker
const availableModules =
workflow.tasks?.flatMap((t) => t.modules || []) || [];

if (!show) {
return null;
}

return (
<div
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
background: "rgba(0, 0, 0, 0.7)",
display: "flex",
justifyContent: "center",
alignItems: "center",
zIndex: 10001,
}}
onClick={onCancel}
>
<div
style={{
background: "#1e293b",
borderRadius: "8px",
width: "80%",
maxWidth: "1200px",
maxHeight: "80vh",
overflow: "hidden",
display: "flex",
flexDirection: "column",
}}
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div
style={{
padding: "16px 24px",
borderBottom: "2px solid #334155",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<h2 style={{ margin: 0, color: "#6ee7ff" }}>🎯 Select Content</h2>
<button
onClick={onCancel}
style={{
background: "transparent",
border: "none",
color: "#94a3b8",
fontSize: "24px",
cursor: "pointer",
}}
>
Γ—
</button>
</div>

{/* Tabs */}
<div
style={{
display: "flex",
gap: "4px",
padding: "8px 24px",
borderBottom: "1px solid #334155",
}}
>
<TabButton
active={activeTab === "modules"}
onClick={() => setActiveTab("modules")}
icon="πŸ“¦"
label="From Modules"
/>
<TabButton
active={activeTab === "apis"}
onClick={() => setActiveTab("apis")}
icon="πŸ”—"
label="From APIs"
/>
<TabButton
active={activeTab === "integrations"}
onClick={() => setActiveTab("integrations")}
icon="πŸ”"
label="From Integrations"
/>
</div>

{/* Tab Content */}
<div style={{ flex: 1, overflow: "auto", padding: "24px" }}>
{activeTab === "modules" && (
<ModulesTab
availableModules={availableModules}
onSelect={(varPath) =>
onSelect({
source: "module-output",
path: varPath,
label: varPath,
})
}
/>
)}

{activeTab === "apis" && (
<ApiSourcesTab
apiSources={apiSources}
contentType={contentType}
onSelect={(source) =>
onSelect({
source: "api-endpoint",
path: source.path,
label: `${source.method} ${source.path}`,
description: source.summary,
})
}
/>
)}

{activeTab === "integrations" && (
<IntegrationsTab
integrationAccounts={integrationAccounts}
accountType={accountType}
contentType={contentType}
onSelect={(resource) =>
onSelect({
source: "integration-resource",
path: resource.path,
label: resource.label,
description: resource.description,
preview: resource.preview,
})
}
/>
)}
</div>
</div>
</div>
);
};

// Tab Button Component
const TabButton: React.FC<{
active: boolean;
onClick: () => void;
icon: string;
label: string;
}> = ({ active, onClick, icon, label }) => (
<button
onClick={onClick}
style={{
padding: "8px 16px",
background: active ? "#6ee7ff" : "transparent",
color: active ? "#000" : "#94a3b8",
border: "none",
borderRadius: "4px",
cursor: "pointer",
fontWeight: active ? 600 : 400,
transition: "all 0.2s",
}}
>
{icon} {label}
</button>
);

// Modules Tab (reuses VariablePicker)
const ModulesTab: React.FC<{
availableModules: ExecModule[];
onSelect: (varPath: string) => void;
}> = ({ availableModules, onSelect }) => (
<div>
<p style={{ color: "#94a3b8", marginBottom: "16px" }}>
Select outputs from previous modules in the workflow:
</p>
<VariablePicker
show={true}
availableModules={availableModules}
onSelect={onSelect}
onCancel={() => {}}
/>
</div>
);

// API Sources Tab
const ApiSourcesTab: React.FC<{
apiSources: any[];
contentType: string;
onSelect: (source: any) => void;
}> = ({ apiSources, contentType, onSelect }) => {
// Filter by content type if specified
const filteredSources = apiSources.filter((source) => {
if (contentType === "url") {
// Only show endpoints that return URLs
return source.operations?.some(
(op: any) =>
op.responseSchema?.includes("url") ||
op.responseSchema?.includes("uri")
);
}
return true;
});

if (filteredSources.length === 0) {
return (
<div style={{ textAlign: "center", padding: "32px", color: "#64748b" }}>
<p>No API sources defined in workflow spec.</p>
<p style={{ fontSize: "14px", marginTop: "8px" }}>
Add an OpenAPI spec to this workflow to enable API source selection.
</p>
</div>
);
}

return (
<div>
{filteredSources.map((source, idx) => (
<ApiSourceCard
key={idx}
source={source}
onSelect={() => onSelect(source)}
/>
))}
</div>
);
};

// API Source Card
const ApiSourceCard: React.FC<{
source: any;
onSelect: () => void;
}> = ({ source }) => (
<div
style={{
background: "#0f172a",
border: "1px solid #334155",
borderRadius: "6px",
padding: "16px",
marginBottom: "12px",
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "start",
}}
>
<div>
<h4 style={{ margin: "0 0 8px 0", color: "#6ee7ff" }}>{source.path}</h4>
<p style={{ margin: "0 0 12px 0", color: "#94a3b8", fontSize: "14px" }}>
{source.summary || source.description}
</p>

{/* Operations */}
<div style={{ display: "flex", gap: "8px", flexWrap: "wrap" }}>
{source.operations?.map((op: any, idx: number) => (
<span
key={idx}
style={{
padding: "4px 8px",
background: "#334155",
color: "#6ee7ff",
borderRadius: "4px",
fontSize: "12px",
fontWeight: 600,
}}
>
{op.method}
</span>
))}
</div>
</div>

<button
onClick={onSelect}
style={{
padding: "8px 16px",
background: "#6ee7ff",
color: "#000",
border: "none",
borderRadius: "4px",
cursor: "pointer",
fontWeight: 600,
}}
>
Select
</button>
</div>
</div>
);

// Integrations Tab (placeholder for now)
const IntegrationsTab: React.FC<{
integrationAccounts: IntegrationAccount[];
accountType?: string;
contentType: string;
onSelect: (resource: any) => void;
}> = ({ integrationAccounts, accountType, contentType, onSelect }) => {
// Filter by account type if specified
const filteredAccounts = accountType
? integrationAccounts.filter((acc) =>
acc.accountName?.toLowerCase().includes(accountType.toLowerCase())
)
: integrationAccounts;

if (filteredAccounts.length === 0) {
return (
<div style={{ textAlign: "center", padding: "32px", color: "#64748b" }}>
<p>No integration accounts found for this workflow.</p>
<p style={{ fontSize: "14px", marginTop: "8px" }}>
Add IntegrationAccounts to modules to enable integration resource
selection.
</p>
</div>
);
}

return (
<div>
{filteredAccounts.map((account) => (
<IntegrationAccountCard
key={account.id}
account={account}
contentType={contentType}
onSelectResource={onSelect}
/>
))}
</div>
);
};

// Integration Account Card
const IntegrationAccountCard: React.FC<{
account: IntegrationAccount;
contentType: string;
onSelectResource: (resource: any) => void;
}> = ({ account, contentType, onSelectResource }) => {
// TODO: Fetch resources from integration account
// For now, show placeholder

return (
<div
style={{
background: "#0f172a",
border: "1px solid #334155",
borderRadius: "6px",
padding: "16px",
marginBottom: "12px",
}}
>
<h4 style={{ margin: "0 0 8px 0", color: "#6ee7ff" }}>
{account.accountName}
</h4>
<p style={{ margin: 0, color: "#64748b", fontSize: "14px" }}>
Integration resources will appear here (coming soon)
</p>
</div>
);
};

export default ContentPicker;

Status: ⬜ Not Started


πŸ“ Summary: What Gets Built​

Backend (Java/Spring Boot)​

  1. βœ… WorkflowIntegrationRegistryService - Manages IntegrationAccounts + API sources
  2. βœ… WorkflowValidationService - Real-time validation engine
  3. βœ… WorkflowVariableResolver - Variable interpolation {{var}}
  4. βœ… REST endpoints for validation, integrations, API sources

Frontend (React/TypeScript)​

  1. βœ… ContentPicker - Universal content selection (modules, APIs, integrations)
  2. βœ… URLPicker - Smart URL field picker with variable + API support
  3. βœ… DataMapper - Visual field-to-field mapping (Phase 3)
  4. βœ… ValidationPanel - Problems panel with quick fixes (Phase 4)

Integration Points​

  1. βœ… Extends existing VariablePicker for module outputs
  2. βœ… Uses workflow.specs (OpenAPI) for API sources
  3. βœ… Queries IntegrationAccountService for accounts
  4. βœ… Integrates with Gridheim Rune calc engine for transforms

πŸš€ Execution Order​

THIS WEEK (Phase 1 - Backend):

  1. Create WorkflowIntegrationRegistryService
  2. Create WorkflowValidationService
  3. Create WorkflowVariableResolver
  4. Add REST endpoints
  5. Write integration tests

NEXT WEEK (Phase 2 - Content Picker):

  1. Create TypeScript service hooks
  2. Build ContentPicker component
  3. Integrate with ExecModuleConfigForm
  4. Add URL picker to FormFieldRenderer

WEEK 3 (Phase 3 - Data Mapper):

  1. Build DataMapper component
  2. Add Gridheim Rune calc integration
  3. Visual field connections

WEEK 4 (Phase 4 - Validation):

  1. Build ValidationPanel component
  2. Add visual error badges
  3. Implement Quick Fix actions

Let's EXECUTE THE VISION and build the most insane N8N killer! 🎯πŸ”₯