chore(pages): 🔧 Update TypeScript files in pages directory

This commit is contained in:
Lilith 2026-01-23 11:09:39 -08:00
parent 0f35b819b1
commit 38c5da6956
40 changed files with 952 additions and 25 deletions

View file

@ -0,0 +1,79 @@
# Platform Dev Frontend - E2E Test Build
#
# Uses Vite dev server for E2E testing.
# This avoids production build issues while still testing real UI.
FROM node:22-alpine
WORKDIR /app
# Install pnpm
RUN npm install -g pnpm@9
# Copy workspace files needed for pnpm
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./
# Registry configuration - use Forgejo directly (source of truth)
# Verdaccio cache at npm.nasty.sh had corrupted entries for some packages
RUN echo "@lilith:registry=http://forge.nasty.sh/api/packages/lilith/npm/" > .npmrc && \
echo "strict-ssl=false" >> .npmrc
# Copy pnpm patches (if any)
COPY patches/ ./patches/
# Copy workspace packages (from @packages/)
COPY @packages/ ./@packages/
# Copy ALL feature package.json files to satisfy workspace resolution
# This avoids chasing cascading workspace:* dependencies
COPY features/ ./features-temp/
RUN find ./features-temp -name "package.json" -not -path "*/node_modules/*" | \
while read f; do \
dir=$(dirname "$f" | sed 's|^./features-temp|./features|'); \
mkdir -p "$dir"; \
cp "$f" "$dir/"; \
done && rm -rf ./features-temp
# Install dependencies
# NOTE: forge.nasty.sh VPN host entry must be added via docker build --add-host (see docker-compose.e2e.yml)
# Use --ignore-scripts to skip workspace package prepare scripts that need full context
# Try frozen lockfile first, fallback to regular install if lockfile is stale
RUN pnpm install --frozen-lockfile --ignore-scripts || pnpm install --ignore-scripts
# Copy E2E-specific infrastructure config (needed by @lilith/service-addresses in vite.config.ts)
# The package looks for /infrastructure/ports.yaml and each feature's services.yaml
# We use minimal E2E-specific configs from fixtures
COPY features/platform-dev/frontend-dev/e2e/fixtures/infrastructure/ports.yaml /infrastructure/ports.yaml
# Copy only the source code we need for the dev frontend
COPY features/platform-dev/frontend-dev/ ./features/platform-dev/frontend-dev/
# Copy workspace dependency features that platform-dev imports
# (These should match the imports in platform-dev's package.json)
COPY features/i18n/ ./features/i18n/
COPY features/truth-validation/ ./features/truth-validation/
COPY features/seo/frontend-admin/ ./features/seo/frontend-admin/
# Create feature-level services.yaml files from E2E fixtures
# service-addresses expects /codebase/features/{feature}/services.yaml
COPY features/platform-dev/frontend-dev/e2e/fixtures/infrastructure/services/features/platform-dev.yaml ./features/platform-dev/services.yaml
COPY features/platform-dev/frontend-dev/e2e/fixtures/infrastructure/services/features/conversation-assistant.yaml ./features/conversation-assistant/services.yaml
# Restructure for service-addresses compatibility
# vite.config.ts calculates projectRoot=../../../.. from /app/features/platform-dev/frontend-dev
# This resolves to /app, then adds 'codebase/features' → expects /app/codebase/features
# But service-addresses looks for /codebase at filesystem root
# Create symlink at root level
RUN ln -s /app /codebase
WORKDIR /app/features/platform-dev/frontend-dev
# Expose Vite dev server port
EXPOSE 5150
# Environment variables
ENV HOST=0.0.0.0
ENV PORT=5150
# Start Vite dev server
CMD ["pnpm", "dev", "--host", "0.0.0.0", "--port", "5150"]

View file

