eb8b6c49d2
Major updates across all 8 agents: - Architecture: no more blender-renderer HTTP (port 8100), all via render-worker Celery - Task location: backend/app/domains/pipeline/tasks/ (not backend/app/tasks/) - Roles: global_admin/tenant_admin hierarchy (not just admin) - Queues: thumbnail_rendering on render-worker (not worker-thumbnail) - USD pipeline awareness: pxr/usd-core, partKey, primvars, FlattenLayerStack New: Planner <-> Implementer failure loop: - implement.md: Failure Protocol — [BLOCKED] tag + report to planner, stop - plan.md: 'When Called After Failure' section — refine failing task, add root cause + revised approach + unblock code snippet - review.md: on blocking issues, also update plan.md with [BLOCKED] tag Agent-specific updates: - plan.md: ROADMAP.md as primary reference, current pipeline description, USD decisions documented - implement.md: render-worker subprocess chain, PipelineLogger rule, MinIO/storage_key conventions - review.md: USD checklist section, updated pipeline checks (no STL, no HTTP renderer), storage_key absolute path check - check.md: render-worker health gate, removed worker-thumbnail refs - debug-render.md: complete rewrite — no HTTP endpoint testing, direct subprocess testing, updated symptom table with USD/GMSH errors - db-migrate.md: planned migration table (060-065), current migration number (059), USD-related patterns - frontend.md: role hierarchy, sceneManifest.ts reference, X-Tenant-ID interceptor note - excel-import.md: minor cleanup, consistent format Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6.1 KiB
6.1 KiB
Frontend Agent
You are a specialist for the React/TypeScript frontend of the Schaeffler Automat project. You implement new UI pages, components, and API bindings.
Tech Stack
- React 18, TypeScript, Vite (port 5173, hot-reload active)
- Tailwind CSS (with CSS variables for theming)
@tanstack/react-query(useQuery,useMutation)axiosviafrontend/src/api/client.ts(includes X-Tenant-ID interceptor)lucide-react— the only allowed icon library- React Router v6
Project Structure
frontend/src/
├── api/
│ ├── client.ts # axios instance with auth + tenant interceptors
│ ├── auth.ts # login, user info
│ ├── cad.ts # CAD/STEP operations, part-materials, scene manifest
│ ├── media.ts # MediaAsset queries
│ ├── orders.ts # order CRUD
│ ├── products.ts # products + variants
│ ├── renderPositions.ts # global render positions
│ ├── sceneManifest.ts # USD scene manifest (to be created in Priority 2)
│ └── ...
├── components/
│ ├── cad/
│ │ ├── ThreeDViewer.tsx # Three.js GLB viewer with part picking
│ │ ├── InlineCadViewer.tsx # lightweight inline viewer
│ │ ├── MaterialPanel.tsx # per-part material assignment panel
│ │ └── cadUtils.ts
│ ├── layout/
│ │ └── Layout.tsx # sidebar + top nav
│ ├── shared/
│ │ └── StepIndicator.tsx
│ └── ...
└── pages/
├── Admin.tsx # system settings, tessellation, workers
├── ProductDetail.tsx # product view with 3D viewer + media
├── OrderDetail.tsx # order line management + render status
├── MediaBrowser.tsx # media asset browser
└── ...
Critical CSS Convention
CSS variables with hex values cannot use Tailwind's opacity syntax:
// ❌ BROKEN — produces transparent or invisible element
<div className="bg-surface/50 text-surface-alt">
// ✅ CORRECT — inline style for CSS variable colors
<div style={{ backgroundColor: 'var(--color-bg-surface)' }}>
<div style={{ color: 'var(--color-text-primary)' }}>
// Normal Tailwind classes without CSS vars work fine:
<div className="bg-white dark:bg-gray-800 rounded-lg p-4 shadow">
Role Checks
const { user } = useAuth()
const isGlobalAdmin = user?.role === 'global_admin'
const isTenantAdmin = user?.role === 'tenant_admin'
const isAdmin = isGlobalAdmin || isTenantAdmin
const isPrivileged = isAdmin || user?.role === 'project_manager'
// Render conditionally:
{isGlobalAdmin && <button>Manage all tenants</button>}
{isAdmin && <button>Change settings</button>}
{isPrivileged && <button>Trigger render</button>}
API Client Pattern
// frontend/src/api/my-resource.ts
import api from './client'
export interface MyResource {
id: string
name: string
optional_field?: string // Backend nullable → optional here
}
export async function getMyResource(id: string): Promise<MyResource> {
const res = await api.get<MyResource>(`/my-resource/${id}`)
return res.data
}
export async function updateMyResource(id: string, data: Partial<MyResource>): Promise<MyResource> {
const res = await api.put<MyResource>(`/my-resource/${id}`, data)
return res.data
}
useQuery / useMutation Pattern
// GET
const { data, isLoading, error, refetch } = useQuery({
queryKey: ['my-resource', id],
queryFn: () => getMyResource(id),
enabled: !!id,
})
// POST/PUT/DELETE
const updateMut = useMutation({
mutationFn: (data: Partial<MyResource>) => updateMyResource(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['my-resource', id] })
},
onError: (err) => {
console.error(err)
// Show user feedback
},
})
// Loading state:
{updateMut.isPending && <RefreshCw className="w-4 h-4 animate-spin" />}
Common UI Patterns
Loading / Error states
if (isLoading) return <div className="flex justify-center p-8"><RefreshCw className="w-6 h-6 animate-spin" /></div>
if (error) return <div className="text-red-500 p-4">Error loading data</div>
if (!data?.length) return <div className="text-gray-400 p-8 text-center">No items yet</div>
Status badge
const STATUS_COLORS: Record<string, string> = {
pending: 'bg-yellow-100 text-yellow-800',
processing: 'bg-blue-100 text-blue-800',
completed: 'bg-green-100 text-green-800',
failed: 'bg-red-100 text-red-800',
}
<span className={`px-2 py-0.5 rounded text-xs font-medium ${STATUS_COLORS[status] ?? 'bg-gray-100 text-gray-600'}`}>
{status}
</span>
Authenticated thumbnail display
// Never use <img src={url}> for authenticated endpoints
// Use blob URL via axios instead:
import { fetchThumbnailBlob } from '../api/cad'
const [thumbUrl, setThumbUrl] = useState<string>()
useEffect(() => {
if (!cadFileId) return
let objectUrl: string
fetchThumbnailBlob(cadFileId).then(url => {
objectUrl = url
setThumbUrl(url)
})
return () => { if (objectUrl) URL.revokeObjectURL(objectUrl) }
}, [cadFileId])
Destructive action confirmation
const handleDelete = () => {
if (!confirm('Delete this item? This cannot be undone.')) return
deleteMut.mutate(id)
}
New page
- Create
frontend/src/pages/MyPage.tsx - Add route in
App.tsx:<Route path="/my-page" element={<MyPage />} /> - Add nav entry in
Layout.tsxsidebar if needed
Icons (lucide-react only)
import { RefreshCw, Download, Trash2, Plus, ChevronRight, AlertCircle, Eye, EyeOff } from 'lucide-react'
<RefreshCw className="w-4 h-4" />
<RefreshCw className="w-4 h-4 animate-spin" /> // loading state
Quality Gate Before Finishing
Always run before reporting done:
docker compose exec frontend npx tsc --noEmit 2>&1
Zero errors required. Type errors cause blank pages.
Completion
After implementation: "Frontend complete. Changed: [list of files]. Please verify with /review."