OneApp Docs
Platform Apps

oneapp-backstage

Visual AI workflow designer with drag-and-drop interface. Build complex AI agents without code. 8 node types, Monaco editor, external integrations (Linear, Slack). Microfrontend embedded in oneapp-onstage.

Quick Start

Create your first AI workflow in 3 minutes:

pnpm dev:oneapp

Access at localhost:3500/backstage. Drag nodes, connect edges, deploy. Skip to Quick Start →

Why oneapp-backstage?

AI workflows built with hard-coded logic. Agent changes require code deployment. No visual debugging for complex chains. External integrations duplicated across projects. Prompt templates scattered in codebases. Team collaboration on AI agents requires technical skills.

oneapp-backstage solves this by providing a visual workflow designer where anyone can create, test, and deploy AI agents with drag-and-drop nodes.

Production-ready with 8 node categories, Monaco code editor, external integrations, microfrontend architecture, real-time workflow execution, and CSP-compliant security.

Use cases

  • AI Agent Design — Build conversational agents with LLM, RAG, and tool nodes
  • Workflow Automation — Connect AI to Linear, Slack, GitHub, Notion
  • Admin Dashboard — Manage AI workflows, monitor execution, debug failures
  • Prompt Engineering — Test and iterate on prompts visually with real data
  • Guardrail Configuration — Add safety checks (PII, toxicity, jailbreak) to workflows

How it works

oneapp-backstage provides a visual workflow designer built on React Flow:

// app/workflows/page.tsx
import { WorkflowDesigner } from "#/components/flow/FlowCanvas";

export default function WorkflowsPage() {
  return (
    <div className="h-screen">
      <WorkflowDesigner />

  );
}

// components/flow/FlowCanvas.tsx
import { ReactFlow, useNodesState, useEdgesState } from "@xyflow/react";
import { InputNode, LLMNode, RAGNode, OutputNode } from "#/components/nodes";

const nodeTypes = {
  inputNode: InputNode,
  llmNode: LLMNode,
  ragNode: RAGNode,
  outputNode: OutputNode,
};

export function WorkflowDesigner() {
  const [nodes, setNodes, onNodesChange] = useNodesState([]);
  const [edges, setEdges, onEdgesChange] = useEdgesState([]);

  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      onNodesChange={onNodesChange}
      onEdgesChange={onEdgesChange}
      nodeTypes={nodeTypes}
      fitView
    />
  );
}

Uses @xyflow/react for canvas, Monaco for code editing, and microfrontend architecture for seamless embedding in oneapp-onstage.

Key features

8 Node Categories — Input, LLM, RAG, Tool, Logic, Output, Integration, Guardrail

Visual Designer — Drag-and-drop interface with React Flow

Monaco Editor — In-node code editing with syntax highlighting

External Integrations — Linear, Slack, GitHub, Notion connectors

Microfrontend — Embedded at /backstage in oneapp-onstage

Real-time Execution — Test workflows with live data

Quick Start

1. Start the microfrontend

# Start all OneApp services
pnpm dev:oneapp

2. Access the designer

Open http://localhost:3500/backstage in your browser.

3. Create your first workflow

Example: Simple Q&A Agent
// Drag nodes onto canvas:
// 1. Input Node (type: text) → captures user question
// 2. LLM Node (provider: anthropic, model: claude-3-sonnet) → processes question
// 3. Output Node (format: markdown) → returns answer

// Connect edges: Input → LLM → Output
// Click "Test Workflow" to execute with sample data

4. Deploy the workflow

app/api/workflows/execute/route.ts
import { executeWorkflow } from "@repo/oneapp-shared/ai-agent";

export async function POST(request: Request) {
  const { workflowId, input } = await request.json();

  const result = await executeWorkflow(workflowId, input);

  return Response.json(result);
}

That's it! Your AI workflow is now accessible via API and can be called from oneapp-onstage or external services.

Standalone Development

