Skip to main content

V5 Frontend Integration Plan

Date: 2025-10-08 Goal: Integrate V4 multi-session workspace + theia llm chat into V5 wrapper Timeline: 8-12 hours (1-2 days) Status: 🟡 Planning → Implementation


🎯 Integration Objective

Merge three separate frontend implementations into one cohesive V5 application:

  1. V5 Wrapper (current) - Modern Header/Footer/Auth connected to V5 API
  2. V4 Frontend-Original - Multi-session workspace with tabs
  3. theia llm Extension - 4-mode llm chat widget

Result: Professional IDE wrapper with multi-session support, VS Code-like layout, and integrated llm chat.


📊 Component Inventory

Source: V5 Current (/workspace/PROJECTS/t2/src/)

ComponentSizeStatusAction
app.tsx369 lines✅ KeepMerge routes
header.tsx5.4KB✅ KeepNo changes
footer.tsx3.8KB✅ KeepNo changes
layout.tsx2.7KB🔄 EnhanceAdd workspace area
side-panel.tsx5.9KB🔄 AdaptMake collapsible
theia-embed.tsx4.0KB✅ KeepFor theia pods
login-page.tsx5.0KB✅ KeepNo changes
register-page.tsx10KB✅ KeepNo changes
auth-store.ts14KB✅ KeepConnected to V5 API
use-theia-theme.ts1.8KB✅ KeepTheme sync
[20+ doc pages]~50KB✅ KeepNo changes

Source: V4 Frontend-Original (/workspace/PROJECTS/t2/src/frontend-original/)

ComponentSizeStatusAction
unified-workspace.tsx12KB✅ CopyAdapt for V5
workspace-store.ts12KB✅ CopyUpdate API endpoints
resizable-panel.tsx3.5KB✅ CopyUse as-is
editor.tsx2.2KB⏭️ Skiptheia provides
file-explorer.tsx12KB⏭️ Skiptheia provides
terminal.tsx10KB⏭️ Skiptheia provides

Source: theia llm Extension (/workspace/PROJECTS/t2/src/browser/llm-integration/)

ComponentSizeStatusAction
llm-chat-widget.tsx12KB✅ ExtractConvert to React
llm-service.ts~8KB✅ CopyAdapt for wrapper

🏗️ Target Architecture

┌─────────────────────────────────────────────────────────────┐
│ V5 Frontend Wrapper │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Header (V5) │ │
│ │ Logo | Theme Toggle | User Menu | Docs │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Main Content Area │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ Multi-Session Tabs (V4) │ │ │
│ │ │ [Session 1] [Session 2] [Session 3] [+] │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ Unified workspace (V4 + theia llm) │ │ │
│ │ │ │ │ │
│ │ │ ┌──┬────────────────────────┬─────────────────┐ │ │ │
│ │ │ │🔍│ Explorer / Search │ editor Area │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │🌳│ - Files │ [File tabs] │ │ │ │
│ │ │ │ │ - Projects │ - main.ts │ │ │ │
│ │ │ │📂│ - Tasks │ - app.tsx │ │ │ │
│ │ │ │ │ │ - README.md │ │ │ │
│ │ │ │🤖│ AI Agents │ │ │ │ │
│ │ │ │ │ - Code Gen │ [Code editor] │ │ │ │
│ │ │ │💬│ - llm Chat (theia) │ │ │ │ │
│ │ │ │ │ - Review │ │ │ │ │
│ │ │ │⚙️│ Settings │ │ │ │ │
│ │ │ └──┴────────────────────────┴─────────────────┘ │ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────────────────────────────────────┐ │ │ │
│ │ │ │ Bottom Panel (Collapsible) │ │ │ │
│ │ │ │ [terminal] [llm Chat] [Logs] [Problems] │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ > npm run dev │ │ │ │
│ │ │ │ Building... │ │ │ │
│ │ │ └──────────────────────────────────────────────┘ │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Footer (V5) │ │
│ │ © 2025 Coditect | Privacy | Terms | Status │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

