Skip to main content

ADR-026: V5 Wrapper Persistence Architecture

Status: Accepted Date: 2025-10-13 Decision Makers: Architecture Team Related ADRs:

  • ADR-014: Eclipse theia as IDE Foundation
  • ADR-001: React + TypeScript for Frontend
  • ADR-016: NGINX Load Balancer

Context

The Coditect V5 architecture combines a React-based wrapper (frontend) with Eclipse theia (IDE backend). The wrapper provides critical functionality:

  • Navigation: Header with logo, user menu, theme toggle
  • Session Management: Multi-tab session switching
  • Branding: Coditect AI branding and custom UI
  • Authentication: User login, profile, settings
  • Documentation: Access to /docs, /support, /faq, etc.

Problem: Users could bypass the wrapper by accessing theia directly:

  • Development: http://localhost:3000 (theia port)
  • Production: http://coditect.ai/theia (NGINX proxy)

This would result in:

  • ❌ Loss of navigation (Header/Footer)
  • ❌ No session management UI
  • ❌ No branding or custom features
  • ❌ Inconsistent user experience
  • ❌ Inability to access documentation, settings, profile

Requirement: Ensure the V5 wrapper always remains around theia for navigation and feature access.


Decision

We will enforce wrapper persistence through a multi-layered approach:

1. React Router Architecture

All routes go through the layout component which provides:

  • Header (navigation, branding, user menu)
  • Main content area (theia embed or page content)
  • Footer (links, copyright)

Key Routes:

  • /ide → Full theia IDE with wrapper
  • /workspace → workspace tab with wrapper
  • /ai-studio → AI Studio tab with wrapper
  • /docs/* → Documentation pages with wrapper
  • All routes use <layout> component

2. Iframe Sandbox Protection

theia is embedded via iframe with strict sandbox rules:

<iframe
src={theiaUrl}
sandbox="allow-same-origin allow-scripts allow-forms allow-modals allow-popups allow-downloads"
// ✅ NO allow-top-navigation - prevents breakout
/>

Security Properties:

  • theia cannot navigate parent window
  • theia cannot break out of iframe context
  • Wrapper remains in control of top-level navigation
  • postMessage used for theme sync (controlled communication)

3. NGINX Redirect Protection

Production NGINX configuration prevents direct theia access:

# SECURITY: Prevent direct access to theia
location = /theia {
return 301 /ide; # Redirect to wrapper
}

# API/WebSocket endpoints (iframe only)
location /theia/ {
proxy_pass http://localhost:3000;
# ... WebSocket support
}

Effect:

  • http://coditect.ai/theia → redirects to http://coditect.ai/ide
  • http://coditect.ai/theia/services → proxied (for iframe)
  • ✅ Users cannot bypass wrapper in production

4. Development Mode Guidance

In development, theia runs on port 3000:

  • Recommended: Use http://localhost:5173/ide (wrapper + theia)
  • ⚠️ Not Recommended: http://localhost:3000 (direct theia, no wrapper)

Documentation: Developer guide explains proper access methods.


Architecture Diagram

┌─────────────────────────────────────────────────────────────────┐
│ User Access Points │
├─────────────────────────────────────────────────────────────────┤
│ │
│ http://coditect.ai/ide ✅ Wrapper + theia │
│ http://coditect.ai/workspace ✅ Wrapper + theia Widgets │
│ http://coditect.ai/ai-studio ✅ Wrapper + AI Chat │
│ http://coditect.ai/docs ✅ Wrapper + Documentation │
│ │
│ http://coditect.ai/theia ❌ Redirects to /ide │
│ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ NGINX (Port 80) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ location / { │
│ root /app/v5-frontend; ← V5 React SPA │
│ try_files $uri /index.html; │
│ } │
│ │
│ location = /theia { │
│ return 301 /ide; ← 🔒 REDIRECT PROTECTION │
│ } │
│ │
│ location /theia/ { │
│ proxy_pass http://localhost:3000; ← theia Backend │
│ } │
│ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ V5 React Application │
│ (React Router + layout) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Header (56px) │ │
│ │ - Logo, Navigation, Theme Toggle, User Menu │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Main Content Area │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ theiaEmbed Component (iframe) │ │ │
│ │ │ │ │ │
│ │ │ 🔒 Sandbox: allow-same-origin allow-scripts │ │ │
│ │ │ (NO allow-top-navigation) │ │ │
│ │ │ │ │ │
│ │ │ src="http://localhost:3000?sessionId=xyz" │ │ │
│ │ │ │ │ │
│ │ │ [theia IDE runs inside here] │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Footer (40px) │ │
│ │ - Copyright, Links, Status │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ Eclipse theia Backend │
│ (Port 3000) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ - Monaco editor │
│ - terminal (xterm.js) │
│ - File Explorer │
│ - AI Chat (theia AI extension) │
│ - WebSocket services │
│ │
└─────────────────────────────────────────────────────────────────┘

Implementation Details

File: src/components/layout.tsx

Purpose: Wraps all routes with Header/Footer

export const layout: React.FC<layoutProps> = ({
sessionId,
theiaUrl = 'http://localhost:3000',
children,
showIDE = false,
tabModality = 'theia',
}) => {
return (
<Flex direction="column" h="100vh" w="100vw">
{/* ✅ Header always present */}
<Header />