Run backstage independently:

pnpm --filter oneapp-backstage dev

Access at localhost:3600 for faster iteration during development.


Technical Details

For Developers: Technical implementation details

Overview

PropertyValue
Locationplatform/apps/oneapp-backstage
Port3600 (standalone), embedded at /backstage in oneapp-onstage
FrameworkNext.js 16 (App Router, React 19, Turbopack)
UI Library@xyflow/react (React Flow)
EditorMonaco Editor (@monaco-editor/react)
Tech StackTypeScript 5, Tailwind CSS 4, Better Auth, Prisma ORM

Project Structure

oneapp-backstage/
├── src/
│   ├── app/                       # Next.js App Router
│   │   ├── (dashboard)/           # Dashboard routes (layout groups)
│   │   │   ├── workflows/         # Workflow management
│   │   │   │   ├── page.tsx       # Workflows list
│   │   │   │   ├── [id]/          # Workflow editor
│   │   │   │   │   └── page.tsx   # Visual designer
│   │   │   │   └── new/           # Create workflow
│   │   │   ├── agents/            # Agent management
│   │   │   ├── integrations/      # External service config
│   │   │   └── settings/          # App settings
│   │   ├── api/                   # API routes
│   │   │   ├── workflows/         # Workflow CRUD
│   │   │   │   ├── execute/       # Execute workflow
│   │   │   │   └── validate/      # Validate workflow
│   │   │   └── integrations/      # Integration endpoints
│   │   └── layout.tsx             # Root layout
│   ├── components/
│   │   ├── flow/                  # React Flow components
│   │   │   ├── FlowCanvas.tsx     # Main canvas wrapper
│   │   │   ├── FlowControls.tsx   # Zoom, fit view controls
│   │   │   ├── FlowMinimap.tsx    # Minimap component
│   │   │   ├── FlowToolbar.tsx    # Node palette
│   │   │   └── FlowSidebar.tsx    # Node properties panel
│   │   ├── nodes/                 # Node implementations
│   │   │   ├── InputNode.tsx      # Input node
│   │   │   ├── LLMNode.tsx        # LLM node
│   │   │   ├── RAGNode.tsx        # RAG node
│   │   │   ├── ToolNode.tsx       # Tool node
│   │   │   ├── LogicNode.tsx      # Logic node
│   │   │   ├── OutputNode.tsx     # Output node
│   │   │   ├── IntegrationNode.tsx # Integration node
│   │   │   └── GuardrailNode.tsx  # Guardrail node
│   │   ├── editors/               # Code editors
│   │   │   ├── MonacoEditor.tsx   # Monaco wrapper
│   │   │   └── PromptEditor.tsx   # Prompt template editor
│   │   └── ui/                    # Shared UI components
│   ├── hooks/                     # Custom React hooks
│   │   ├── useWorkflow.ts         # Workflow state management
│   │   ├── useNodes.ts            # Node operations
│   │   ├── useEdges.ts            # Edge operations
│   │   └── useExecution.ts        # Workflow execution
│   ├── lib/                       # Utilities and helpers
│   │   ├── workflow-executor.ts   # Workflow runtime
│   │   ├── node-validators.ts     # Node validation logic
│   │   └── integration-client.ts  # External API clients
│   └── types/                     # TypeScript definitions
│       ├── nodes.ts               # Node type definitions
│       ├── workflow.ts            # Workflow types
│       └── integrations.ts        # Integration types
├── env.ts                         # Environment config (@t3-oss/env-nextjs)
├── next.config.ts                 # Next.js config with CSP
└── package.json                   # Dependencies

Node Types

1. Input Nodes (📥)

Capture user input and trigger workflows.

// components/nodes/InputNode.tsx
import { Handle, Position } from "@xyflow/react";
import { z } from "zod/v4";

interface InputNodeData {
  type: "text" | "file" | "voice" | "webhook";
  label: string;
  placeholder?: string;
  validation?: z.ZodSchema;
  defaultValue?: string;
}