📋 Detailed Integration Steps

Phase 1: Setup & Preparation (30 minutes)

1.1 Create New Component Directories

mkdir -p src/components/workspace
mkdir -p src/components/llm
mkdir -p src/components/session-tabs
mkdir -p src/stores
mkdir -p src/services

1.2 Install Missing Dependencies

# Check if we need any additional packages
npm install react-resizable-panels # Already installed ✅
npm install react-icons # Already installed ✅

Phase 2: Copy V4 Components (1 hour)

2.1 Copy Unifiedworkspace Component

Source: src/frontend-original/src/components/workspace/unified-workspace.tsx Destination: src/components/workspace/unified-workspace.tsx

Changes Needed:

// Remove theia-specific imports
// Change from: import { FiFileText, ... } from 'react-icons/fi'
// Change to: Keep react-icons (already have it)

// Update layout structure
- Remove theia widgets (editor, terminal, file-explorer)
- Keep Activity Bar
- Keep Panel system
- Add llm Chat integration point

2.2 Copy workspaceStore

Source: src/frontend-original/src/stores/workspace-store.ts Destination: src/stores/workspace-store.ts

Changes Needed:

// Update API endpoints
- OLD: apiUrl('/workspaces')NEW: apiUrl('/v5/sessions')
- OLD: apiUrl('/projects')NEW: apiUrl('/v5/projects') (if exists)
- OLD: apiUrl('/tasks')NEW: apiUrl('/v5/tasks') (if exists)

// Update data models to match V5 API
interface Session { // Was: workspace
id: string
tenantId: string // Match V5 API field names
userId: string
name: string
workspacePath?: string
createdAt: string
updatedAt: string
lastAccessedAt: string
}

2.3 Copy ResizablePanel

Source: src/frontend-original/src/components/resizable-panel.tsx Destination: src/components/workspace/resizable-panel.tsx

Changes: None needed (utility component)


Phase 3: Extract theia llm Widget (2 hours)

3.1 Convert llmChatWidget to Standalone React Component

Source: src/browser/llm-integration/llm-chat-widget.tsx (theia widget) Destination: src/components/llm/llmChat.tsx (React component)

Conversion Steps:

// BEFORE (theia widget):
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
@injectable()
export class llmChatWidget extends ReactWidget {
protected render(): React.ReactNode { ... }
}

// AFTER (React component):
import React, { useState, useEffect } from 'react';
export const llmChat: React.FC<llmChatProps> = () => {
const [messages, setMessages] = useState<llmMessage[]>([]);
const [mode, setMode] = useState<WorkflowMode>('single');
// ... rest of component

return (
<div className="llm-chat-container">
{/* Same UI as theia widget */}
</div>
);
}

Key Changes:

  1. Remove @injectable() and @inject() decorators
  2. Convert class properties → React state (useState)
  3. Convert methods → functions
  4. Keep all UI exactly the same
  5. Keep 4 workflow modes (single, parallel, sequential, consensus)
  6. Keep model selectors

3.2 Copy llmService

Source: src/browser/llm-integration/services/llm-service.ts Destination: src/services/llm-service.ts

Changes Needed:

// Remove theia dependency injection
- Remove: @injectable()
- Remove: import from '@theia/core'

// Make it a plain TypeScript class
export class llmService {
private readonly LM_STUDIO_BASE_URL =
import.meta.env.VITE_LM_STUDIO_URL || 'http://localhost:1234/v1';

// Keep all methods as-is
async chatCompletion(...) { ... }
async getAvailableModels() { ... }
// etc.
}

// Export singleton instance
export const llmService = new llmService();

3.3 Create llm Store (Zustand)

New File: src/stores/llm-store.ts

import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { llmService } from '../services/llm-service';

interface llmState {
// State
messages: llmMessage[];
mode: WorkflowMode;
primaryModel: string | null;
secondaryModel: string | null;
availableModels: Array<{ id: string; name: string; provider: string }>;
isLoading: boolean;

// Actions
sendMessage: (content: string) => Promise<void>;
setMode: (mode: WorkflowMode) => void;
setPrimaryModel: (model: string) => void;
setSecondaryModel: (model: string) => void;
clearMessages: () => void;
loadModels: () => Promise<void>;
}

