feat(refactor/phase2): pipeline modularity + dead code removal

Phase 2.3 — Fix render cancellation (real Celery task ID):
  - orders.py cancel endpoints: read celery_task_id from render_job_doc
    instead of synthetic "render-{line_id}" which was a no-op
  - render_order_line_still_task: creates RenderJobDocument at task start,
    stores self.request.id as celery_task_id, writes step-level state
    (RESOLVE_STEP_PATH → BLENDER_STILL) back to order_lines.render_job_doc

Phase 3.1 — Remove Pillow overlay dead code:
  - blender_render.py: deleted 55-line Pillow post-processing block
    (lines 798-851, green bar + model name label)
  - transparent_bg=True is always passed; the else branch was unreachable
  - Removed mention from script docstring

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 19:27:33 +01:00
parent ea31ed657c
commit 966c3aed57
4 changed files with 200 additions and 64 deletions
+140
View File
@@ -0,0 +1,140 @@
You are an expert UX auditor, QA engineer, and frontend performance specialist.
Your task is to perform a thorough audit of the web service at http://localhost:5173.
---
## YOUR AUDIT MISSION
Systematically evaluate the service across 6 dimensions and produce a structured
report with prioritized, actionable recommendations.
---
## AUDIT DIMENSIONS
### 1. 🎨 Visual Consistency & Theme Audit
- Navigate through ALL available themes (light, dark, high-contrast, custom, etc.)
- Switch between themes mid-session and check for visual glitches or FOUC (flash of unstyled content)
- Verify consistent use of: colors, typography, spacing, border-radius, icon style, shadow depth
- Check that ALL UI states (hover, focus, active, disabled, loading, error, empty) are themed correctly
- Look for hardcoded colors that break in certain themes
- Verify component library consistency — do buttons, cards, inputs, and modals share a visual language?
- Flag any element that looks "out of place" or inconsistent with the design system
### 2. 🧪 Functional QA & UI Integrity
- Test EVERY interactive element: buttons, dropdowns, toggles, sliders, modals, accordions, tabs
- Verify all tooltips: Do they appear? Are they cut off? Do they have correct content?
- Check for overlapping elements (z-index issues, overflow problems, popover collisions)
- Test all form inputs: validation messages, placeholder text, character limits, required fields
- Check loading states: spinners, skeletons — do they appear and resolve correctly?
- Test error states: 404s, failed API calls, empty states — are they handled gracefully?
- Verify that keyboard navigation works (Tab order, Enter/Space activation, Escape to close)
- Check focus trapping in modals and drawers
### 3. 📱 Mobile Friendliness Assessment
- Simulate viewport widths: 320px, 375px, 390px, 768px, 1024px
- Check for horizontal scroll at any breakpoint (major red flag)
- Verify touch target sizes (minimum 44x44px for interactive elements)
- Assess readability: font sizes, line lengths, contrast on small screens
- Check that dropdowns, modals, and overlays are usable on mobile
- Verify that navigation collapses properly (hamburger menus, bottom bars, etc.)
- Test forms on mobile: Are inputs large enough? Does the correct keyboard type appear?
- Check images: Do they scale and crop correctly? Are they too large to load on mobile?
### 4. ⚡ User Flow Efficiency Analysis
For the following key user journeys, count the number of steps and assess whether
they can be streamlined:
- Onboarding / account creation
- Reaching the core value action (the main thing users come to do)
- Settings or preferences update
- Search and filtering to a specific result
- Error recovery (what happens when something goes wrong?)
For each flow:
- Count total clicks/taps to complete
- Identify redundant confirmation steps
- Flag missing shortcuts or bulk actions
- Suggest progressive disclosure opportunities (hide complexity until needed)
- Note any missing defaults that force unnecessary user decisions
### 5. 💡 UX Improvement Suggestions
Based on all findings above, generate prioritized suggestions:
- **Quick wins** (low effort, high impact): e.g., better default values,
clearer button labels, improved empty states
- **Medium effort improvements**: e.g., combining steps, better feedback loops,
smarter form validation
- **Strategic improvements**: e.g., onboarding redesign, navigation restructure,
design system consolidation
For each suggestion include:
- What the current behavior is
- What the improved behavior would be
- Why this matters (user impact)
- Estimated effort: Low / Medium / High
### 6. 🚀 Performance & Speed Improvement Opportunities
- Identify any UI-visible lag: slow renders, janky animations, delayed responses
- Check for layout shift (elements jumping after load)
- Note any unoptimized images (large files, no lazy loading)
- Look for blocking interactions (buttons unresponsive during loading)
- Suggest optimistic UI patterns where applicable (update UI before server confirms)
- Identify where skeleton loaders or progressive rendering would reduce perceived lag
- Flag any unnecessary full-page reloads that could be AJAX/SPA transitions
- Check if heavy operations give the user feedback (progress bars, estimated time)
---
## OUTPUT FORMAT
Structure your final report as follows:
# Schaeffler Automat — UX & Quality Audit Report
**Date**: [date]
**Overall Score**: [X/10]
## Executive Summary
[3-5 sentence overview of the service's current state and top priorities]
## Critical Issues 🔴
[Issues that are broken, inaccessible, or severely harm the experience]
## Major Improvements 🟠
[Significant friction points or inconsistencies]
## Minor Refinements 🟡
[Polish items that would elevate the quality]
## Wins ✅
[What's working well — don't only focus on negatives]
## Prioritized Recommendation List
| Priority | Area | Issue | Suggestion | Effort |
|----------|------|--------|------------|--------|
| 1 | ... | ... | ... | Low |
## Theme & Visual Consistency Report
[Detailed findings from Dimension 1]
## Functional QA Report
[Detailed findings from Dimension 2]
## Mobile Report
[Detailed findings from Dimension 3]
## User Flow Efficiency Report
[Step counts and flow diagrams for key journeys]
## Performance Observations
[Detailed findings from Dimension 6]
---
## AUDIT PRINCIPLES
- Be specific: reference exact UI elements, page names, and interaction states
- Be constructive: every criticism should come with a concrete suggestion
- Prioritize ruthlessly: not everything is equally important
- Think like a first-time user AND a power user — they have different needs
- The goal is: less lag, better functionality, more clarity, easy-to-use options,
consistent UI, and perfectly working themes
Write the report to `visual-audit-report.md` in the project root.
+12 -8
View File
@@ -954,12 +954,14 @@ async def cancel_line_render(
cancelled_backend = line.render_backend_used or "celery"
errors: list[str] = []
# Revoke Celery task (best-effort)
# Revoke Celery task (best-effort) using real task ID from job document
try:
from app.tasks.celery_app import celery_app
celery_app.control.revoke(
f"render-{line_id}", terminate=True, signal="SIGTERM"
)
real_task_id = None
if line.render_job_doc:
real_task_id = line.render_job_doc.get("celery_task_id")
task_id = real_task_id or f"render-{line_id}"
celery_app.control.revoke(task_id, terminate=True, signal="SIGTERM")
except Exception as exc:
errors.append(f"Celery revoke failed: {str(exc)[:200]}")
@@ -1025,11 +1027,13 @@ async def cancel_order_renders(
errors: list[str] = []
for line in lines:
# Revoke Celery task (best-effort)
# Revoke Celery task using real task ID from job document
try:
celery_app.control.revoke(
f"render-{line.id}", terminate=True, signal="SIGTERM"
)
real_task_id = None
if line.render_job_doc:
real_task_id = line.render_job_doc.get("celery_task_id")
task_id = real_task_id or f"render-{line.id}"
celery_app.control.revoke(task_id, terminate=True, signal="SIGTERM")
except Exception:
pass
+48
View File
@@ -395,14 +395,47 @@ def render_order_line_still_task(self, order_line_id: str, **params) -> dict:
Wraps render_still_task logic but accepts order_line_id instead of step_path.
On success, creates a MediaAsset record via publish_asset.
"""
import asyncio
from app.domains.rendering.job_document import RenderJobDocument, JobState
from app.core.process_steps import StepName
log_task_event(self.request.id, f"Starting render_order_line_still_task: order_line={order_line_id}", "info")
# Initialise job document and store real Celery task ID
job_doc = RenderJobDocument.new(order_line_id=order_line_id, celery_task_id=self.request.id)
job_doc.set_state(JobState.RUNNING)
def _save_job_doc():
async def _run():
from app.database import AsyncSessionLocal
from app.domains.orders.models import OrderLine
from sqlalchemy import update as _upd
async with AsyncSessionLocal() as db:
await db.execute(
_upd(OrderLine)
.where(OrderLine.id == order_line_id)
.values(render_job_doc=job_doc.to_dict())
)
await db.commit()
try:
asyncio.get_event_loop().run_until_complete(_run())
except Exception as _exc:
logger.debug("_save_job_doc failed: %s", _exc)
_save_job_doc()
job_doc.begin_step(StepName.RESOLVE_STEP_PATH)
step_path_str, cad_file_id = _resolve_step_path_for_order_line(order_line_id)
if not step_path_str:
job_doc.fail_step(StepName.RESOLVE_STEP_PATH, "product missing or has no linked CAD file")
job_doc.set_state(JobState.FAILED, error="Cannot resolve STEP path")
_save_job_doc()
log_task_event(self.request.id, f"Failed: cannot resolve STEP path for order_line {order_line_id}", "error")
raise RuntimeError(
f"Cannot resolve STEP path for order_line {order_line_id}: "
"product missing or has no linked CAD file"
)
job_doc.finish_step(StepName.RESOLVE_STEP_PATH, output={"step_path": step_path_str})
step = Path(step_path_str)
output_dir = step.parent / "renders"
@@ -410,12 +443,24 @@ def render_order_line_still_task(self, order_line_id: str, **params) -> dict:
output_path = output_dir / f"line_{order_line_id}.png"
try:
job_doc.begin_step(StepName.BLENDER_STILL)
from app.services.render_blender import render_still
result = render_still(
step_path=step,
output_path=output_path,
**params,
)
job_doc.finish_step(
StepName.BLENDER_STILL,
output={"output_path": str(output_path), "duration_s": result.get("total_duration_s")},
)
job_doc.set_state(JobState.COMPLETED, result={
"output_path": str(output_path),
"duration_s": result.get("total_duration_s"),
"engine_used": result.get("engine_used"),
})
_save_job_doc()
publish_asset.delay(
order_line_id,
"still",
@@ -438,6 +483,9 @@ def render_order_line_still_task(self, order_line_id: str, **params) -> dict:
_update_workflow_run_status(order_line_id, "completed")
return result
except Exception as exc:
job_doc.fail_step(StepName.BLENDER_STILL, str(exc))
job_doc.set_state(JobState.FAILED, error=str(exc))
_save_job_doc()
log_task_event(self.request.id, f"Failed: {exc}", "error")
logger.error("render_order_line_still_task failed for %s: %s", order_line_id, exc)
try:
-56
View File
@@ -14,7 +14,6 @@ Features:
- Isometric-style angle (elevation 28°, azimuth 40°).
- Dynamic clip planes.
- Standard (non-Filmic) colour management → no grey tint.
- Schaeffler green top bar + model name label via Pillow post-processing.
"""
import sys
import os
@@ -795,59 +794,4 @@ sys.stdout.flush()
bpy.ops.render.render(write_still=True)
print("[blender_render] render done.", flush=True)
# ── Pillow post-processing: green bar + model name label ─────────────────────
# Skip overlay for transparent renders to keep clean alpha channel
if transparent_bg:
print("[blender_render] Transparent mode — skipping Pillow overlay.")
else:
try:
from PIL import Image, ImageDraw, ImageFont
img = Image.open(output_path).convert("RGBA")
draw = ImageDraw.Draw(img)
W, H = img.size
# Schaeffler green top bar
bar_h = max(8, H // 32)
draw.rectangle([0, 0, W - 1, bar_h - 1], fill=(0, 137, 61, 255))
# Model name strip at bottom
model_name = os.path.splitext(os.path.basename(glb_path))[0]
label_h = max(20, H // 20)
img.alpha_composite(
Image.new("RGBA", (W, label_h), (30, 30, 30, 180)),
dest=(0, H - label_h),
)
font_size = max(10, label_h - 6)
font = None
for fp in [
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
"/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf",
"/usr/share/fonts/truetype/freefont/FreeSansBold.ttf",
]:
if os.path.exists(fp):
try:
font = ImageFont.truetype(fp, font_size)
break
except Exception:
pass
if font is None:
font = ImageFont.load_default()
tb = draw.textbbox((0, 0), model_name, font=font)
text_w = tb[2] - tb[0]
draw.text(
((W - text_w) // 2, H - label_h + (label_h - (tb[3] - tb[1])) // 2),
model_name, font=font, fill=(255, 255, 255, 255),
)
img.convert("RGB").save(output_path, format="PNG")
print(f"[blender_render] Pillow overlay applied.")
except ImportError:
print("[blender_render] Pillow not in Blender Python skipping overlay.")
except Exception as exc:
print(f"[blender_render] Pillow overlay failed (non-fatal): {exc}")
print("[blender_render] Done.")