export function InputNode({ data }: { data: InputNodeData }) {
  return (
    <div className="rounded-lg border bg-white p-4 shadow-md">
      <div className="text-sm font-semibold">📥 {data.label}
      <div className="mt-2 text-xs text-gray-500">Type: {data.type}

      {/* Output handle */}
      <Handle type="source" position={Position.Right} />

  );
}

Supported Input Types:

  • text — Single-line or multi-line text input
  • file — File upload (PDF, DOCX, images)
  • voice — Audio input with transcription
  • webhook — External HTTP triggers

2. LLM Nodes (🤖)

Process text with large language models.

// components/nodes/LLMNode.tsx
interface LLMNodeData {
  provider: "openai" | "anthropic" | "google";
  model: string;
  systemPrompt: string;
  temperature: number;
  maxTokens: number;
  streaming?: boolean;
}

export function LLMNode({ data }: { data: LLMNodeData }) {
  return (
    <div className="rounded-lg border bg-blue-50 p-4 shadow-md">
      <Handle type="target" position={Position.Left} />

      <div className="text-sm font-semibold">🤖 LLM
      <div className="mt-2 space-y-1 text-xs">
        <div>Provider: {data.provider}
        <div>Model: {data.model}
        <div>Temperature: {data.temperature}


      <Handle type="source" position={Position.Right} />

  );
}

Supported Providers:

  • OpenAI — GPT-4, GPT-3.5 Turbo
  • Anthropic — Claude 3 Sonnet, Claude 3 Opus
  • Google — Gemini Pro, Gemini Flash

3. RAG Nodes (📚)

Vector search and document retrieval.

// components/nodes/RAGNode.tsx
interface RAGNodeData {
  vectorStore: "pinecone" | "upstash" | "qdrant";
  topK: number;
  threshold: number;
  namespace?: string;
  embeddingModel?: string;
}

export function RAGNode({ data }: { data: RAGNodeData }) {
  return (
    <div className="rounded-lg border bg-purple-50 p-4 shadow-md">
      <Handle type="target" position={Position.Left} />

      <div className="text-sm font-semibold">📚 RAG
      <div className="mt-2 space-y-1 text-xs">
        <div>Store: {data.vectorStore}
        <div>Top K: {data.topK}
        <div>Threshold: {data.threshold}


      <Handle type="source" position={Position.Right} />

  );
}

Vector Store Integrations:

  • Pinecone — Managed vector database
  • Upstash Vector — Serverless vector database
  • Qdrant — Self-hosted vector search engine

4. Tool Nodes (🔧)

Execute functions and external API calls.

// components/nodes/ToolNode.tsx
interface ToolNodeData {
  toolId: string;
  toolName: string;
  parameters: Record<string, unknown>;
  timeout: number;
  retries?: number;
}

export function ToolNode({ data }: { data: ToolNodeData }) {
  return (
    <div className="rounded-lg border bg-green-50 p-4 shadow-md">
      <Handle type="target" position={Position.Left} />

      <div className="text-sm font-semibold">🔧 {data.toolName}
      <div className="mt-2 text-xs text-gray-500">
        Timeout: {data.timeout}ms


      <Handle type="source" position={Position.Right} />

  );
}

Available Tools:

  • Web Search — Google, Brave Search API
  • Calculator — Mathematical expressions
  • Code Executor — Run Python, JavaScript code
  • API Call — Generic HTTP requests
  • Database Query — SQL/Prisma queries

5. Logic Nodes (🔀)

Conditional routing and branching.

// components/nodes/LogicNode.tsx
interface LogicNodeData {
  condition: string; // JavaScript expression
  branches: {
    label: string;
    targetNodeId: string;
    condition?: string;
  }[];
}