export const usellmStore = create<llmState>()(
persist(
(set, get) => ({
messages: [],
mode: 'single',
primaryModel: null,
secondaryModel: null,
availableModels: [],
isLoading: false,

sendMessage: async (content: string) => {
// Implementation from llmChatWidget
},

setMode: (mode) => set({ mode }),
setPrimaryModel: (model) => set({ primaryModel: model }),
setSecondaryModel: (model) => set({ secondaryModel: model }),
clearMessages: () => set({ messages: [] }),

loadModels: async () => {
const models = await llmService.getAvailableModels();
set({ availableModels: models });
}
}),
{ name: 'llm-storage' }
)
);

Phase 4: Create Session Tab Manager (1.5 hours)

New File: src/components/session-tabs/session-tab-manager.tsx

import React from 'react';
import { Box, HStack, IconButton, Text, useColorModeValue } from '@chakra-ui/react';
import { FiPlus, FiX } from 'react-icons/fi';
import { useworkspaceStore } from '../../stores/workspaceStore';

interface SessionTab {
id: string;
name: string;
isDirty?: boolean;
}

export const SessionTabManager: React.FC = () => {
const { sessions, currentSession, setCurrentSession, createSession, deleteSession } = useworkspaceStore();

const bgColor = useColorModeValue('gray.100', 'gray.800');
const activeBg = useColorModeValue('white', 'gray.700');
const borderColor = useColorModeValue('gray.200', 'gray.600');

return (
<HStack
spacing={0}
borderBottom={`1px solid ${borderColor}`}
bg={bgColor}
px={2}
py={1}
overflowX="auto"
>
{sessions.map((session) => (
<Box
key={session.id}
px={4}
py={2}
bg={currentSession?.id === session.id ? activeBg : 'transparent'}
borderBottom={currentSession?.id === session.id ? '2px solid' : 'none'}
borderColor="blue.500"
cursor="pointer"
onClick={() => setCurrentSession(session)}
position="relative"
display="flex"
alignItems="center"
gap={2}
_hover={{ bg: useColorModeValue('gray.50', 'gray.700') }}
>
<Text fontSize="sm" fontWeight={currentSession?.id === session.id ? 'semibold' : 'normal'}>
{session.name}
</Text>
{session.isDirty && (
<Box w="6px" h="6px" borderRadius="full" bg="blue.500" />
)}
<IconButton
aria-label="Close session"
icon={<FiX />}
size="xs"
variant="ghost"
onClick={(e) => {
e.stopPropagation();
deleteSession(session.id);
}}
/>
</Box>
))}

<IconButton
aria-label="New session"
icon={<FiPlus />}
size="sm"
variant="ghost"
onClick={() => createSession('Untitled')}
/>
</HStack>
);
};

Phase 5: Integrate into V5 layout (2 hours)

5.1 Update layout.tsx

File: src/components/layout.tsx

import React from 'react';
import { Box, Flex } from '@chakra-ui/react';
import { Header } from './Header';
import { Footer } from './Footer';
import { SessionTabManager } from './session-tabs/SessionTabManager';
import { Unifiedworkspace } from './workspace/Unifiedworkspace';

interface layoutProps {
children?: React.ReactNode;
showIDE?: boolean;
sessionId?: string;
}

export const layout: React.FC<layoutProps> = ({ children, showIDE = false, sessionId }) => {
return (
<Flex direction="column" h="100vh">
<Header />

{showIDE ? (
<Box flex="1" overflow="hidden">
{/* Multi-session tabs */}
<SessionTabManager />

{/* Unified workspace (VS Code-like layout) */}
<Unifiedworkspace />
</Box>
) : (
<Box flex="1" overflow="auto">
{children}
</Box>
)}

<Footer />
</Flex>
);
};

5.2 Update app.tsx Routes