{/* Main content area */}
<Flex flex={1} overflow="hidden">
{showSidePanel && <SidePanel />}
{renderMainContent()} {/* theia or page content */}
</Flex>

{/* ✅ Footer always present */}
<Footer />
</Flex>
)
}

Effect: Every route gets wrapped, ensuring Header/Footer persistence.


File: src/components/theia-embed.tsx

Purpose: Embeds theia in secure iframe

<Box
as="iframe"
ref={iframeRef}
src={sessionId ? `${theiaUrl}?sessionId=${sessionId}` : theiaUrl}
w="100%"
h="100%"
border="none"
title="theia IDE"
allow="clipboard-read; clipboard-write"
sandbox="allow-same-origin allow-scripts allow-forms allow-modals allow-popups allow-downloads"
// ✅ NO allow-top-navigation - prevents breakout
/>

Security Properties:

  1. allow-same-origin - Enables postMessage communication
  2. allow-scripts - theia needs JavaScript
  3. allow-forms - theia forms (settings, search)
  4. allow-modals - theia dialogs
  5. allow-popups - theia file downloads
  6. allow-downloads - File download support
  7. NOT included: allow-top-navigation (prevents parent navigation)

File: nginx-combined.conf

Purpose: NGINX routing with redirect protection

# SECURITY: Prevent direct access to theia - force users through V5 wrapper
location = /theia {
return 301 /ide; # Redirect to wrapper
}

# API and WebSocket endpoints for theia (accessed via iframe)
location /theia/ {
rewrite ^/theia/(.*) /$1 break;
proxy_pass http://localhost:3000;
proxy_http_version 1.1;

# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

# ... other proxy settings
}

Routing Logic:

  • http://coditect.ai/theia → 301 redirect to /ide
  • http://coditect.ai/theia/services → proxied to theia
  • http://coditect.ai/theia/file → proxied to theia
  • Root path / serves V5 React SPA

File: src/app.tsx

Purpose: React Router configuration

<Routes>
{/* Root redirect based on auth */}
<Route path="/" element={isAuthenticated ? <Navigate to="/ide" /> : <Navigate to="/login" />} />

{/* IDE routes with wrapper */}
<Route path="/ide" element={
isAuthenticated ? (
<layout showIDE tabModality="theia" />
) : (
<Navigate to="/login" />
)
} />

<Route path="/workspace" element={
isAuthenticated ? (
<layout showIDE tabModality="workspace" />
) : (
<Navigate to="/login" />
)
} />

{/* Page routes with wrapper */}
<Route path="/docs" element={<layout showIDE={false}><DocumentationPage /></layout>} />
<Route path="/settings" element={<layout showIDE={false}><SettingsPage /></layout>} />

{/* Catch-all */}
<Route path="*" element={<Navigate to="/" />} />
</Routes>

Effect: All routes use <layout>, ensuring wrapper presence.


Security Benefits

1. Consistent User Experience

  • Users always see Coditect branding
  • Navigation always available (Header)
  • Footer links always accessible

2. Session Management

  • Multi-session tabs managed by wrapper
  • Session switching UI available
  • Session state tracked in V5 frontend

3. Authentication Integration

  • Protected routes enforced by React Router
  • Login/logout handled in wrapper
  • User profile/settings accessible

4. Documentation Access

  • /docs, /support, /faq available via Header navigation
  • Help resources always one click away
  • Status page accessible

5. Branding Protection

  • Coditect logo and theme always visible
  • Custom design system enforced
  • Professional appearance maintained