export function LogicNode({ data }: { data: LogicNodeData }) {
  return (
    <div className="rounded-lg border bg-yellow-50 p-4 shadow-md">
      <Handle type="target" position={Position.Left} />

      <div className="text-sm font-semibold">🔀 Logic
      <div className="mt-2 text-xs">
        {data.branches.length} branches


      {/* Multiple output handles for branches */}
      {data.branches.map((branch, i) => (
        <Handle
          key={branch.label}
          type="source"
          position={Position.Right}
          id={branch.label}
          style={{ top: `${30 + i * 20}px` }}
        />
      ))}

  );
}

Logic Operators:

  • Conditionalif/else branching
  • Switch — Multiple condition matching
  • Loop — Iterate over arrays
  • Filter — Conditional filtering

6. Output Nodes (📤)

Generate artifacts and responses.

// components/nodes/OutputNode.tsx
interface OutputNodeData {
  format: "text" | "json" | "markdown" | "html" | "stream";
  template?: string;
  transformation?: string; // JS code
}

export function OutputNode({ data }: { data: OutputNodeData }) {
  return (
    <div className="rounded-lg border bg-gray-50 p-4 shadow-md">
      <Handle type="target" position={Position.Left} />

      <div className="text-sm font-semibold">📤 Output
      <div className="mt-2 text-xs text-gray-500">
        Format: {data.format}


  );
}

Output Formats:

  • Text — Plain text response
  • JSON — Structured data
  • Markdown — Formatted text
  • HTML — Rich content
  • Stream — Real-time streaming response

7. Integration Nodes (🔌)

Connect to external services.

// components/nodes/IntegrationNode.tsx
interface IntegrationNodeData {
  service: "slack" | "linear" | "github" | "notion";
  action: string;
  credentials: string; // Reference to stored credentials
  config: Record<string, unknown>;
}

export function IntegrationNode({ data }: { data: IntegrationNodeData }) {
  return (
    <div className="rounded-lg border bg-indigo-50 p-4 shadow-md">
      <Handle type="target" position={Position.Left} />

      <div className="text-sm font-semibold">🔌 {data.service}
      <div className="mt-2 text-xs text-gray-500">
        Action: {data.action}


      <Handle type="source" position={Position.Right} />

  );
}

Supported Integrations:

  • Slack — Send messages, create channels, manage users
  • Linear — Create issues, update status, search
  • GitHub — Create PRs, issues, search code
  • Notion — Create pages, update databases

8. Guardrail Nodes (🛡️)

Safety and validation checks.

// components/nodes/GuardrailNode.tsx
interface GuardrailNodeData {
  checks: ("pii" | "toxicity" | "jailbreak" | "custom")[];
  action: "block" | "warn" | "flag" | "sanitize";
  customRules?: string[];
  severity?: "low" | "medium" | "high";
}

export function GuardrailNode({ data }: { data: GuardrailNodeData }) {
  return (
    <div className="rounded-lg border border-red-300 bg-red-50 p-4 shadow-md">
      <Handle type="target" position={Position.Left} />

      <div className="text-sm font-semibold">🛡️ Guardrail
      <div className="mt-2 text-xs">
        {data.checks.length} checks active


      <Handle type="source" position={Position.Right} />

  );
}

Safety Checks:

  • PII Detection — Detect emails, SSNs, credit cards
  • Toxicity — Detect harmful, offensive content
  • Jailbreak — Prevent prompt injection attacks
  • Custom Rules — Regex or LLM-based validation

Workflow Execution

Workflow Runtime

// lib/workflow-executor.ts
import type { Node, Edge } from "@xyflow/react";
import { executeNode } from "./node-executors";

export async function executeWorkflow(nodes: Node[], edges: Edge[], input: Record<string, unknown>) {
  // Build execution graph
  const graph = buildExecutionGraph(nodes, edges);

  // Find start nodes (no incoming edges)
  const startNodes = nodes.filter((node) => !edges.some((edge) => edge.target === node.id));

  // Execute nodes in topological order
  const results = new Map<string, unknown>();

  for (const node of startNodes) {
    await executeNodeRecursive(node, graph, results, input);
  }

  // Return final output
  const outputNodes = nodes.filter((n) => n.type === "outputNode");
  return outputNodes.map((n) => results.get(n.id));
}