File: src/app.tsx

// Add new imports
import { Unifiedworkspace } from './components/workspace/Unifiedworkspace';

// Update /ide route
<Route
path="/ide"
element={
isAuthenticated ? (
<layout showIDE>
{/* Unifiedworkspace is rendered by layout */}
</layout>
) : (
<Navigate to="/login" />
)
}
/>

// Add /ide/:sessionId route for specific sessions
<Route
path="/ide/:sessionId"
element={
isAuthenticated ? (
<layout showIDE sessionId={undefined} />
) : (
<Navigate to="/login" />
)
}
/>

Phase 6: Update Stores for V5 API (1.5 hours)

6.1 Update workspace-store.ts

File: src/stores/workspace-store.ts

import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface Session {
id: string;
tenantId: string;
userId: string;
name: string;
workspacePath?: string;
createdAt: string;
updatedAt: string;
lastAccessedAt: string;
isDirty?: boolean;
}

interface workspaceState {
// Data
sessions: Session[];
currentSession: Session | null;
isLoading: boolean;
error: string | null;

// Actions
fetchSessions: () => Promise<void>;
setCurrentSession: (session: Session) => void;
createSession: (name: string) => Promise<void>;
updateSession: (id: string, data: Partial<Session>) => Promise<void>;
deleteSession: (id: string) => Promise<void>;
}

const apiUrl = (path: string) => {
const base = import.meta.env.VITE_API_URL || 'http://34.46.212.40/api';
return `${base}${path}`;
};

