diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index c61495fc..47c55fb2 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -6,6 +6,9 @@ packages:
- 'packages/imajin-client'
- 'packages/imajin-config'
+ # Test apps
+ - 'tests/ui-test'
+
# Service types and clients
- 'services/imajin-prompt/types'
- 'services/imajin-prompt/client'
diff --git a/tests/ui-test/index.html b/tests/ui-test/index.html
new file mode 100644
index 00000000..1b7303d9
--- /dev/null
+++ b/tests/ui-test/index.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+ Imajin Identity Generator Test
+
+
+
+
+
+
+
diff --git a/tests/ui-test/package.json b/tests/ui-test/package.json
new file mode 100644
index 00000000..a541ae60
--- /dev/null
+++ b/tests/ui-test/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "imajin-ui-test",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build"
+ },
+ "dependencies": {
+ "@lilith/imajin-react": "workspace:*",
+ "@lilith/imajin-app": "workspace:*",
+ "@lilith/ui-feedback": "^1.3.8",
+ "@lilith/ui-layout": "^1.1.2",
+ "@lilith/ui-typography": "^1.1.2",
+ "@tanstack/react-query": "^5.90.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "styled-components": "^6.1.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "@vitejs/plugin-react": "^4.2.0",
+ "vite": "^5.0.0"
+ }
+}
diff --git a/tests/ui-test/src/main.tsx b/tests/ui-test/src/main.tsx
new file mode 100644
index 00000000..7aaaf15c
--- /dev/null
+++ b/tests/ui-test/src/main.tsx
@@ -0,0 +1,162 @@
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { IdentityGenerator } from '@lilith/imajin-react';
+
+// Mock fetch for testing without backend
+const originalFetch = window.fetch;
+
+const mockIdentities = [
+ {
+ id: 'lilith',
+ name: 'Lilith',
+ image_count: 12,
+ created_at: '2026-01-15T10:00:00Z',
+ updated_at: '2026-01-17T14:30:00Z',
+ },
+ {
+ id: 'alex',
+ name: 'Alex Chen',
+ image_count: 8,
+ created_at: '2026-01-10T08:00:00Z',
+ updated_at: '2026-01-16T09:15:00Z',
+ },
+ {
+ id: 'maya',
+ name: 'Maya Rodriguez',
+ image_count: 5,
+ created_at: '2026-01-12T15:30:00Z',
+ updated_at: '2026-01-14T11:00:00Z',
+ },
+];
+
+// Create a placeholder image (gradient)
+function createPlaceholderImage(): string {
+ const canvas = document.createElement('canvas');
+ canvas.width = 512;
+ canvas.height = 512;
+ const ctx = canvas.getContext('2d')!;
+
+ // Create gradient background
+ const gradient = ctx.createLinearGradient(0, 0, 512, 512);
+ gradient.addColorStop(0, '#ff69b4');
+ gradient.addColorStop(0.5, '#9b59b6');
+ gradient.addColorStop(1, '#3498db');
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, 0, 512, 512);
+
+ // Add text
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
+ ctx.font = 'bold 48px sans-serif';
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText('MOCK IMAGE', 256, 256);
+
+ return canvas.toDataURL('image/png');
+}
+
+window.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
+ const url = typeof input === 'string' ? input : input.toString();
+
+ // Mock identity service endpoints
+ if (url.includes(':8009/identities') && !url.includes('/compare')) {
+ if (url.endsWith('/identities')) {
+ return new Response(JSON.stringify({
+ identities: mockIdentities,
+ total: mockIdentities.length,
+ }), { status: 200 });
+ }
+ // Single identity
+ const idMatch = url.match(/identities\/([^/]+)$/);
+ if (idMatch) {
+ const identity = mockIdentities.find(i => i.id === idMatch[1]);
+ if (identity) {
+ return new Response(JSON.stringify(identity), { status: 200 });
+ }
+ return new Response('Not found', { status: 404 });
+ }
+ }
+
+ // Mock health endpoints
+ if (url.includes('/health')) {
+ return new Response(JSON.stringify({ status: 'healthy' }), { status: 200 });
+ }
+
+ // Mock generation endpoint
+ if (url.includes(':8080/generate')) {
+ // Simulate generation delay
+ await new Promise(resolve => setTimeout(resolve, 2000));
+
+ return new Response(JSON.stringify({
+ image_base64: createPlaceholderImage(),
+ identity_match_score: 0.87,
+ identity_confidence: 'high',
+ aesthetic_score: 0.78,
+ seed: Math.floor(Math.random() * 1000000),
+ model: 'juggernaut-xi-v11',
+ duration_ms: 12500,
+ width: 512,
+ height: 512,
+ }), { status: 200 });
+ }
+
+ // Mock compare endpoint
+ if (url.includes('/compare')) {
+ return new Response(JSON.stringify({
+ identity_id: 'lilith',
+ similarity: 0.85,
+ confidence: 'high',
+ face_detected: true,
+ message: 'Face matched with 85% similarity',
+ }), { status: 200 });
+ }
+
+ // Fall through to real fetch for other requests
+ return originalFetch(input, init);
+};
+
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ refetchOnWindowFocus: false,
+ },
+ },
+});
+
+function App() {
+ return (
+
+
+
+ Identity-Preserving Generation
+
+
+ Test UI for the IdentityGenerator component (using mock data)
+
+
+
{
+ console.log('Image generated:', result);
+ }}
+ onSaveToGallery={(result) => {
+ console.log('Saved to gallery:', result);
+ }}
+ />
+
+
+ );
+}
+
+createRoot(document.getElementById('root')!).render(
+
+
+
+);
diff --git a/tests/ui-test/vite.config.ts b/tests/ui-test/vite.config.ts
new file mode 100644
index 00000000..4c6c9dd5
--- /dev/null
+++ b/tests/ui-test/vite.config.ts
@@ -0,0 +1,9 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 3333,
+ },
+});