Trade-offs

Advantages

User Experience: Consistent navigation and branding ✅ Security: Controlled access to theia backend ✅ Features: Full V5 features always available ✅ Branding: Coditect identity maintained ✅ Documentation: Help always accessible

Disadvantages

⚠️ Performance: Iframe introduces slight latency (~20-50ms) ⚠️ Complexity: Extra layer between user and theia ⚠️ Debugging: Iframe can complicate DevTools inspection ⚠️ Development: Must use localhost:5173, not localhost:3000

Mitigations

  • Performance: Acceptable for browser-based IDE use case
  • Complexity: Well-documented architecture, clear separation
  • Debugging: Iframe can be inspected via DevTools → Frames
  • Development: Documented in developer guide

Alternatives Considered

Alternative 1: Direct theia Access (No Wrapper)

Approach: Users access theia directly at / without wrapper.

Pros:

  • Simpler architecture
  • Slightly better performance
  • No iframe complexity

Cons:

  • ❌ No custom branding
  • ❌ No navigation to docs/settings
  • ❌ No session management UI
  • ❌ No authentication integration
  • ❌ Loss of V5 features

Rejected: Loses critical business requirements (branding, navigation, features).


Alternative 2: theia Extension for Navigation

Approach: Build theia extensions for Header/Footer instead of wrapper.

Pros:

  • Native theia integration
  • No iframe needed
  • Slightly better performance

Cons:

  • ❌ Much more complex to build (3-6 months)
  • ❌ Harder to maintain
  • ❌ theia updates could break extensions
  • ❌ Less flexible than React wrapper
  • ❌ Requires deep theia expertise

Rejected: Too complex, slower development, harder to maintain.


Alternative 3: Single-Page App Without Iframe

Approach: Embed theia widgets directly into React components (no iframe).

Pros:

  • No iframe overhead
  • Seamless integration
  • Better performance

Cons:

  • ❌ Very complex integration (6-12 months)
  • ❌ Requires custom theia build
  • ❌ CSS conflicts between React and theia
  • ❌ Harder to update theia versions
  • ❌ Risk of breaking changes

Rejected: Too complex, high risk, long development time.


Alternative 4: Referer Check (Instead of Redirect)

Approach: Check HTTP Referer header to allow only iframe access.

location /theia {
if ($http_referer !~* "^https?://[^/]+/$") {
return 403; # Forbidden
}
proxy_pass http://localhost:3000;
}

Pros:

  • More restrictive than redirect
  • Prevents direct browser access

Cons:

  • ❌ Referer header can be spoofed
  • ❌ Breaks legitimate use cases (e.g., curl, API clients)
  • ❌ Browser extensions can strip Referer
  • ❌ Less user-friendly (403 error vs redirect)

Rejected: Redirect is more user-friendly and sufficient for our use case.


Testing Strategy

Manual Testing

Test Case 1: Direct /theia Access (Production)

# Expected: Redirect to /ide
curl -I http://coditect.ai/theia
# HTTP/1.1 301 Moved Permanently
# Location: /ide

Test Case 2: Wrapper Navigation

# Expected: V5 wrapper loads, theia in iframe
curl http://coditect.ai/ide
# Returns V5 React SPA HTML

Test Case 3: theia API Access (From Iframe)

# Expected: Proxied to theia backend
curl http://coditect.ai/theia/services
# Returns theia WebSocket endpoint

Test Case 4: Iframe Sandbox (Browser DevTools)

// In theia iframe console:
window.top.location = 'http://evil.com'
// Expected: SecurityError - blocked by sandbox

Automated Testing

Test File: tests/e2e/wrapper-persistence.spec.ts

describe('Wrapper Persistence', () => {
it('redirects /theia to /ide', async () => {
const response = await fetch('/theia', { redirect: 'manual' })
expect(response.status).toBe(301)
expect(response.headers.get('Location')).toBe('/ide')
})

it('shows Header and Footer on /ide', async () => {
await page.goto('/ide')
expect(await page.locator('header').isVisible()).toBe(true)
expect(await page.locator('footer').isVisible()).toBe(true)
})

it('embeds theia in iframe', async () => {
await page.goto('/ide')
const iframe = await page.locator('iframe[title="theia IDE"]')
expect(await iframe.isVisible()).toBe(true)
})

it('prevents iframe from navigating parent', async () => {
await page.goto('/ide')
const iframe = await page.frameLocator('iframe[title="theia IDE"]')

// Try to navigate parent (should fail)
await iframe.evaluate(() => {
try {
window.top.location = 'http://evil.com'
return 'success'
} catch (e) {
return 'blocked'
}
})

expect(result).toBe('blocked')
})
})

