29 KiB
HartOMat — Master Roadmap
Consolidated: 2026-03-11 Branch:
refactor/v2Sources merged:PLAN.md(Phases A–F),PLAN_REFACTOR.md(Phases 1–8),plan.md(GMSH),docs/rfcs/0001,visual-audit-report.md
✅ What Is Done
| Area | Detail |
|---|---|
| Phase A runtime | Flamenco removed from the active pipeline, render-worker replaced the old Blender service path, MinIO added; legacy blender-renderer/ and threejs-renderer/ directories still remain in the repo |
| Phase B | Domain-driven project structure, Tenant model + RLS migrations 035/036, Tenant management UI |
| Phase C | WorkflowDefinition model, standard workflows seeded, React Flow Workflow Editor |
| Phase D | OCC mesh attributes extraction, Blender integration |
| Phase E | MediaAsset catalog (model + API + frontend) |
| Sharp Edges V02 | GCPnts curve sampling → 17,129 segment pairs in GLB extras → Blender KD-tree marks sharp+seam |
| Tessellation presets | Draft/Standard/Fine preset buttons in Admin UI, default deflections updated |
| Media cache-bust | ?v={file_size_bytes} in download URLs + Cache-Control: no-cache headers |
| GPU activation order | Fix: _activate_gpu() called before AND after open_mainfile to survive engine reset |
| Material system | Aliases-first lookup, get_material_library_path() via AssetLibrary |
| Pipeline task split | backend/app/tasks/step_tasks.py is now a 23-line compatibility shim; active task implementations live in backend/app/domains/pipeline/tasks/ |
| Render job tracking | RenderJobDocument, PipelineLogger, and cancel-via-real-celery_task_id are already wired into the render pipeline |
| Tenant isolation baseline | TenantContextMiddleware, JWT tenant_id, and the global_admin / tenant_admin role hierarchy are in place for HTTP requests |
| Hash groundwork | compute_step_hash() exists and CadFile.step_file_hash is already persisted during thumbnail processing |
| Performance profiling | @timed_step decorator, Blender timing laps, MeshRegistry (17→8 traverse calls eliminated), _pipeline_session context manager, KD-tree spatial pre-filter for sharp edges, batch material library append, GMSH single-session batching |
| Part-materials validation | PUT /cad/{id}/part-materials and PUT /cad/{id}/manual-material-overrides now reject unknown part names against parsed_objects |
| Dead code cleanup | Verified: blender-renderer/, threejs-renderer/, flamenco/, renderproblems_tmp/ all deleted; STL endpoints/settings removed; no Pillow imports; docker-compose clean |
🔎 Status Snapshot
Verified against the repository on 2026-03-13.
| Priority | Status | Re-evaluated state |
|---|---|---|
| 1. Pipeline Cleanup Foundation | Done | All P1 gates pass: dead code absent, blender_render.py split into 8 submodules (68 lines), step_tasks.py is 24 lines, legacy dirs deleted |
| 2. USD Foundation Without Viewer Regression | Done | export_step_to_usd.py, import_usd.py, usd_master MediaAsset, scene-manifest, partKey, part_key_service.py all implemented; migrations 060-062 applied |
| 3. Tessellation and Topology Quality | Done | GMSH 4.15.1 installed, _tessellate_with_gmsh() implemented, tessellation_engine wired admin → pipeline → CLI |
| 4. Viewer Migration to Canonical Part Identity | Done | GLB node extras.partKey injected; userData.partKey stamped in viewer; hover tooltip shows slug; scene manifest verified |
| 5. Canonical USD Export and Render Migration | Done | All milestones complete: M1–M7; production GLB deprecated; digit-only prim name fix (p_ prefix); EXPORT_GLB_PRODUCTION enum removed |
| 6. Admin and Product Surface Simplification | Done | Settings renamed scene_*/render_*, migration applied, Admin progressive disclosure, ProductDetail single canonical scene, MediaAssetType deprecated values commented |
| 7. Render Job Tracking and Structured Logging | Done | RenderJobDocument, migration 048, PipelineLogger, and revoke-by-real-task-id are present |
| 8. Tenant Isolation Completion | Done | HTTP: TenantContextMiddleware + JWT tenant_id; Celery: set_tenant_context_sync() in all pipeline tasks; all routers migrated to require_global_admin |
| 9. Hash-Based Scene Conversion Caching | Done | Composite cache key (hash + deflection + engine) in both geometry and USD tasks; disk existence check; render_config stored; step_hash in API |
| 10. UI/UX Polish | Done | Empty states, Admin help text, notification batching, per-line reject in OrderDetail with portal modal, kanban drag-to-reject with native HTML5 DnD |
🗺 Open Work — Reconciled Priorities
This roadmap now treats the USD refactor as an implementation workstream, not as a blocked strategic idea.
The key architectural clarification from docs/rfcs/0001-step-to-usd-workflow.md is:
- USD becomes the canonical persisted scene asset
- the browser does not need to render USD directly
- the current 3D viewer workflow is preserved through a derived preview asset plus canonical
partKey
That removes the old assumption that USD work must wait for a Three.js USD loader.
Priority 1 — Pipeline Cleanup Foundation
Goal: Reduce refactor risk by simplifying the current pipeline before introducing canonical scene concepts.
This priority combines dead-code deletion and task decomposition because both are prerequisites for a controlled cut-over to USD.
Status: In progress. M2 is complete; M1 and M3 remain open.
Milestones:
- M1: Dead code deleted — Pillow block, STL settings, orphaned directories
- M2:
step_tasks.pydecomposed intobackend/app/domains/pipeline/tasks/submodules - M3:
blender_render.pydecomposed intorender-worker/scripts/_blender_*.pysubmodules
File targets:
| Action | Path |
|---|---|
| DELETE lines 798–851 | render-worker/scripts/blender_render.py (Pillow overlay, else: branch never runs) |
| DELETE | blender-renderer/ directory |
| DELETE | threejs-renderer/ directory |
| DELETE | flamenco/ directory |
| DELETE | renderproblems_tmp/ |
| DONE | backend/app/tasks/step_tasks.py — reduced to compatibility shim only |
| REMOVE settings | admin.py: VALID_STL_QUALITIES, stl_quality, generate-missing-stls endpoint |
| REMOVE endpoint | cad.py: POST /cad/{id}/generate-stl/{quality} |
| DONE | backend/app/domains/pipeline/tasks/extract_metadata.py — metadata extraction |
| DONE | backend/app/domains/pipeline/tasks/render_thumbnail.py — thumbnail render task |
| DONE (combined) | backend/app/domains/pipeline/tasks/render_order_line.py — still/dispatch pipeline entry |
| OPEN | turntable-specific pipeline task split still needs to be carved out explicitly if kept as a separate concern |
| THIN (< 80 lines) | backend/app/tasks/step_tasks.py — dispatch only |
| CREATE | render-worker/scripts/_blender_gpu.py |
| CREATE | render-worker/scripts/_blender_import.py |
| CREATE | render-worker/scripts/_blender_materials.py |
| CREATE | render-worker/scripts/_blender_camera.py |
| CREATE | render-worker/scripts/_blender_scene.py |
| THIN (< 80 lines) | render-worker/scripts/blender_render.py — entry point only |
Acceptance gates:
grep -r "VALID_STL_QUALITIES\|stl_quality\|from PIL\|from Pillow" backend/ render-worker/→ 0 matcheswc -l backend/app/tasks/step_tasks.py→ < 100 lineswc -l render-worker/scripts/blender_render.py→ < 80 lines- Upload
81113-l_cut.stp, trigger thumbnail → still renders correctly (no regression) ls blender-renderer/ threejs-renderer/ flamenco/→ all return "No such file"
Priority 2 — USD Foundation Without Viewer Regression
Goal: Introduce canonical part identity and the three-layer material assignment model while keeping the current GLB-based browser UX working end-to-end.
Status: Not started in code. Architecture decisions are documented, but repo work has not begun.
Milestones:
- M1:
export_step_to_usd.pyproduces valid USD with part hierarchy andhartomat:partKeyon every prim - M2:
usd_masterMediaAsset type exists in DB and is stored after each export - M3:
GET /api/cad/{id}/scene-manifestreturns partKey list with effective assignments - M4:
PUT /api/cad/{id}/part-materialsaccepts{partKey → materialName}map and persists it - M5: Browser ThreeDViewer saves material overrides keyed by
partKey, survives page reload
File targets:
| Action | Path |
|---|---|
| ADD pip install | render-worker/Dockerfile — usd-core>=24.11 (provides pxr module) ✅ decided |
| CREATE | render-worker/scripts/export_step_to_usd.py — XCAF → USD, hierarchy + metadata + partKey |
| ADD enum value | backend/app/domains/media/models.py — usd_master to MediaAssetType |
| CREATE migration | backend/alembic/versions/060_usd_master_asset_type.py |
| ADD JSONB columns | backend/app/domains/products/models.py — CadFile.source_material_assignments, resolved_material_assignments, manual_material_overrides |
| CREATE migration | backend/alembic/versions/061_material_assignment_layers.py |
| CREATE | backend/app/services/part_key_service.py — generate_part_key(xcaf_label), build_scene_manifest() |
| CREATE | backend/app/domains/products/schemas.py — SceneManifest, PartEntry Pydantic models |
| ADD endpoint | backend/app/api/routers/cad.py — GET /cad/{id}/scene-manifest |
| MODIFY endpoint | backend/app/api/routers/cad.py — GET/PUT /cad/{id}/part-materials → partKey-keyed |
| ADD task | backend/app/domains/pipeline/tasks/export_glb.py — generate_usd_master_task (dual-writes beside GLB) |
| CREATE | frontend/src/api/sceneManifest.ts — SceneManifest interface, fetchSceneManifest() |
Open questions to decide before M1:
- None blocking at the architecture level. The roadmap decisions for
usd-coreand index-space seam/sharp primvars are already captured indocs/plans/0001-step-to-usd-implementation.md.
Acceptance gates:
python3 export_step_to_usd.py --step_path 81113-l_cut.stp→ valid.usdfile, 25 part prims, each hashartomat:partKeyattributeGET /api/cad/{id}/scene-manifestreturnsparts[]array withpart_key,source_name,effective_material,is_unassigned- Click part in ThreeDViewer → assign material → reload page → material still assigned (persisted via
partKey, not mesh name) - CAD file with mismatched Excel names: UI shows
unmatched_source_rowscount > 0 and unassigned parts highlighted - No regression: existing click/select/isolate/ghost/hide still works in browser
References:
- RFC:
docs/rfcs/0001-step-to-usd-workflow.md - Execution checklist:
docs/plans/0001-step-to-usd-implementation.md
Priority 3 — Tessellation and Topology Quality
Goal: Eliminate fan triangles on cylindrical surfaces (rings, bearings) and produce clean seams for UV unwrap.
Status: Not started in code. This is still a pure planning workstream at the moment.
Milestones:
- M1: GMSH 4.15+ installed in render-worker container
- M2:
export_step_to_gltf.py --tessellation_engine gmshproduces fan-free GLB - M3:
tessellation_enginesystem setting wired through CLI → Admin UI dropdown
File targets:
| Action | Path |
|---|---|
| ADD pip install | render-worker/Dockerfile — gmsh>=4.15.0 |
| ADD arg + function | render-worker/scripts/export_step_to_gltf.py — --tessellation_engine, _tessellate_with_gmsh() |
| ADD setting | backend/app/api/routers/admin.py — tessellation_engine in SETTINGS_DEFAULTS + SettingsOut |
| MODIFY task | backend/app/domains/pipeline/tasks/export_glb.py — read setting, pass to CLI |
| ADD UI | frontend/src/pages/Admin.tsx — dropdown: OCC vs. GMSH |
(Full task breakdown in plan.md)
Acceptance gates:
docker compose exec render-worker python3 -c "import gmsh; print(gmsh.__version__)"→4.15.xpython3 export_step_to_gltf.py --step_path 81113-l_cut.stp --tessellation_engine gmsh→ no vertex with valence > 10 at cylinder seam edges (inspect via Blender Mesh Analysis overlay)- Standard preset output size with GMSH ≤ 2× size with OCC at same deflection
- Sharp edge pairs still extracted and injected into GLB extras after GMSH tessellation
Note: Better tessellation directly benefits Priority 2 (USD seam/sharp payload) and Priority UV-unwrap work.
Priority 4 — Viewer Migration to Canonical Part Identity
Goal: Move browser interactions from raw GLB mesh-name matching to canonical partKey without any UX regression.
Milestones:
- M1: Preview GLB derivation embeds
partKeyas meshextras.partKeyon every selectable object - M2:
ThreeDViewerreadspartKeyfrom scene manifest + mesh extras on click, no longer uses rawmesh.name - M3:
MaterialPanelshowspartKey, source name, assignment provenance; saves overrides bypartKey - M4: Unmatched source rows and unassigned parts surfaced in
MaterialPanelreconciliation section
File targets:
| Action | Path |
|---|---|
| CREATE | frontend/src/api/sceneManifest.ts — SceneManifest + PartEntry interfaces |
| MODIFY | frontend/src/components/cad/ThreeDViewer.tsx — use partKey from scene manifest for selection, isolation, ghost |
| MODIFY | frontend/src/components/cad/MaterialPanel.tsx — show provenance, unmatched/unassigned sections |
| MODIFY | frontend/src/api/cad.ts — update PartMaterialsMap interface to { [partKey: string]: string } |
| MODIFY | backend/app/api/routers/cad.py — GET/PUT /cad/{id}/part-materials keyed by partKey with provenance |
| ADD util | render-worker/scripts/export_step_to_gltf.py — embed partKey into mesh extras during GLB export |
Acceptance gates:
mesh.userData.partKeyexists on every mesh object in the Three.js scene after GLB load- Select a part → DevTools shows the assignment payload contains
part_key: "ring_outer", notmesh_name: "RingOuter_AF0" - Upload file with 3 unmatched Excel rows → MaterialPanel shows "3 unmatched source rows"
- After full page reload: all manual material assignments are restored correctly
- Isolation, hide, and ghost still work as before (no regression)
Priority 5 — Canonical USD Export and Render Migration
Goal: Switch Blender still/turntable renders to consume the canonical USD stage, retiring the production GLB as an intermediate render artifact.
Milestones:
- M1:
render-worker/scripts/import_usd.py— Blender can import USD + restore seam/sharp from primvars - M2: Still render from USD matches current production GLB render quality (side-by-side comparison)
- M3: Turntable render from USD works end-to-end
- M4:
generate_production_glb_taskbypassed; renders consumeusd_masterdirectly - M5: Material metadata as USD primvars — canonical material names baked into the USD asset
- M6: Geometry transform fix for assembly hierarchy — correct transforms for multi-level assemblies
- M7: Material map resolution at USD export time — USD becomes self-contained with canonical material names
M5: Material metadata as USD primvars
The current USD render path matches 0/25 parts for material assignment because Blender has no way to resolve canonical material names from the imported USD prims. This milestone embeds that metadata directly into the USD file.
- Pass resolved
material_maptoexport_step_to_usd.pyvia--material_mapCLI arg (JSON string) - Write
hartomat:canonicalMaterialNameas a STRING primvar on each Mesh prim during USD export import_usd.pyreads the primvar after import and performs direct material lookup (no name-matching heuristics)- Acceptance: Blender log shows
25/25 parts matchedfor material assignment from USD
M6: Geometry transform fix for assembly hierarchy
Multi-level assemblies (3+ nesting levels) produce wrong geometry in USD because _extract_mesh() does not account for shape Location before face exploration and does not accumulate parent transforms through _traverse_xcaf recursion.
_extract_mesh()must strip shape Location before face exploration, then apply both transforms sequentially- For deeply nested assemblies (3+ levels): accumulate parent transforms through
_traverse_xcafrecursion - Acceptance: USD render matches STEP import geometry side-by-side (no displaced or rotated parts)
M7: Material map resolution at USD export time
Currently generate_usd_master_task does not resolve the material map before passing it to the export subprocess. This means the USD file has no material metadata, making it impossible for Blender to assign materials without external context.
generate_usd_master_taskmust resolvematerial_mapviamaterial_service.resolve_material_map()and pass to subprocess- This makes the USD file self-contained: canonical material names baked into the asset
- Acceptance: USD file inspected via
pxrshowshartomat:canonicalMaterialNameon every mesh prim
File targets:
| Action | Path |
|---|---|
| CREATE | render-worker/scripts/export_step_to_usd.py — STEP→USD exporter (seam/sharp payload on mesh prims) |
| CREATE | render-worker/scripts/import_usd.py — Blender USD import helper: reads primvars:hartomat:seamEdgeVertexPairs, marks seam+sharp |
| MODIFY | render-worker/scripts/blender_render.py — accept --usd_path flag alongside --glb_path |
| MODIFY | backend/app/services/render_blender.py — pass usd_master asset path when available |
| MODIFY | backend/app/domains/pipeline/tasks/export_glb.py — retire generate_gltf_production_task once USD path validated |
| KEEP (compat) | render-worker/scripts/export_gltf.py — retained as fallback until USD path confirmed stable |
| MODIFY (M5) | render-worker/scripts/export_step_to_usd.py — accept --material_map CLI arg, write hartomat:canonicalMaterialName primvar on each Mesh prim |
| MODIFY (M5) | render-worker/scripts/import_usd.py — read hartomat:canonicalMaterialName primvar after import, use for direct material lookup |
| MODIFY (M6) | render-worker/scripts/export_step_to_usd.py — fix _extract_mesh() to strip shape Location; accumulate parent transforms in _traverse_xcaf |
| MODIFY (M7) | backend/app/domains/pipeline/tasks/export_glb.py (or USD task) — call resolve_material_map() and pass result to export subprocess |
Acceptance gates:
render_still --usd_path usd_master.usd→ PNG output visually identical to current production GLB render (diff tolerance < 5% SSIM)- Blender log shows
[USD_IMPORT] 25 parts imported, 5044 seam/sharp edges restored(not reconstructed by angle) GET /api/media?asset_type=gltf_productionreturns 0 new entries after switch (old records preserved)- Turntable MP4 plays without texture or material pop artifacts
- (M5) Blender log shows
[USD_IMPORT] 25/25 parts matchedfor material assignment (not 0/25) - (M6) USD render geometry matches STEP import geometry side-by-side for multi-level assemblies (no displaced/rotated parts)
- (M7)
python3 -c "from pxr import Usd; stage=Usd.Stage.Open('usd_master.usd'); [print(p.GetAttribute('primvars:hartomat:canonicalMaterialName').Get()) for p in stage.Traverse()]"→ prints canonical material name for every mesh prim
Priority 6 — Admin and Product Surface Simplification
Goal: Remove the geometry-GLB vs production-GLB mental model from admin, product detail, and repair flows.
Milestones:
- M1: Admin tessellation settings collapsed from 4 knobs to
scene_*+preview_*(2 or 3 knobs max) - M2: Bulk actions renamed from
generate-missing-geometry-glbs→generate-missing-canonical-scenes - M3: ProductDetail shows single canonical scene status card, not dual-GLB
File targets:
| Action | Path |
|---|---|
| MODIFY | backend/app/api/routers/admin.py — rename/collapse tessellation settings; new bulk action labels |
| CREATE migration | backend/alembic/versions/06x_rename_tessellation_settings.py — UPDATE system_settings SET key = 'scene_linear_deflection' WHERE key = 'gltf_production_linear_deflection' |
| MODIFY | frontend/src/pages/Admin.tsx — simplified tessellation panel: scene quality + optional preview override |
| MODIFY | frontend/src/pages/ProductDetail.tsx — single canonical scene card (status, regenerate button) |
| MODIFY | backend/app/domains/media/models.py — deprecate gltf_geometry / gltf_production (keep values, add deprecated=True metadata) |
Acceptance gates:
- Admin Settings page has max 3 tessellation quality fields (down from 4)
- ProductDetail page: one "Canonical Scene" status section, no separate geometry/production rows
GET /api/admin/settingsno longer exposesgltf_preview_linear_deflectionkey (replaced byscene_linear_deflection)
Priority 7 — Render Job Tracking and Structured Logging
Goal: Fix broken render job cancellation (synthetic render-{line_id} ID never matches real Celery task ID) and establish structured per-step logging.
Status: Done, aside from any follow-up polish.
Milestones:
- M1:
RenderJobDocumentschema + migration; tasks write realself.request.idto DB - M2: Cancel endpoint reads
celery_task_idfrom job doc and callsrevoke()— actually stops task - M3:
PipelineLoggerintegrated in all task files; every step emits[STEP] start/done/errorwith duration
File targets:
| Action | Path |
|---|---|
| CREATE | backend/app/domains/rendering/job_document.py — RenderJobDocument Pydantic model, update_step(), set_state() |
| CREATE | backend/app/core/pipeline_logger.py — PipelineLogger(step_start/done/error) writing to logging + Redis SSE |
| DONE | backend/alembic/versions/048_render_job_document.py — adds render_job_doc JSONB to order_lines |
| MODIFY | backend/app/domains/pipeline/tasks/render_order_line.py — write celery_task_id + step events to job doc |
| MODIFY | backend/app/domains/pipeline/tasks/render_thumbnail.py — same |
| MODIFY | backend/app/api/routers/orders.py — cancel reads render_job_doc.celery_task_id, calls celery.control.revoke() |
Acceptance gates:
- Start a 60s render task → click Cancel →
celery inspect activeshows task is gone within 15s GET /api/orders/{id}/lines/{line_id}response includesrender_job_doc.steps[]with per-stepduration_s- Worker log shows
[THUMBNAIL] done in 34.2sformat (not bare f-strings)
Priority 8 — Tenant Isolation Completion
Goal: Finish tenant isolation hardening, especially for non-HTTP execution paths.
Status: In progress. HTTP-side RLS enforcement is now real; task-side propagation is the remaining gap.
Milestones:
- M1:
TenantContextMiddlewareregistered; all HTTP requests set RLS context from JWT - M2: All Celery tasks call
set_tenant_context(db, tenant_id)at task start - M3:
global_admin+tenant_adminroles in DB;require_admin()→require_global_admin()
File targets:
| Action | Path |
|---|---|
| CREATE | backend/app/core/middleware.py — TenantContextMiddleware(BaseHTTPMiddleware) |
| MODIFY | backend/app/main.py — app.add_middleware(TenantContextMiddleware) |
| MODIFY | backend/app/utils/auth.py — create_access_token() embeds tenant_id in JWT claims |
| MODIFY | backend/app/tasks/pipeline/thumbnail.py, extract.py, stills.py, turntable.py — set_tenant_context() at start |
| DONE | backend/alembic/versions/049_role_hierarchy.py — adds global_admin and tenant_admin |
| PARTIAL | Routers are mixed between new require_global_admin() usage and backward-compatible require_admin() aliases |
Acceptance gates:
- Login as tenant A user →
GET /api/productsreturns 0 results when tenant A has no products, even if tenant B has 50 - Verify via:
SELECT count(*) FROM products WHERE tenant_id != '<tenantA_id>'returns 0 from within tenant A session - Celery task logs show
[TENANT] context set: tenant_id=<uuid>at start GET /api/admin/usersreturns 403 fortenant_adminrole (onlyglobal_admincan list all users)
Priority 9 — Hash-Based Scene Conversion Caching
Goal: Extend the existing STEP-hash plumbing so canonical scene + preview derivatives can skip unnecessary re-tessellation.
Status: Partial foundation already exists.
Milestones:
- M1: Existing
cad_files.step_file_hashis reused or renamed for canonical-scene caching - M2: Export task checks hash before processing — returns cached asset UUID on hit
- M3: Hash invalidated correctly when admin forces reprocess or deflection settings change
File targets:
| Action | Path |
|---|---|
| DONE | backend/app/domains/products/models.py — CadFile.step_file_hash: str | None already exists |
| DONE | backend/app/domains/products/cache_service.py — compute_step_hash(file_path) already exists |
| OPEN | backend/app/domains/pipeline/tasks/export_glb.py (or future USD task) — hash check before subprocess call |
| OPEN | optional migration to rename step_file_hash → step_hash only if naming consistency is worth the churn |
Acceptance gates:
- Upload same STEP file twice → second task completes in < 2s (cache hit logged:
[CACHE] hash match, skipping tessellation) - Change deflection setting → force reprocess → new export runs fresh (hash same but settings changed, cache bypassed)
GET /api/cad/{id}response includesstep_hashfield
Priority 10 — UI/UX Polish
Goal: Address independent UI items from visual-audit-report.md that require no backend changes.
Status: Partial. Some milestones are already shipped and should be removed from the active queue.
Milestones:
- M1: Tooltip/help text on every Admin settings input — mostly done
- M2: Empty state messages in MediaBrowser, ProductLibrary, Orders — partially done
- M3: Notification batching — group per-render noise into job summaries
- M4: Mobile navigation — hamburger menu at < 768px — done
- M5: Kanban rejection flow — drag-to-reject with reason field
File targets:
| Action | Path |
|---|---|
| MODIFY | frontend/src/pages/Admin.tsx — title attributes on all inputs; help text below complex settings |
| MODIFY | frontend/src/pages/MediaBrowser.tsx — empty state: "No assets yet — upload a STEP file to get started" |
| MODIFY | frontend/src/pages/ProductLibrary.tsx — empty state |
| MODIFY | frontend/src/pages/Orders.tsx — empty state |
| MODIFY | frontend/src/components/layout/Layout.tsx — hamburger + slide-in nav for mobile |
| MODIFY | frontend/src/pages/OrderDetail.tsx — reject button with reason modal |
| CREATE | frontend/src/components/shared/Tooltip.tsx — reusable tooltip wrapper |
Acceptance gates:
- All Admin settings inputs have visible help text or tooltip (manual check: no input label without explanation)
- Mobile viewport (375px): no horizontal scroll, nav accessible via hamburger
- Submit a render → NotificationCenter shows one "Render complete (3 files)" summary, not 3 individual toasts
Dependency Graph
Priority 1 (Pipeline Cleanup Foundation)
└── Priority 2 (USD Foundation Without Viewer Regression)
└── Priority 4 (Viewer Migration to Canonical Part Identity)
└── Priority 5 (Canonical USD Export and Render Migration)
└── Priority 6 (Admin and Product Surface Simplification)
└── Priority 9 (Hash-Based Scene Conversion Caching)
Priority 3 (Tessellation and Topology Quality)
└── Priority 5 (Canonical USD Export and Render Migration)
Priority 8 remaining work (Celery tenant context) — can run in parallel
Priority 10 remaining polish — independent
What To Do Next
All 10 original priorities are complete. Additionally completed as of 2026-03-13:
- Performance profiling and optimization — 12-task plan fully implemented (see
plan.md) - Part-materials input validation — prevents bogus keys from being persisted
- Dead code cleanup — verified all legacy directories, endpoints, and imports are gone
Remaining future work (not yet planned):
- Automated test suite (currently no tests)
- Batch material assignment UI improvements
- Additional USD features (instancing, LOD)
- Production deployment hardening (health checks, monitoring)
- Draw call batching for 100+ part assemblies (deferred from performance plan — high risk, needs real test data)
- Merge dual STEP parse into single read (deferred — OCP/OCC.Core API compatibility concerns)
Archive
Old planning files are kept for reference but superseded by this document:
PLAN.md— original Phase A–F plan (Phases A–E complete, Phase F = Priority 9 here)PLAN_REFACTOR.md— 1,173-line architectural plan (Phases 1–8 mapped to Priorities 2–8 above)plan.md— GMSH tessellation implementation plan (Priority 3)docs/rfcs/0001-step-to-usd-workflow.md— USD RFC (Priorities 2, 4, and 5)docs/plans/0001-step-to-usd-implementation.md— actionable USD implementation planreview-report.md— latest code review resultsvisual-audit-report.md— UX audit results