export const useworkspaceStore = create<workspaceState>()(
persist(
(set, get) => ({
sessions: [],
currentSession: null,
isLoading: false,
error: null,

fetchSessions: async () => {
const token = localStorage.getItem('auth_token');
if (!token) return;

set({ isLoading: true, error: null });

try {
const response = await fetch(apiUrl('/v5/sessions'), {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});

if (!response.ok) {
throw new Error('Failed to fetch sessions');
}

const data = await response.json();
const sessions = data.data || [];

set({
sessions,
currentSession: get().currentSession || sessions[0] || null
});
} catch (error) {
set({ error: error instanceof Error ? error.message : 'Failed to fetch sessions' });
} finally {
set({ isLoading: false });
}
},

setCurrentSession: (session) => {
set({ currentSession: session });
},

createSession: async (name: string) => {
const token = localStorage.getItem('auth_token');
if (!token) return;

try {
const response = await fetch(apiUrl('/v5/sessions'), {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ name })
});

if (!response.ok) {
throw new Error('Failed to create session');
}

const data = await response.json();
const newSession = data.data;

set(state => ({
sessions: [...state.sessions, newSession],
currentSession: newSession
}));
} catch (error) {
set({ error: error instanceof Error ? error.message : 'Failed to create session' });
}
},

updateSession: async (id: string, updates: Partial<Session>) => {
const token = localStorage.getItem('auth_token');
if (!token) return;

try {
const response = await fetch(apiUrl(`/v5/sessions/${id}`), {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(updates)
});

if (!response.ok) {
throw new Error('Failed to update session');
}

const data = await response.json();
const updatedSession = data.data;

set(state => ({
sessions: state.sessions.map(s => s.id === id ? updatedSession : s),
currentSession: state.currentSession?.id === id ? updatedSession : state.currentSession
}));
} catch (error) {
set({ error: error instanceof Error ? error.message : 'Failed to update session' });
}
},

deleteSession: async (id: string) => {
const token = localStorage.getItem('auth_token');
if (!token) return;

try {
const response = await fetch(apiUrl(`/v5/sessions/${id}`), {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`
}
});

if (!response.ok) {
throw new Error('Failed to delete session');
}

set(state => {
const remainingSessions = state.sessions.filter(s => s.id !== id);
return {
sessions: remainingSessions,
currentSession: state.currentSession?.id === id
? remainingSessions[0] || null
: state.currentSession
};
});
} catch (error) {
set({ error: error instanceof Error ? error.message : 'Failed to delete session' });
}
}
}),
{ name: 'workspace-storage' }
)
);

Phase 7: Styling & Polish (1 hour)

7.1 Create workspace Styles

New File: src/components/workspace/workspace.css

/* Unified workspace Styles */
.unified-workspace {
display: flex;
height: 100%;
overflow: hidden;
}

.activity-bar {
width: 48px;
background: var(--chakra-colors-gray-800);
border-right: 1px solid var(--chakra-colors-gray-700);
display: flex;
flex-direction: column;
}

.sidebar {
width: 300px;
background: var(--chakra-colors-gray-800);
border-right: 1px solid var(--chakra-colors-gray-700);
overflow-y: auto;
}

.editor-area {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}

.editor-tabs {
display: flex;
background: var(--chakra-colors-gray-800);
border-bottom: 1px solid var(--chakra-colors-gray-700);
overflow-x: auto;
}

.editor-content {
flex: 1;
overflow: auto;
background: var(--chakra-colors-gray-900);
}

.bottom-panel {
height: 200px;
background: var(--chakra-colors-gray-800);
border-top: 1px solid var(--chakra-colors-gray-700);
display: flex;
flex-direction: column;
}

/* llm Chat Styles */
.llm-chat-container {
display: flex;
flex-direction: column;
height: 100%;
background: var(--chakra-colors-gray-900);
}

.llm-chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
}

.llm-message {
padding: 12px;
margin-bottom: 12px;
border-radius: 8px;
background: var(--chakra-colors-gray-800);
border: 1px solid var(--chakra-colors-gray-700);
}

.llm-message.user {
background: var(--chakra-colors-blue-900);
border-color: var(--chakra-colors-blue-700);
}

7.2 Import Styles in Unifiedworkspace

import './workspace.css';

Phase 8: Testing & Integration (2 hours)

8.1 Local Development Test Checklist

# Start dev server
npm run prototype:dev

# Test sequence:
1. Register new user (test-integration@example.com)
2. Login successfully
3. Redirect to /ide
4. See session tabs appear
5. Click "+" to create new session
6. Switch between sessions
7. Open llm Chat in activity bar
8. Test all 4 workflow modes:
- Single mode with one model
- Parallel mode with two models
- Sequential mode (llm1 → llm2)
- Consensus mode
9. Verify session persistence (refresh page)
10. Logout and login again
11. Verify sessions restored

8.2 Known Issues & Fixes

Issue 1: CORS errors when calling V5 API

// Fix in backend: Add CORS headers
// Or use proxy in vite.config.ts:
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://34.46.212.40',
changeOrigin: true
}
}
}
})

Issue 2: Session not persisting after page reload

// Fix: Load sessions on app mount
useEffect(() => {
if (isAuthenticated) {
workspaceStore.fetchSessions();
}
}, [isAuthenticated]);

📁 File Structure (After Integration)

src/
├── app.tsx # ✅ Updated with new routes
├── main.tsx # ✅ No changes
├── index.css # ✅ No changes

├── components/
│ ├── header.tsx # ✅ No changes
│ ├── footer.tsx # ✅ No changes
│ ├── layout.tsx # 🔄 Enhanced with workspace
│ ├── side-panel.tsx # ✅ Keep as-is
│ ├── theia-embed.tsx # ✅ Keep for theia pods
│ │
│ ├── workspace/ # 🆕 NEW
│ │ ├── unified-workspace.tsx # ✅ From V4
│ │ ├── resizable-panel.tsx # ✅ From V4
│ │ └── workspace.css # 🆕 NEW
│ │
│ ├── session-tabs/ # 🆕 NEW
│ │ └── session-tab-manager.tsx # 🆕 NEW
│ │
│ ├── llm/ # 🆕 NEW
│ │ ├── llmChat.tsx # ✅ From theia widget
│ │ ├── model-selector.tsx # 🆕 Extracted
│ │ └── mode-selector.tsx # 🆕 Extracted
│ │
│ └── [Origami components] # ✅ No changes

├── pages/ # ✅ All existing pages
│ ├── login-page.tsx
│ ├── register-page.tsx
│ └── [20+ doc pages]

├── stores/ # 🔄 Enhanced
│ ├── auth-store.ts # ✅ No changes
│ ├── workspace-store.ts # 🆕 NEW (from V4, adapted)
│ └── llm-store.ts # 🆕 NEW

├── services/ # 🆕 NEW
│ └── llm-service.ts # ✅ From theia extension

├── hooks/
│ └── use-theia-theme.ts # ✅ No changes

├── theme/
│ └── index.ts # ✅ No changes

└── types/
└── index.ts # 🔄 Add WorkflowMode, etc.

🔄 API Endpoint Mapping

FeatureV4 EndpointV5 EndpointStatus
Sessions/workspaces/v5/sessions✅ EXISTS
Session CreatePOST /workspacesPOST /v5/sessions✅ EXISTS
Session ListGET /workspacesGET /v5/sessions✅ EXISTS
Session GetGET /workspaces/{id}GET /v5/sessions/{id}✅ EXISTS
Session UpdatePATCH /workspaces/{id}PATCH /v5/sessions/{id}❌ NEED TO ADD
Session DeleteDELETE /workspaces/{id}DELETE /v5/sessions/{id}✅ EXISTS
Projects/projects/v5/projects❌ FUTURE
Tasks/tasks/v5/tasks❌ FUTURE

Note: We'll use sessions instead of workspaces for V5. Projects and tasks are future features.


⏱️ Time Estimates

PhaseTaskTimePriority
1Setup & Preparation30m🔴 Critical
2Copy V4 Components1h🔴 Critical
3Extract theia llm Widget2h🔴 Critical
4Create Session Tab Manager1.5h🔴 Critical
5Integrate into V5 layout2h🔴 Critical
6Update Stores for V5 API1.5h🔴 Critical
7Styling & Polish1h🟡 High
8Testing & Integration2h🔴 Critical
Total11.5h~1.5 days

🎯 Success Criteria

Functional Requirements

  • User can register and login with V5 API
  • User sees session tabs after login
  • User can create new sessions (+ button)
  • User can switch between sessions (tab click)
  • User can close sessions (X button)
  • Unifiedworkspace renders with activity bar
  • llm Chat accessible from activity bar
  • llm Chat has 4 workflow modes
  • Model selection works (16+ models)
  • Sessions persist across page reloads
  • Sessions sync with V5 backend API

UI Requirements

  • Header/Footer remain from V5
  • Theme toggle works (dark/light)
  • Responsive layout (no horizontal scroll)
  • Activity bar icons visible
  • editor area functional
  • Bottom panel collapsible
  • Professional VS Code-like appearance

Technical Requirements

  • No TypeScript errors
  • No console errors
  • API calls use correct V5 endpoints
  • JWT auth working
  • Multi-tenant isolation
  • Local storage persistence
  • Production build succeeds

🚀 Deployment Plan

After successful local testing:

# 1. Create .env.production
VITE_API_URL=http://34.46.212.40/api

# 2. Build production bundle
npm run prototype:build

# 3. Create Dockerfile.frontend
# (see v5-frontend-integration-plan.md Phase 9)

# 4. Deploy to GKE
gcloud builds submit --config cloudbuild-frontend.yaml

# 5. Update LoadBalancer routing
# / → Frontend
# /api/v5/* → Backend API
# /theia/* → theia pods (future)

📋 Next Steps

Immediate (After plan approval):

  1. Execute Phase 1 (Setup)
  2. Execute Phase 2 (Copy components)
  3. Execute Phase 3 (Extract llm widget)

Short-term (Same session): 4. Execute Phase 4-6 (Integration) 5. Execute Phase 7-8 (Testing)

Deploy (Next session): 6. Production build 7. Deploy to GKE 8. End-to-end testing on production


Plan Status: ✅ READY FOR EXECUTION

Estimated Completion: 1.5 days (solo) | 1 day (with assistance)

Next Action: Proceed with Phase 1 implementation?