Rollout Plan

Phase 1: Development (Complete)

✅ Update nginx-combined.conf with redirect ✅ Verify iframe sandbox attributes ✅ Test wrapper persistence locally

Phase 2: Staging

  • Deploy to staging environment
  • Run E2E test suite
  • Manual QA testing
  • Performance validation

Phase 3: Production

  • Deploy to production (blue-green rollout)
  • Monitor redirect metrics
  • Monitor iframe performance
  • User feedback collection

Phase 4: Documentation

✅ Create ADR-026 (this document) ✅ Update architecture docs ✅ Update developer guides ✅ Add visual diagrams


Monitoring

Metrics to Track

  1. Redirect Rate

    • Metric: nginx_redirect_count{path="/theia"}
    • Alert: If redirect count > 100/day (users trying to bypass)
  2. Iframe Load Time

    • Metric: theia_iframe_load_duration_ms
    • Target: < 500ms (p95)
  3. Wrapper Navigation

    • Metric: v5_header_click_count
    • Goal: High engagement with wrapper navigation
  4. Direct Access Attempts

    • Metric: direct_theia_access_count
    • Alert: If growing trend (education needed)

Logging

# NGINX access log
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';

# Track redirects
access_log /var/log/nginx/access.log main;

Query Example:

# Count /theia redirects per day
grep "GET /theia HTTP" /var/log/nginx/access.log | grep "301" | wc -l

References


Appendix A: Configuration Files

nginx-combined.conf (Full)

# NGINX configuration for combined V5 Frontend + theia Backend

server {
listen 80;
server_name _;

# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;

# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript
application/x-javascript application/xml+rss application/json
application/javascript;

# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}

# V5 Frontend (React SPA) - Root path
location / {
root /app/v5-frontend;
try_files $uri $uri/ /index.html;

# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}

# Don't cache index.html
location = /index.html {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
}

# SECURITY: Prevent direct access to theia
location = /theia {
return 301 /ide;
}

# theia Backend - API/WebSocket (iframe only)
location /theia/ {
rewrite ^/theia/(.*) /$1 break;
proxy_pass http://localhost:3000;
proxy_http_version 1.1;

# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

# Proxy headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# Timeouts
proxy_read_timeout 86400;
proxy_send_timeout 86400;
proxy_connect_timeout 86400;

# Buffer settings
proxy_buffering off;
proxy_request_buffering off;
}

# theia WebSocket endpoint
location /theia/services {
rewrite ^/theia/(.*) /$1 break;
proxy_pass http://localhost:3000;
proxy_http_version 1.1;

# WebSocket headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# No timeouts for WebSocket
proxy_read_timeout 0;
proxy_send_timeout 0;
}
}

Appendix B: User Documentation

For End Users

How to Access the IDE:

Correct Way:

https://coditect.ai/ide

Incorrect Way (will redirect):

https://coditect.ai/theia

Why the wrapper is important:

  • Navigation to documentation, settings, profile
  • Session management UI (switch between projects)
  • Coditect branding and custom features
  • Theme customization
  • Help and support access

For Developers

Development Access:

Correct Way (with wrapper):

http://localhost:5173/ide

⚠️ Not Recommended (direct theia, no wrapper):

http://localhost:3000

Why use the wrapper in development?

  • Test wrapper integration early
  • Verify navigation works
  • Catch iframe issues during development
  • Matches production behavior

When direct theia access is OK:

  • Debugging theia-specific issues
  • Testing theia extensions
  • theia backend development

Decision

We adopt the multi-layered wrapper persistence architecture:

  1. ✅ React Router with layout component for all routes
  2. ✅ Iframe sandbox to prevent theia breakout
  3. ✅ NGINX redirect to enforce wrapper access
  4. ✅ Developer documentation for proper usage

Rationale:

  • Ensures consistent user experience
  • Maintains Coditect branding
  • Provides full V5 feature access
  • Minimal performance impact
  • Simple to implement and maintain

Status: Accepted and implemented (2025-10-13)


Last Updated: 2025-10-13 Version: 1.0 Author: Architecture Team Reviewers: Development Team, Security Team