async function executeNodeRecursive(
  node: Node,
  graph: Map<string, Node[]>,
  results: Map<string, unknown>,
  input: Record<string, unknown>
) {
  // Get input from previous nodes
  const nodeInput = collectNodeInput(node, results, input);

  // Execute node logic
  const result = await executeNode(node, nodeInput);
  results.set(node.id, result);

  // Execute downstream nodes
  const downstream = graph.get(node.id) || [];
  for (const nextNode of downstream) {
    await executeNodeRecursive(nextNode, graph, results, input);
  }
}

Node Execution

// lib/node-executors/index.ts
export async function executeNode(node: Node, input: Record<string, unknown>) {
  switch (node.type) {
    case "inputNode":
      return input[node.data.label] || node.data.defaultValue;

    case "llmNode":
      return executeLLMNode(node.data, input);

    case "ragNode":
      return executeRAGNode(node.data, input);

    case "toolNode":
      return executeToolNode(node.data, input);

    case "logicNode":
      return executeLogicNode(node.data, input);

    case "outputNode":
      return executeOutputNode(node.data, input);

    case "integrationNode":
      return executeIntegrationNode(node.data, input);

    case "guardrailNode":
      return executeGuardrailNode(node.data, input);

    default:
      throw new Error(`Unknown node type: ${node.type}`);
  }
}

// lib/node-executors/llm.ts
async function executeLLMNode(data: LLMNodeData, input: Record<string, unknown>) {
  const { provider, model, systemPrompt, temperature, maxTokens } = data;

  // Call AI provider
  if (provider === "anthropic") {
    const response = await anthropic.messages.create({
      model,
      system: systemPrompt,
      messages: [{ role: "user", content: String(input.prompt) }],
      temperature,
      max_tokens: maxTokens
    });

    return response.content[0].text;
  }

  // ... other providers
}

Monaco Editor Integration

Code Editor Component

// components/editors/MonacoEditor.tsx
"use client";

import Editor from "@monaco-editor/react";

interface MonacoEditorProps {
  value: string;
  onChange: (value: string) => void;
  language?: "javascript" | "typescript" | "python" | "json";
  height?: string;
}

export function MonacoEditor({
  value,
  onChange,
  language = "javascript",
  height = "300px",
}: MonacoEditorProps) {
  return (
    <Editor
      height={height}
      defaultLanguage={language}
      value={value}
      onChange={(value) => onChange(value || "")}
      theme="vs-dark"
      options={{
        minimap: { enabled: false },
        fontSize: 14,
        lineNumbers: "on",
        scrollBeyondLastLine: false,
        automaticLayout: true,
      }}
    />
  );
}

Prompt Template Editor

// components/editors/PromptEditor.tsx
import { MonacoEditor } from "./MonacoEditor";

export function PromptEditor({
  value,
  onChange,
}: {
  value: string;
  onChange: (value: string) => void;
}) {
  return (
    <div className="space-y-2">
      <label className="text-sm font-medium">System Prompt</label>
      <MonacoEditor
        value={value}
        onChange={onChange}
        language="markdown"
        height="200px"
      />

      <div className="text-xs text-gray-500">
        Use &#123;&#123;variable&#125;&#125; for dynamic values


  );
}

Microfrontend Architecture

Host Configuration (oneapp-onstage)

// oneapp-onstage/next.config.ts
const microfrontends = {
  "oneapp-backstage": {
    routes: ["/backstage", "/backstage-preview"],
    assetPrefix: "/oneapp-backstage-assets",
    localPort: 3600,
    remoteUrl:
      process.env.NODE_ENV === "production"
        ? "https://backstage.yourdomain.com"
        : `http://localhost:3600`,
  },
};