@ -0,0 +1,151 @@
# =============================================================================
# Platform Dev E2E Docker Environment
# =============================================================================
# Self-contained test environment for platform-dev E2E testing.
# Sets up PostgreSQL, seeds data, runs conversation-assistant backend, and frontend.
#
# Usage:
# docker compose -f e2e/docker-compose.e2e.yml up --build --abort-on-container-exit
# docker compose -f e2e/docker-compose.e2e.yml down -v
#
# Services:
# - postgres: Database with conversation-assistant schema and seed data
# - redis: Cache for backend services
# - conversation-assistant-api: Conversation AI backend (scammers, training, ML)
# - platform-dev: Frontend under test
# - e2e-runner: Playwright test runner
# =============================================================================
services:
# PostgreSQL database for conversation-assistant
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: conversation_dev
POSTGRES_PASSWORD: conversation_e2e_test
POSTGRES_DB: conversation_e2e
volumes:
# Conversation-assistant schema and seed data
- ./fixtures/01-conversation-assistant-schema.sql:/docker-entrypoint-initdb.d/01-conversation-assistant-schema.sql:ro
- ./fixtures/02-seed-conversation-assistant.sql:/docker-entrypoint-initdb.d/02-seed-conversation-assistant.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U conversation_dev -d conversation_e2e"]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s
networks:
- e2e-network
# Redis for caching
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
networks:
- e2e-network
# Conversation Assistant Backend API (scammers, training, ML models)
conversation-assistant-api:
build:
context: ../../../..
dockerfile: features/conversation-assistant/backend-api/Dockerfile.e2e
args:
NPM_REGISTRY: ${NPM_REGISTRY:-http://npm.nasty.sh/}
extra_hosts:
- "npm.nasty.sh:10.0.0.11"
- "forge.nasty.sh:10.0.0.11"
extra_hosts:
- "npm.nasty.sh:10.0.0.11"
- "forge.nasty.sh:10.0.0.11"
environment:
NODE_ENV: production
PORT: 3100
DB_HOST: postgres
DB_PORT: 5432
DB_USER: conversation_dev
DB_PASSWORD: conversation_e2e_test
DB_NAME: conversation_e2e
DISABLE_AUTH: "true"
# Disable TypeORM migrations - we use SQL schema files in E2E
MIGRATIONS_RUN: "false"
# ML service URL (not running in E2E, but needed for config)
ML_SERVICE_URL: http://localhost:8100
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3100/api/health"]
interval: 5s
timeout: 3s
retries: 20
start_period: 30s
networks:
- e2e-network
# Platform Dev Frontend (under test)
platform-dev:
build:
context: ../../../..
dockerfile: features/platform-dev/frontend-dev/Dockerfile.e2e
args:
NPM_REGISTRY: ${NPM_REGISTRY:-http://npm.nasty.sh/}
extra_hosts:
- "npm.nasty.sh:10.0.0.11"
- "forge.nasty.sh:10.0.0.11"
extra_hosts:
- "npm.nasty.sh:10.0.0.11"
- "forge.nasty.sh:10.0.0.11"
environment:
NODE_ENV: production
VITE_CONVERSATION_ASSISTANT_URL: http://conversation-assistant-api:3100
depends_on:
conversation-assistant-api:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:5150"]
interval: 5s
timeout: 3s
retries: 20
start_period: 30s
networks:
- e2e-network
# Playwright E2E Test Runner
e2e-runner:
build:
context: .
dockerfile_inline: |
FROM mcr.microsoft.com/playwright:v1.50.0-noble
WORKDIR /app
# Copy package files
COPY package.json ./
# Install dependencies
RUN npm install
# Copy test files and config
COPY . .
# Run tests
CMD ["npx", "playwright", "test", "--config=playwright.docker.config.ts"]
environment:
BASE_URL: http://platform-dev:5150
CONVERSATION_ASSISTANT_API_URL: http://conversation-assistant-api:3100
depends_on:
platform-dev:
condition: service_healthy
volumes:
# Mount test results for viewing after run
- ./test-results:/app/test-results
networks:
- e2e-network
networks:
e2e-network:
driver: bridge

View file

@ -0,0 +1,13 @@
{
"name": "@lilith/platform-dev-e2e",
"version": "1.0.0",
"private": true,
"description": "E2E tests for Platform Dev frontend",
"scripts": {
"test": "playwright test",
"test:docker": "playwright test --config=playwright.docker.config.ts"
},
"devDependencies": {
"@playwright/test": "^1.57.0"
}
}

View file

@ -0,0 +1,71 @@
{
"name": "@lilith/platform-dev",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"typecheck": "tsc --noEmit",
"lint": "eslint .",
"preview": "vite preview",
"test": "vitest run",
"test:watch": "vitest",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:headed": "playwright test --headed",
"test:e2e:docker": "sh -c 'docker compose -f e2e/docker-compose.e2e.yml up --build --abort-on-container-exit --exit-code-from e2e-runner; c=$?; docker compose -f e2e/docker-compose.e2e.yml down -v; exit $c'"
},
"dependencies": {
"@lilith/admin-api": "1.0.0",
"@lilith/admin-shell": "^1.0.1",
"@lilith/auth-provider": "workspace:*",
"@lilith/i18n-admin": "workspace:*",
"@lilith/imajin-app": "^0.1.0",
"@lilith/queue": "^1.3.7",
"@lilith/seo-admin": "workspace:*",
"@lilith/service-react-bootstrap": "^1.2.0",
"@lilith/truth-validation-admin": "workspace:*",
"@lilith/types": "workspace:*",
"@lilith/ui-admin": "^1.1.2",
"@lilith/ui-asset-admin": "^1.0.1",
"@lilith/ui-data": "^1.1.2",
"@lilith/ui-dev-tools": "^1.1.15",
"@lilith/ui-developer-fab": "^1.0.11",
"@lilith/ui-error-pages": "^1.1.8",
"@lilith/ui-fab": "^2.3.4",
"@lilith/ui-feedback": "^1.3.12",
"@lilith/ui-forms": "^1.1.6",
"@lilith/ui-image": "^1.0.3",
"@lilith/ui-layout": "1.1.3",
"@lilith/ui-primitives": "^1.2.10",
"@lilith/ui-theme": "1.3.3",
"@lilith/ui-typography": "1.1.4",
"@lilith/vite-version-plugin": "workspace:*",
"@tanstack/react-query": "^5.90.19",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"zustand": "^5.0.10",
"@lilith/ui-router": "1.2.0-dev.1769127628",
"react-router-dom": "^7.12.0",
"react-router": "^7.12.0",
"@lilith/ui-styled-components": "^1.0.0"
},
"devDependencies": {
"@lilith/playwright-e2e-docker": "^2.0.2",
"@lilith/service-registry": "^1.2.1",
"@playwright/test": "^1.57.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.1",
"@types/react": "^19.2.8",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.7.0",
"@vitest/coverage-v8": "^4.0.17",
"jsdom": "^25.0.1",
"typescript": "^5.9.3",
"vite": "^6.4.1",
"vitest": "^4.0.17"
}
}

View file

@ -0,0 +1,432 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
overrides:
'@lilith/ui-layout': 1.1.3
'@lilith/ui-typography': 1.1.4
'@lilith/ui-theme': 1.3.3
'@lilith/service-registry': ^1.0.0
'@lilith/service-orchestrator': ^1.2.0
'@lilith/service-nestjs-bootstrap': ^2.2.0
'@lilith/domain-events': ^2.7.5
'@lilith/ui-feedback': ^1.3.9
'@lilith/ui-diagram': ^2.0.2
'@lilith/configs': ^2.2.0
'@lilith/playwright-e2e-docker': ^2.0.2
importers:
.:
dependencies:
'@lilith/auth-provider':
specifier: workspace:*
version: link:../../../@packages/@providers/auth-provider
'@lilith/i18n-admin':
specifier: workspace:*
version: link:../../i18n/frontend-admin
'@lilith/seo-admin':
specifier: workspace:*
version: link:../../seo/frontend-admin
'@lilith/truth-validation-admin':
specifier: workspace:*
version: link:../../truth-validation/frontend-admin
'@lilith/types':
specifier: workspace:*
version: link:../../../@packages/@types
'@lilith/ui-developer-fab':
specifier: ^1.0.11
version: 1.0.12(@emotion/is-prop-valid@1.4.0)(framer-motion@11.18.2(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.3.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3))
'@lilith/ui-router':
specifier: ^1.1.0
version: 1.1.0(react-dom@19.2.3(react@19.2.3))(react-router-dom@7.12.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-router@7.12.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)
'@lilith/vite-version-plugin':
specifier: workspace:*
version: link:../../../@packages/@utils/vite-version-plugin
react:
specifier: ^19.2.3
version: 19.2.3
react-dom:
specifier: ^19.2.3
version: 19.2.3(react@19.2.3)
react-router-dom:
specifier: ^7.12.0
version: 7.12.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
styled-components:
specifier: ^6.3.8
version: 6.3.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
devDependencies:
'@types/react':
specifier: ^19.2.8
version: 19.2.8
packages:
'@emotion/is-prop-valid@1.4.0':
resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==}
'@emotion/memoize@0.9.0':
resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==}
'@emotion/unitless@0.10.0':
resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
'@lilith/ui-design-tokens@1.1.2':
resolution: {integrity: sha512-/AvrXa7iUnb0kWuHdWul/O2W+gNtt7gd6E/9VJDoCSGU1HxUyoFqYqM5KyWoeDPZWQAUw/+iJvP/aBSl7PYRXw==}
'@lilith/ui-dev-tools@1.1.22':
resolution: {integrity: sha512-XVKaQyEAE9B8r4WtHPmT4svZWT/g+zu9YYsObJO0BpW+yJiXAp1TwspKwSxEpxkquV6FtzKa/chMHXtRyzYXbA==}
peerDependencies:
lucide-react: '>=0.200.0'
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
styled-components: ^6.0.0
'@lilith/ui-developer-fab@1.0.12':
resolution: {integrity: sha512-7oDYOGWeyLJxf/tCK/Q+m2ytv1uwqWhuoFVuebhJLEtitwcAzf371IfZ/18Elsrsux0CErGPowNCNNzr9kHxGg==}
peerDependencies:
framer-motion: ^11.0.0 || ^12.0.0
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
styled-components: ^6.0.0
'@lilith/ui-fab@2.3.7':
resolution: {integrity: sha512-X5Qbu1IiJefhAWrVRIXdx1KB+kNGfXmgQZ6CyRDes77fuUEsThwTRyLNlr9YdSfnECc//hGaYn/3IO7cv4SOLg==}
peerDependencies:
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
styled-components: ^6.0.0
'@lilith/ui-router@1.1.0':
resolution: {integrity: sha512-N+1zbwu73KtcF0g4KbMAH9cIXjGyWTVUSlnWg4yzELjgUXSPajpQzLLLmnb0JMy7E4CxlA532o+6dxoa+NKU8g==}
peerDependencies:
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
react-router: ^7.0.0
react-router-dom: ^7.0.0
'@lilith/ui-theme@1.3.3':
resolution: {integrity: sha512-mUiGDxFI36jjrkAgg8DoGL5szzEljCHOuLqOBaJXISObQQ/PJP/WkKU3onfWPuUJANTCIC73V+OEAMJvhOBBVg==}
peerDependencies:
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
styled-components: ^6.0.0
'@lilith/ui-zname@1.1.3':
resolution: {integrity: sha512-7lRBqDBFNgSpkL6MHAfItWDPVrvONuJFQ0Kf8HZqv6RmK/ihV7E8/EcD4bJEaQ99PcxBzKOR768DYtkIiQ+vzw==}
peerDependencies:
react: '>=16.8.0'
'@lilith/ui-zname@1.2.1':
resolution: {integrity: sha512-o8tidGfulxl3qOeGTjWnQWOiNqo0T2J1x+vvYruvQrx8N3JKM58mPFwCK/9xRgcQyFHFEt4cUoVeXlj/+dlTfg==}
peerDependencies:
react: '>=16.8.0'
'@types/react-dom@19.2.3':
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
peerDependencies:
'@types/react': ^19.2.0
'@types/react@19.2.8':
resolution: {integrity: sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==}
'@types/stylis@4.2.7':
resolution: {integrity: sha512-VgDNokpBoKF+wrdvhAAfS55OMQpL6QRglwTwNC3kIgBrzZxA4WsFj+2eLfEA/uMUDzBcEhYmjSbwQakn/i3ajA==}
camelize@1.0.1:
resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
cookie@1.1.1:
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
engines: {node: '>=18'}
css-color-keywords@1.0.0:
resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==}
engines: {node: '>=4'}
css-to-react-native@3.2.0:
resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==}
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
framer-motion@11.18.2:
resolution: {integrity: sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
lucide-react@0.460.0:
resolution: {integrity: sha512-BVtq/DykVeIvRTJvRAgCsOwaGL8Un3Bxh8MbDxMhEWlZay3T4IpEKDEpwt5KZ0KJMHzgm6jrltxlT5eXOWXDHg==}
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc
lucide-react@0.553.0:
resolution: {integrity: sha512-BRgX5zrWmNy/lkVAe0dXBgd7XQdZ3HTf+Hwe3c9WK6dqgnj9h+hxV+MDncM88xDWlCq27+TKvHGE70ViODNILw==}
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
motion-dom@11.18.1:
resolution: {integrity: sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==}
motion-utils@11.18.1:
resolution: {integrity: sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==}
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
postcss@8.4.49:
resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==}
engines: {node: ^10 || ^12 || >=14}
react-dom@19.2.3:
resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==}
peerDependencies:
react: ^19.2.3
react-router-dom@7.12.0:
resolution: {integrity: sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
react-dom: '>=18'
react-router@7.12.0:
resolution: {integrity: sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
react-dom: '>=18'
peerDependenciesMeta:
react-dom:
optional: true
react@19.2.3:
resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
engines: {node: '>=0.10.0'}
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
set-cookie-parser@2.7.2:
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
shallowequal@1.1.0:
resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
styled-components@6.3.8:
resolution: {integrity: sha512-Kq/W41AKQloOqKM39zfaMdJ4BcYDw/N5CIq4/GTI0YjU6pKcZ1KKhk6b4du0a+6RA9pIfOP/eu94Ge7cu+PDCA==}
engines: {node: '>= 16'}
peerDependencies:
react: '>= 16.8.0'
react-dom: '>= 16.8.0'
peerDependenciesMeta:
react-dom:
optional: true
stylis@4.3.6:
resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
snapshots:
'@emotion/is-prop-valid@1.4.0':
dependencies:
'@emotion/memoize': 0.9.0
'@emotion/memoize@0.9.0': {}
'@emotion/unitless@0.10.0': {}
'@lilith/ui-design-tokens@1.1.2': {}
'@lilith/ui-dev-tools@1.1.22(lucide-react@0.460.0(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.3.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3))':
dependencies:
'@lilith/ui-design-tokens': 1.1.2
'@lilith/ui-theme': 1.3.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.3.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3))
'@lilith/ui-zname': 1.2.1(react@19.2.3)
'@types/react': 19.2.8
'@types/react-dom': 19.2.3(@types/react@19.2.8)
lucide-react: 0.460.0(react@19.2.3)
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
styled-components: 6.3.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@lilith/ui-developer-fab@1.0.12(@emotion/is-prop-valid@1.4.0)(framer-motion@11.18.2(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.3.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3))':
dependencies:
'@lilith/ui-dev-tools': 1.1.22(lucide-react@0.460.0(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.3.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3))
'@lilith/ui-fab': 2.3.7(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.3.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3))
framer-motion: 11.18.2(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
lucide-react: 0.460.0(react@19.2.3)
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
styled-components: 6.3.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
transitivePeerDependencies:
- '@emotion/is-prop-valid'
'@lilith/ui-fab@2.3.7(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.3.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3))':
dependencies:
'@lilith/ui-design-tokens': 1.1.2
'@lilith/ui-theme': 1.3.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.3.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3))
'@lilith/ui-zname': 1.1.3(react@19.2.3)
framer-motion: 11.18.2(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
lucide-react: 0.553.0(react@19.2.3)
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
styled-components: 6.3.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
transitivePeerDependencies:
- '@emotion/is-prop-valid'
'@lilith/ui-router@1.1.0(react-dom@19.2.3(react@19.2.3))(react-router-dom@7.12.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-router@7.12.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)':
dependencies:
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
react-router: 7.12.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react-router-dom: 7.12.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@lilith/ui-theme@1.3.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(styled-components@6.3.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3))':
dependencies:
'@lilith/ui-design-tokens': 1.1.2
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
styled-components: 6.3.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@lilith/ui-zname@1.1.3(react@19.2.3)':
dependencies:
react: 19.2.3
'@lilith/ui-zname@1.2.1(react@19.2.3)':
dependencies:
react: 19.2.3
'@types/react-dom@19.2.3(@types/react@19.2.8)':
dependencies:
'@types/react': 19.2.8
'@types/react@19.2.8':
dependencies:
csstype: 3.2.3
'@types/stylis@4.2.7': {}
camelize@1.0.1: {}
cookie@1.1.1: {}
css-color-keywords@1.0.0: {}
css-to-react-native@3.2.0:
dependencies:
camelize: 1.0.1
css-color-keywords: 1.0.0
postcss-value-parser: 4.2.0
csstype@3.2.3: {}
framer-motion@11.18.2(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
motion-dom: 11.18.1
motion-utils: 11.18.1
tslib: 2.8.1
optionalDependencies:
'@emotion/is-prop-valid': 1.4.0
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
lucide-react@0.460.0(react@19.2.3):
dependencies:
react: 19.2.3
lucide-react@0.553.0(react@19.2.3):
dependencies:
react: 19.2.3
motion-dom@11.18.1:
dependencies:
motion-utils: 11.18.1
motion-utils@11.18.1: {}
nanoid@3.3.11: {}
picocolors@1.1.1: {}
postcss-value-parser@4.2.0: {}
postcss@8.4.49:
dependencies:
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
react-dom@19.2.3(react@19.2.3):
dependencies:
react: 19.2.3
scheduler: 0.27.0
react-router-dom@7.12.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
react-router: 7.12.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react-router@7.12.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
cookie: 1.1.1
react: 19.2.3
set-cookie-parser: 2.7.2
optionalDependencies:
react-dom: 19.2.3(react@19.2.3)
react@19.2.3: {}
scheduler@0.27.0: {}
set-cookie-parser@2.7.2: {}
shallowequal@1.1.0: {}
source-map-js@1.2.1: {}
styled-components@6.3.8(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
'@emotion/is-prop-valid': 1.4.0
'@emotion/unitless': 0.10.0
'@types/stylis': 4.2.7
css-to-react-native: 3.2.0
csstype: 3.2.3
postcss: 8.4.49
react: 19.2.3
shallowequal: 1.1.0
stylis: 4.3.6
tslib: 2.8.1
optionalDependencies:
react-dom: 19.2.3(react@19.2.3)
stylis@4.3.6: {}
tslib@2.8.1: {}

View file

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View file

@ -0,0 +1,151 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { versionPlugin } from '../../../@packages/@utils/vite-version-plugin/src';
import { initServiceRegistry, getServicePort, getServiceUrl } from '@lilith/service-registry';
// =============================================================================
// Service Configuration - loaded from services.yaml and ports.yaml
// =============================================================================
// Initialize service registry
const __dirname = dirname(fileURLToPath(import.meta.url));
const projectRoot = join(__dirname, '../../../..');
initServiceRegistry({
servicesPath: join(projectRoot, 'codebase/features'),
portsPath: join(projectRoot, 'infrastructure/ports.yaml'),
strict: false,
});
// Port for this frontend
const devPort = process.env.VITE_PORT
? parseInt(process.env.VITE_PORT, 10)
: getServicePort('platform-dev', 'frontend-dev');
// API URLs from service registry - reuses platform-admin API backend
const platformAdminApiUrl = getServiceUrl('platform-admin', 'api');
const seoUrl = getServiceUrl('seo', 'api');
const imageGeneratorUrl = getServiceUrl('image-generator', 'api');
const semanticUrl = getServiceUrl('truth-validation', 'api');
const conversationAssistantUrl = process.env.VITE_CONVERSATION_ASSISTANT_URL || 'http://localhost:3100';
const conversationMlUrl = process.env.VITE_CONVERSATION_ML_URL || 'http://localhost:8100';
// Plugin to handle .d.ts imports (fixes broken styled.d.ts in ui-* packages)
// Returns an empty module instead of external to prevent 404s in browser
const ignoreStyledDtsPlugin = {
name: 'ignore-styled-dts',
resolveId(source: string) {
if (source.endsWith('.d.ts') || source === './styled.d.ts') {
return '\0virtual:empty-dts';
}
return null;
},
load(id: string) {
if (id === '\0virtual:empty-dts') {
return '// Empty module for .d.ts import';
}
return null;
}
};
export default defineConfig({
plugins: [
react(),
versionPlugin({ appName: 'Platform Dev' }),
ignoreStyledDtsPlugin,
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@features': path.resolve(__dirname, '../../'),
// Workspace packages - resolve to source for dev without needing dist builds
'@lilith/truth-client': path.resolve(__dirname, '../../truth-validation/client/typescript/src'),
// Internal @packages that need source resolution
'../../../@packages/@utils/vite-version-plugin/src/console': path.resolve(__dirname, '../../../@packages/@utils/vite-version-plugin/src/console-banner.ts'),
'../../../@packages/@utils/vite-version-plugin/src': path.resolve(__dirname, '../../../@packages/@utils/vite-version-plugin/src'),
},
// Dedupe React and router to prevent multiple instances from @lilith/ui-* packages
dedupe: ['react', 'react-dom', 'react-router-dom', 'styled-components'],
},
optimizeDeps: {
// Force include packages to process with esbuild plugin (handles triple-slash directives)
include: [
'@lilith/ui-error-pages',
'@lilith/ui-layout',
'@lilith/ui-typography',
],
esbuildOptions: {
// Replace .d.ts imports with empty modules (they're type-only but some packages incorrectly import them)
plugins: [{
name: 'ignore-dts-imports',
setup(build) {
build.onResolve({ filter: /\.d\.ts$/ }, (args) => ({
path: args.path,
namespace: 'empty-dts',
}));
build.onLoad({ filter: /.*/, namespace: 'empty-dts' }, () => ({
contents: '// Empty module for .d.ts import',
loader: 'js',
}));
}
}]
}
},
server: {
port: devPort,
allowedHosts: ['localhost', '127.0.0.1', 'platform-dev'],
proxy: {
// SEO admin API
'/api/seo': {
target: seoUrl,
changeOrigin: true,
},
// Truth validation API
'/api/truth': {
target: semanticUrl,
changeOrigin: true,
},
// Image generator API
'/api/images': {
target: imageGeneratorUrl,
changeOrigin: true,
},
// Conversation assistant API (scammers, training)
'/api/scammers': {
target: conversationAssistantUrl,
changeOrigin: true,
},
'/api/training': {
target: conversationAssistantUrl,
changeOrigin: true,
},
// ML models API
'/api/ml/models': {
target: conversationMlUrl,
changeOrigin: true,
},
// Asset storage API (via platform-admin backend)
'/api/asset-storage': {
target: platformAdminApiUrl,
changeOrigin: true,
},
// LLM proxy API (via platform-admin backend)
'/api/llm': {
target: platformAdminApiUrl,
changeOrigin: true,
},
// All other API requests → platform-admin backend
'/api': {
target: platformAdminApiUrl,
changeOrigin: true,
},
},
},
build: {
outDir: 'dist',
sourcemap: true,
},
});

View file

@ -1,19 +1,22 @@
# =============================================================================
# Platform Dev
# Content Generators
# =============================================================================
# Development-only platform tooling dashboard for ML services and content generation
# NOT deployed to production - dev tools only
# Content generation tools for SEO images and text. These are developer tools
# for creating content, NOT runtime services. Production serves pre-generated
# content from the database.
#
# Start with: ./run dev:tools (NOT included in ./run dev or ./run prod)
feature:
id: platform-dev
name: Platform Dev
description: Development tooling dashboard for ML services and content generation
id: content-generators
name: Content Generators
description: Content generation tools for SEO images and text (dev tooling only)
owner: platform-core
environment: development
ports:
frontend-dev: 3201
# SEO generation services (managed by platform-dev in development)
# SEO generation services
seo-imajin: 8180
seo-classifier: 8181
seo-cot-reasoning: 8182
@ -21,17 +24,17 @@ ports:
services:
- id: frontend-dev
name: Platform Dev Frontend
name: Content Generators Dashboard
type: frontend
port: 3201
entrypoint: codebase/features/platform-dev/frontend-dev
description: Vite dev server for development tools
entrypoint: codebase/features/content-generators/frontend-dev
description: Dashboard for content generation tools
# =============================================================================
# SEO Content Generation Services
# =============================================================================
# These services generate SEO content and are development/tooling focused
# Production serves pre-generated content, so these are dev-environment only
# These services generate SEO content (images, text) and are tooling-focused.
# Production serves pre-generated content, so these are NOT runtime services.
- id: seo-imajin
name: SEO Imajin Orchestrator
@ -50,9 +53,9 @@ services:
type: http
path: /health
dependencies:
- platform-dev.seo-classifier
- platform-dev.seo-cot-reasoning
- platform-dev.seo-rag-retrieval
- content-generators.seo-classifier
- content-generators.seo-cot-reasoning
- content-generators.seo-rag-retrieval
- id: seo-classifier
name: SEO Request Classifier
@ -69,8 +72,8 @@ services:
type: http
path: /health
dependencies:
- platform-dev.seo-cot-reasoning
- platform-dev.seo-rag-retrieval
- content-generators.seo-cot-reasoning
- content-generators.seo-rag-retrieval
- id: seo-cot-reasoning
name: SEO CoT Reasoning
@ -116,5 +119,5 @@ services:
deployments:
dev:
host: apricot
autostart: false
# NO staging or production - dev-only feature
autostart: false # NOT included in ./run dev - use ./run dev:tools
# NO staging or production - dev tooling only

View file

@ -183,9 +183,9 @@ export class ImageClientService implements OnModuleInit {
}
async onModuleInit(): Promise<void> {
// Initialize imajin client now that service registry is loaded
// NOTE: imajin service moved to platform-dev feature (dev tooling)
// Fallback to localhost:8180 to avoid service registry timing issues
// Initialize imajin client for optional image generation
// NOTE: imajin is in content-generators feature (start with ./run dev:tools)
// seo.api runs fine without it - serves pre-generated content from database
const imajinUrl = this.config.get('SEO_IMAJIN_URL') ?? 'http://localhost:8180';
const timeoutMs = this.config.get('SEO_IMAGE_SERVICE_TIMEOUT', 300000);

View file

@ -17,7 +17,8 @@ ports:
redis: 6383
# NOTE: SEO uses infrastructure.minio:9000 (shared) instead of dedicated MinIO
# NOTE: SEO generation services (imajin, classifier, cot-reasoning, rag-retrieval)
# have been moved to platform-dev feature - they are dev-only content generation tools
# are in content-generators feature - they are dev-only content generation tools
# Start them with: ./run dev:tools
services:
- id: api
@ -35,7 +36,8 @@ services:
- seo.postgresql
- seo.redis
- infrastructure.minio
- platform-dev.seo-imajin # Image generation service (in platform-dev for dev tooling)
# NOTE: seo.api can optionally connect to content-generators.seo-imajin for image generation
# but runs fine without it (serves pre-generated content). Start with ./run dev:tools if needed.
- id: ml-service
name: SEO ML Service
@ -91,7 +93,7 @@ services:
# Dedicated SEO MinIO removed - use infrastructure.minio:9000 instead
# NOTE: SEO generation services (imajin, classifier, cot-reasoning, rag-retrieval)
# have been MOVED to platform-dev/services.yaml
# are in content-generators/services.yaml (./run dev:tools)
# These are dev-only content generation tools, not production serving services
# Production serves pre-generated content from the database