π― 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)β
- β
WorkflowIntegrationRegistryService- Manages IntegrationAccounts + API sources - β
WorkflowValidationService- Real-time validation engine - β
WorkflowVariableResolver- Variable interpolation{{var}} - β REST endpoints for validation, integrations, API sources
Frontend (React/TypeScript)β
- β
ContentPicker- Universal content selection (modules, APIs, integrations) - β
URLPicker- Smart URL field picker with variable + API support - β
DataMapper- Visual field-to-field mapping (Phase 3) - β
ValidationPanel- Problems panel with quick fixes (Phase 4)
Integration Pointsβ
- β
Extends existing
VariablePickerfor module outputs - β
Uses
workflow.specs(OpenAPI) for API sources - β
Queries
IntegrationAccountServicefor accounts - β Integrates with Gridheim Rune calc engine for transforms
π Execution Orderβ
THIS WEEK (Phase 1 - Backend):
- Create
WorkflowIntegrationRegistryService - Create
WorkflowValidationService - Create
WorkflowVariableResolver - Add REST endpoints
- Write integration tests
NEXT WEEK (Phase 2 - Content Picker):
- Create TypeScript service hooks
- Build
ContentPickercomponent - Integrate with ExecModuleConfigForm
- Add URL picker to FormFieldRenderer
WEEK 3 (Phase 3 - Data Mapper):
- Build
DataMappercomponent - Add Gridheim Rune calc integration
- Visual field connections
WEEK 4 (Phase 4 - Validation):
- Build
ValidationPanelcomponent - Add visual error badges
- Implement Quick Fix actions
Let's EXECUTE THE VISION and build the most insane N8N killer! π―π₯