// Proxy configuration
async rewrites() {
  return [
    {
      source: "/backstage/:path*",
      destination: `http://localhost:3600/backstage/:path*`,
    },
    {
      source: "/oneapp-backstage-assets/:path*",
      destination: `http://localhost:3600/_next/:path*`,
    },
  ];
}

Remote Configuration (oneapp-backstage)

// oneapp-backstage/next.config.ts
const nextConfig = {
  basePath: process.env.NODE_ENV === "production" ? "" : "/backstage",
  assetPrefix: process.env.NODE_ENV === "production" ? "" : "/oneapp-backstage-assets"
};

Route Configuration

RouteDescriptionFeature Flag
/backstageWorkflows listNone
/backstage/workflowsWorkflow managementNone
/backstage/workflows/[id]Workflow editorNone
/backstage/agentsAgent managementNone
/backstage/integrationsIntegration settingsNone
/backstage-preview/*Preview/beta featuresbackstage-preview

Environment Variables

Required Variables

# Database (Neon Postgres)
DATABASE_URL="postgresql://user:pass@host/db?sslmode=require"
DATABASE_URL_UNPOOLED="postgresql://user:pass@host/db?sslmode=require"

# Authentication (Better Auth)
BETTER_AUTH_SECRET="generate-random-secret-key"
BETTER_AUTH_URL="http://localhost:3500"
TRUSTED_ORIGINS="http://localhost:3500,http://localhost:3600"

# Feature Flags (for backstage-preview route)
NEXT_PUBLIC_FEATURE_BACKSTAGE_PREVIEW="false"

AI Providers

# AI Gateway (optional proxy)
AI_GATEWAY_API_KEY="your-gateway-key"

# OpenAI
OPENAI_API_KEY="sk-..."

# Anthropic
ANTHROPIC_API_KEY="sk-ant-..."

# Google AI
GOOGLE_AI_API_KEY="..."

Vector Stores

# Pinecone
PINECONE_API_KEY="..."
PINECONE_ENVIRONMENT="us-east1-gcp"

# Upstash Vector
UPSTASH_VECTOR_REST_URL="https://..."
UPSTASH_VECTOR_REST_TOKEN="..."

# Qdrant
QDRANT_URL="http://localhost:6333"
QDRANT_API_KEY="..."

External Integrations

# Linear
LINEAR_API_KEY="lin_api_..."

# Slack
SLACK_BOT_TOKEN="xoxb-..."
SLACK_SIGNING_SECRET="..."

# GitHub
GITHUB_TOKEN="ghp_..."

# Notion
NOTION_API_KEY="secret_..."

Email (Resend)

RESEND_TOKEN="re_..."
RESEND_FROM="noreply@yourdomain.com"

Content Security Policy

oneapp-backstage configures CSP for Monaco Editor and external API calls:

// next.config.ts
const cspHeader = `
  default-src 'self';
  script-src 'self' 'unsafe-eval' 'unsafe-inline' https://cdn.jsdelivr.net;
  style-src 'self' 'unsafe-inline';
  img-src 'self' blob: data: https:;
  font-src 'self' data:;
  connect-src 'self'
    https://api.anthropic.com
    https://api.openai.com
    https://generativelanguage.googleapis.com
    https://api.linear.app
    https://slack.com
    wss://slack.com;
  worker-src 'self' blob:;
  frame-src 'self';
`;

export default {
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [
          {
            key: "Content-Security-Policy",
            value: cspHeader.replace(/\n/g, "")
          }
        ]
      }
    ];
  }
};

Key CSP Rules:

  • unsafe-eval — Required for Monaco Editor
  • unsafe-inline — Required for Monaco syntax highlighting
  • worker-src blob: — Required for Monaco web workers
  • connect-src — Whitelist AI provider APIs

API Routes

Execute Workflow

// app/api/workflows/execute/route.ts
import { NextRequest } from "next/server";
import { executeWorkflow } from "#/lib/workflow-executor";
import { db } from "@repo/db-prisma";

export async function POST(request: NextRequest) {
  const { workflowId, input } = await request.json();

  // Fetch workflow from database
  const workflow = await db.workflow.findUnique({
    where: { id: workflowId },
    include: { nodes: true, edges: true }
  });

  if (!workflow) {
    return Response.json({ error: "Workflow not found" }, { status: 404 });
  }

  // Execute workflow
  const result = await executeWorkflow(workflow.nodes, workflow.edges, input);

  return Response.json({ result });
}

Validate Workflow

// app/api/workflows/validate/route.ts
import { validateWorkflow } from "#/lib/node-validators";

export async function POST(request: NextRequest) {
  const { nodes, edges } = await request.json();

  const validation = validateWorkflow(nodes, edges);

  if (!validation.valid) {
    return Response.json({ valid: false, errors: validation.errors }, { status: 400 });
  }

  return Response.json({ valid: true });
}

Testing

Type Checking

pnpm --filter oneapp-backstage typecheck

Linting

pnpm --filter oneapp-backstage lint

Manual Testing

  1. Start the app: pnpm dev:oneapp
  2. Navigate to http://localhost:3500/backstage
  3. Create a new workflow:
    • Drag Input node → LLM node → Output node
    • Connect edges between nodes
    • Configure LLM node with provider, model, prompt
    • Click "Test Workflow" with sample input
  4. Verify execution completes successfully
  5. Check output format matches expected result

Deployment

oneapp-backstage deploys as a microfrontend embedded in oneapp-onstage. No separate deployment needed.

Build Process

# Build backstage (happens automatically during oneapp-onstage build)
pnpm --filter oneapp-backstage build

Asset Routing

Assets are served from /oneapp-backstage-assets/* on the host application:

# Production URLs
https://yourdomain.com/backstage              → oneapp-backstage pages
https://yourdomain.com/oneapp-backstage-assets → oneapp-backstage static assets

Environment Variables (Production)

Set all required environment variables in your hosting platform (Vercel, Railway, etc.):

  • Database: DATABASE_URL, DATABASE_URL_UNPOOLED
  • Auth: BETTER_AUTH_SECRET, BETTER_AUTH_URL, TRUSTED_ORIGINS
  • AI Providers: OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_AI_API_KEY
  • Integrations: LINEAR_API_KEY, SLACK_BOT_TOKEN, GITHUB_TOKEN, NOTION_API_KEY

Troubleshooting

Monaco Editor Not Loading

Issue: Monaco Editor fails to load with CSP errors.

Solution: Ensure CSP allows unsafe-eval and worker-src blob::

const cspHeader = `
  script-src 'self' 'unsafe-eval';
  worker-src 'self' blob:;
`;

Nodes Not Connecting

Issue: Edges don't connect between nodes.

Solution: Verify Handle components have correct type and position:

// Source node
<Handle type="source" position={Position.Right} />

// Target node
<Handle type="target" position={Position.Left} />

Workflow Execution Fails

Issue: Workflow execution fails with timeout errors.

Solution: Increase node timeout or add error handling:

const result = await executeNode(node, input).catch((error) => {
  console.error(`Node ${node.id} failed:`, error);
  return { error: error.message };
});

Microfrontend Routes Not Working

Issue: /backstage routes return 404.

Solution: Verify proxy configuration in oneapp-onstage:

// next.config.ts
async rewrites() {
  return [
    {
      source: "/backstage/:path*",
      destination: "http://localhost:3600/backstage/:path*",
    },
  ];
}
  • oneapp-onstage — Host application embedding backstage
  • oneapp-api — REST API for workflow execution
  • @repo/oneapp-shared — Shared Prisma schema and ORM functions

On this page