Files
HartOMat/visual-audit-report.md
T
Hartmut 89c44b846f feat(phase5.1+6): fallback material cleanup + notification batch refactor
Phase 5.1 — MATERIAL_PALETTE removal:
- Remove MATERIAL_PALETTE + _material_to_color() from step_processor.py
- build_part_colors() now returns {part→material_name} for Blender resolver

Phase 6 — Notification Center Refactor:
- Migration 051: add channel (activity|notification|alert) to audit_log,
  add frequency (immediate|daily|never) to notification_configs
- Three notification channels: activity (per-render), notification (batch
  order summaries), alert (admin infrastructure)
- Per-render emit_notification_sync calls demoted to channel=activity
- New emit_batch_render_notification_sync(): single summary notification
  when all order lines reach terminal state ("47/50 succeeded, 3 failed")
- Beat task batch_render_notifications every 60s: safety-net for missed
  batch notifications after order completion
- GET /notifications: defaults to channel IN (notification, alert);
  accepts ?channel=activity for activity feed
- Unread count badge counts only notification+alert channels
- Notifications.tsx: three tabs (Notifications | Activity | Alerts)
- NotificationSettings.tsx: frequency dropdown per event type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 20:20:07 +01:00

26 KiB
Raw Blame History

Schaeffler Automat — UX & Quality Audit Report

Date: 2026-03-08 Audited by: Source-code analysis (frontend dev server not reachable at audit time) Overall Score: 7.5/10


Executive Summary

Schaeffler Automat is a feature-rich internal media-creation pipeline with a well-structured React/TypeScript frontend. The application covers a broad functional surface — order management, product library, render monitoring, billing, materials, workers, and a customisable analytics dashboard — and demonstrates strong UX thinking in several areas (kanban + list view duality, multi-step wizard flows, real-time activity feeds, rich filtering). The design system is coherent and token-driven with dark-mode and accent-colour theming. The most significant weaknesses are: the sidebar navigation grows unwieldy without visual hierarchy, several modal/dialog components opt out of the design system (hard-coded Tailwind white/gray classes instead of semantic tokens), the "Upload" primary workflow is hidden mid-list in the sidebar rather than foregrounded, and a handful of page-level experiences fall short of the richness shown elsewhere (Billing, WorkflowEditor, AssetLibraries are visibly incomplete). No automated tests exist for the UI, which is a process risk.


Critical Issues

C1 — ValidationDialog opts out of the design system entirely

File: frontend/src/pages/Upload.tsx (lines 710855)

The ValidationDialog and NewInvoiceModal (Billing.tsx lines 3976) use hard-coded bg-white, text-gray-900, border-gray-200, text-gray-700 etc. instead of semantic tokens (bg-surface, text-content, border-border-default). In dark-mode these dialogs will render as fully white panels against a dark background — an accessibility and branding failure. The same pattern appears in the Billing page action buttons (bg-blue-600).

C2 — Sticky bottom bars use a fixed left-60 offset that breaks on mobile

Files: NewProductOrder.tsx (lines 476, 615, 736), NewOrder.tsx (assumed similar)

The bottom action bars are positioned with fixed bottom-0 left-60 right-0. On mobile, the sidebar is w-60 but collapses behind an overlay — the bottom bar will overlap or clip because it always assumes a 240 px left sidebar is present. Combined with pt-12 main content on mobile (for the top header bar), this creates layout collisions.

C3 — confirm() used throughout for destructive confirmations

Files: Orders.tsx (line 164), ProductLibrary.tsx (line 167), Materials.tsx (line 404), Admin.tsx (multiple), Billing.tsx (line 209), WorkerActivity.tsx (line 288)

The native browser confirm() dialog is used for all destructive actions (delete order, delete product, purge queue, delete invoice). This is: (a) not styleable — it looks completely alien from the product design, (b) blocks the event loop, (c) not keyboard-accessible in a predictable way, and (d) inconsistent across browsers. A small reusable ConfirmModal would resolve all of these.

C4 — No <Toaster /> / toast provider verified in root; double LiveRenderLog import

Files: App.tsx, OrderDetail.tsx (imports LiveRenderLog from ../components/LiveRenderLog), WorkerActivity.tsx (imports from ../components/LiveRenderLog) File: frontend/src/components/tasks/LiveRenderLog.tsx also exists

There are two versions of LiveRenderLog.tsx (/components/LiveRenderLog.tsx and /components/tasks/LiveRenderLog.tsx). OrderDetail.tsx imports from ../components/LiveRenderLog (the root-level one). The tasks/ version appears to be a separate copy. This creates a maintenance hazard where fixes applied to one are not reflected in the other.


Major Improvements

M1 — Sidebar navigation lacks visual grouping / hierarchy

File: frontend/src/components/layout/Layout.tsx

The sidebar lists up to 14 navigation items in a flat list without any section dividers between user-facing pages (Dashboard, Orders, Products, Materials, Activity, Preferences, Upload) and admin-facing pages (Admin, Billing, Media Browser, Workers, Workflows, Asset Libraries, Notification Settings, Tenants). At maximum, an admin user sees all 14+ items with no visual separation. Recommended: add a small section label or divider line before the admin section.

M2 — "Upload" is buried in the nav when it is the primary data-entry point

File: frontend/src/components/layout/Layout.tsx (line 18)

The "Upload" link is item #7 of 7 in the main nav list, placed after Preferences. For the primary workflow path (upload Excel → review → create order → dispatch renders), Upload should either be promoted to a more prominent position or merged with the "New Order" CTA at the top of the sidebar. Currently the "New Order" button at sidebar top goes to /orders/new which is a choice page, while Excel import lives at /upload. The duplication is confusing.

M3 — Price estimates display bare decimal numbers without currency symbol

Files: NewProductOrder.tsx (lines 623, 742), OrderDetail.tsx

Price estimates in the wizard bottom bars show Estimated: 25.00 with no currency symbol. The formatCurrency function in Billing.tsx does this correctly with Intl.NumberFormat; the pattern is not shared. The wizard should use the same formatter: € 25,00.

M4 — Product Library hard cap of 200 products with no pagination

File: frontend/src/pages/ProductLibrary.tsx (line 131)

listProducts is called with limit: 200. Above 200 products there is no pagination, infinite scroll, or "load more" — items beyond the cap silently disappear. The same cap applies to the New Product Order wizard Step 1. This will become a real problem as the library grows.

M5 — Orders Kanban view: columns are fixed-width (272 px) and overflow horizontally

File: frontend/src/pages/Orders.tsx (lines 593628)

The Kanban board uses min-w-[272px] per column inside min-w-max — so with 5 columns (~1400 px) the view is wider than a 1280 px laptop screen and triggers horizontal scrolling of the entire content area. A more adaptive approach (auto-sizing columns, or capping at 3 visible columns with horizontal scroll contained to the board) would be better.

M6 — Billing page is disconnected from order data

File: frontend/src/pages/Billing.tsx

The "New Invoice" modal (lines 3476) creates invoices with an empty order_line_ids: [] array — there is no way to select or attach order lines to an invoice from the UI. The total_net field in the table is always null/ for freshly created invoices since no line items are added. The page appears to be a skeleton that is visible to admins and project managers but does not yet deliver its core value.

M7 — Workflow Editor page appears non-functional

File: frontend/src/pages/WorkflowEditor.tsx (not read in full but exposed in routing)

The Workflows route exists and is admin-gated but the page content was not visible in the source scan. Based on the API client (/api/workflows.ts) and the GitBranch icon, this feature appears to be scaffolded but not implemented, yet it is present in the sidebar nav for admins/PMs. An "Under construction" state would be more honest than a silent empty page.

M8 — Floating bulk-action bars use a hardcoded ml-[120px] offset

Files: Orders.tsx (line 356), ProductLibrary.tsx (line 365)

The fixed bottom-6 left-1/2 -translate-x-1/2 ml-[120px] centering pattern assumes the sidebar is always 240 px wide (half = 120 px). On mobile, the sidebar is hidden, so this offset pushes the bar off-center. A responsive solution (e.g., centering within the main content area) is needed.


Minor Refinements

Y1 — confirm() expand text is sometimes inconsistent in casing and punctuation

E.g., Delete ${ids.length} order${...}? This cannot be undone. vs Delete material "${mat.name}"? — no "cannot be undone" note on the latter. Standardise the confirmation copy.

File: frontend/src/pages/Login.tsx

Even in an internal tool, this is a common expectation. The page is clean and minimal, but the absence of any password-reset path means admins must use the CLI/admin panel to reset credentials.

Y3 — Category labels mix German and English in different contexts

CATEGORY_LABELS maps Kugellager, Gleitlager, Anschlagplatten (German) while other labels are English abbreviations (TRB, CRB, SRB/TORB, Linear). The mix is visible in filter dropdowns, product cards, and wizard steps. A consistent decision (all English or all German with English tooltips) would improve clarity.

Y4 — WorkerManagement lists a worker-thumbnail service that MEMORY.md says is removed

File: frontend/src/pages/WorkerManagement.tsx (line 83)

SCALABLE_SERVICES includes worker-thumbnail which according to the project memory was replaced by render-worker. Scaling a non-existent service will silently fail. The constant should be updated to match the actual docker-compose.

Y5 — Materials page header action area gets cramped at medium screen widths

File: frontend/src/pages/Materials.tsx (lines 214244)

The header row contains 4 buttons (Import Standards, Seed Aliases, Schaeffler Wizard, Add Material). At viewport widths below ~1100 px these wrap awkwardly or overflow. Grouping them in a dropdown or toolbar with overflow handling would help.

Y6 — Search highlight in Orders uses only the first occurrence

File: frontend/src/pages/Orders.tsx Highlight component (lines 496509)

The Highlight component finds only the first match of the search term (indexOf). If a product name contains the search string twice, only the first is highlighted. This is a minor usability gap.

Y7 — Notification page title uses text-xl while all other pages use text-2xl

File: frontend/src/pages/Notifications.tsx (line 77)

h1 className="text-xl font-semibold text-content" vs Dashboard/Orders/Products/Materials all using text-2xl font-bold text-content. Minor but visible inconsistency.

Y8 — WorkerActivity page shows locale date in German but the app is primarily English

File: frontend/src/pages/WorkerActivity.tsx (lines 196, 458)

Dates use toLocaleDateString('de-DE') / toLocaleTimeString('de-DE') hard-coded. Since the rest of the UI is in English, using the browser's locale (no explicit locale argument) or en-GB would be more consistent.

Y9 — Admin page contains an inline DashboardCustomizeModal alongside complex settings panels

File: frontend/src/pages/Admin.tsx

The Admin page is enormous (79 KB of source) with user management, renderer settings, pricing, output types, render templates, asset libraries, and dashboard customisation all on a single scrolling page with no in-page navigation or tabs. The page would be significantly easier to use if it were split into tabbed sections.

The product detail page has an ArrowLeft back button but does not show a breadcrumb like "Products / 81113-L_CUT" making it easy to lose context when navigating via direct URL.


Wins

  • Design system is solid: the token-based Tailwind config (bg-surface, text-content, border-border-default) is consistently applied in 95% of components. Dark mode and accent-color theming work correctly throughout the main layout.
  • Dual view toggle (Kanban/List for Orders, Grid/Table for Products) is well-implemented and the active state is clearly communicated.
  • Orders search is excellent: real-time debouncing, backend full-text search, inline search highlight, contextual empty states, result counts, and status chips all combine into a genuinely useful experience.
  • New Product Order wizard is well-designed: the 3-step flow (Select → Configure → Review), global toggles that apply across all selected products, partial-selection indicators (e.g., 2/5 counts on output type buttons), and the sticky bottom bar with live job count and price estimate are all thoughtful UX decisions.
  • Worker Activity page is rich and informative: unified timeline merging CAD and Render events, expandable detail panels with Blender log, renderer badge, timing KPIs, queue visualisation, and task cancellation — all on one page with 5-second auto-refresh.
  • Notification center: the bell dropdown with portal rendering, 15-second polling, unread badge, and click-to-navigate behaviour is a polished implementation. The full Notifications page adds pagination and "mark all as read".
  • Excel upload flow is well-thought-out: the preview-first approach (no data is committed until explicit confirmation), per-row include/exclude toggles, duplicate detection with pre-unchecked rows, validation dialog with alias save-shortcut, and the optional draft-for-skipped-rows creation show genuine product thinking.
  • Dashboard is highly customisable with 15 widget types, per-widget timeframe controls, drag-to-reorder, and tenant-level default dashboard config.
  • Sidebar live status indicators: the blue pulse dot on "Activity" when tasks are active and the red dot when tasks have failed gives operators an at-a-glance system health signal without needing to open the page.
  • Responsive sidebar: mobile hamburger + overlay backdrop + auto-close on navigation is correctly implemented with accessible aria-label attributes on the toggle buttons.
  • Preferences page is simple and effective: theme mode (light/dark/system) and accent colour picker persist via Zustand with localStorage, applying immediately.

Prioritized Recommendation List

Priority Area Issue Suggestion Effort
P1 Accessibility / Dark Mode ValidationDialog and NewInvoiceModal use hard-coded white/gray, breaking dark mode Replace all bg-white, text-gray-*, border-gray-* in dialogs with semantic tokens S (12h)
P1 Mobile Layout Fixed bottom bars use left-60 offset, breaking on mobile when sidebar is hidden Use left-0 md:left-60 or calculate offset responsively S (1h)
P1 UX Pattern confirm() used for all destructive actions Implement a small reusable ConfirmModal component M (34h)
P2 Navigation Flat sidebar with 14+ items, no section grouping Add a <div class="border-t my-2"> + optional "Admin" label before admin routes XS (30min)
P2 Navigation "Upload" is buried at item #7; primary data-entry workflow Move Upload higher in nav or consolidate with "New Order" CTA S (1h)
P2 Data Display Price estimates shown without currency symbol Extract and reuse formatCurrency from Billing.tsx in wizard bottom bars XS (30min)
P2 Scalability Product Library has a hard cap of 200 products, no pagination Add server-side pagination with offset param and "Load more" or page buttons M (35h)
P2 Maintenance Duplicate LiveRenderLog.tsx at two paths Consolidate to components/tasks/LiveRenderLog.tsx, update all imports S (1h)
P3 Completeness Billing page cannot attach order lines to invoices Implement order-line selector in NewInvoiceModal L (12d)
P3 Completeness WorkerManagement lists removed worker-thumbnail service Remove from SCALABLE_SERVICES constant XS (5min)
P3 Admin UX Admin page is one massive scrolling page Add tabs (Users / Renderer / Pricing / Templates / System) M (46h)
P3 Internationalisation German category labels mixed with English labels Standardise to one language or add tooltip for German terms S (1h)
P3 Mobile UX Orders Kanban columns overflow at standard laptop widths Cap visible columns or provide horizontal scroll within the board only M (3h)
P4 UX Polish Notification page uses text-xl while all peers use text-2xl font-bold Align heading style XS (5min)
P4 UX Polish Date formatting hard-coded to de-DE throughout worker activity Use browser locale or consistent en-GB S (1h)
P4 Discoverability Workflow Editor page is blank / non-functional but visible in nav Add a "Coming soon" placeholder or remove from nav XS (15min)

Theme & Visual Consistency Report

Strengths:

  • The Tailwind design-token system is mature: bg-surface, bg-surface-alt, bg-surface-hover, bg-surface-muted, text-content, text-content-secondary, text-content-muted, border-border-default, border-border-light, and the full status token set (status-success-bg/text, status-warning-bg/text, status-error-bg/text, status-info-bg/text) are used consistently across ~95% of components.
  • The accent-colour system (bg-accent, text-accent, bg-accent-light, bg-accent-hover, text-accent-text) is correctly applied throughout buttons, NavLink active states, and focus rings.
  • Dark mode is applied at the <html class="dark"> level with CSS variable overrides — a clean and proven approach.

Inconsistencies / Gaps:

  1. Dialogs and modals opt out: ValidationDialog (Upload.tsx), NewInvoiceModal (Billing.tsx), and portions of the Admin.tsx asset-library section use bg-white, text-gray-*, border-gray-200, and hardcoded bg-blue-600 buttons. These will break in dark mode.
  2. Purple is used as a secondary accent in several places (bg-purple-600, text-purple-700, bg-purple-100) for "render positions" / "perspectives" — this is not part of the defined accent preset palette and will look incorrect when a user switches to a non-purple accent.
  3. Billing page "New Invoice" button uses bg-blue-600 hover:bg-blue-700 instead of btn-primary (which respects the accent token). If a user has chosen the Amber or Teal accent, the button stays blue.
  4. Status chips in Orders.tsx for kanban column headers use Tailwind colour classes directly (bg-gray-500, bg-blue-500, bg-amber-500, bg-green-600, bg-red-500) rather than the status token system — these cannot be overridden in dark mode.
  5. hover:brightness-95 on Materials page group headers will not produce the intended subtle hover effect in dark mode where bg-slate-50 already contrasts with the dark background.

Functional QA Report

Verified working (via code analysis):

  • Auth flow: JWT stored in Zustand + localStorage, ProtectedRoute and AdminRoute guards, redirect to /login on unauthenticated access.
  • Order creation: both wizard paths (Excel upload + product wizard) lead to draft orders with order number confirmation.
  • Render dispatch: per-line and bulk dispatch buttons, cancel-render per-line and bulk, render progress bar in Kanban cards.
  • Material alias management: add/delete aliases, seed from standards, search across aliases.
  • Dashboard customisation: widget toggle modal, timeframe controls, custom date range.
  • Worker activity: unified timeline with auto-refresh (5s), Blender log expand, reprocess trigger.
  • Notification system: bell badge with unread count, 15s poll, portal dropdown, click-to-navigate to order, mark-one and mark-all-read.

Potential functional issues:

  1. AliasPill performs an extra API fetch on every delete (Materials.tsx lines 494505): listAliases(materialId) is called lazily on each delete click to look up the alias ID, because MaterialOut only returns alias strings, not IDs. This means every alias delete triggers an extra GET request. If the network is slow or the aliases list is stale the delete may target the wrong alias.
  2. Draft orders are deletable via bulk delete (Orders.tsx isDeletable allows submitted status too) — the confirmation copy says "This cannot be undone" but does not warn that a submitted order may already be in processing. Check if this is intentional.
  3. NewProductOrder wizard Step 1 bottom bar appears at fixed bottom-0 left-60 only when selectedProducts.size > 0. Before any product is selected, the "Next" button is not visible at all — there is no affordance indicating the user should click a product to proceed. A subtle "Select at least one product to continue" hint would help first-time users.
  4. Product Library onSelect handler inconsistency: ProductCard.onSelect is called with e.target.checked (boolean) but the prop type is (checked: boolean) => void and the parent handler is () => toggleOne(product.id) — the checked argument is discarded. The checkbox visual and the toggle may briefly diverge if the click and the optimistic state differ.
  5. WorkerManagement scale control starts at count=1 on every page load — there is no display of the current running instance count, so users cannot know if they are scaling up or down.

Mobile Report

What works on mobile:

  • Sidebar is correctly hidden by default behind a hamburger menu at md: breakpoint.
  • Mobile top header bar (height 48px) with hamburger, title, and NotificationCenter bell.
  • pt-12 md:pt-0 on the main content area accounts for the fixed mobile header.
  • Overlay backdrop (semi-transparent) closes sidebar on tap.
  • Search inputs, status chips, and filter bars use flex-wrap throughout.

Mobile problems:

  1. Fixed bottom bars (NewProductOrder, NewProductOrder Step 2/3, bulk-delete bar in Orders/Products) use left-60 — on mobile this offsets the bar 240px from the left edge of a screen that may only be 375px wide, making the bar very narrow or partially off-screen.
  2. Orders Kanban requires horizontal scrolling at any mobile screen size (5 × 272px = 1360px total). On mobile, the list view (view === 'list') is much more appropriate; consider defaulting to list view at sm: breakpoints.
  3. Admin page is a single very long scrolling page with complex tables (OutputTypeTable, RenderTemplateTable, PricingTierTable) that will overflow horizontally on mobile screens. These tables have overflow-auto wrappers but the horizontal scrolling UX is poor on touch.
  4. Order Detail page — based on the import list this page is the most complex in the app (~80KB source). The two-panel layout (items list + render lines table) is likely problematic on small screens without explicit responsive column layout.
  5. Worker Activity KV grid uses grid-cols-2 sm:grid-cols-3 — on very small screens (320px) even 2-column grids may be too cramped for monospace metric values.

User Flow Efficiency Report

Core flow: Excel import → review → create order → upload STEP → dispatch renders

  • Step count: Upload page (4 sub-steps) → OrderDetail → StepDropzone → Dispatch button.
  • The flow is logical but the step numbering on the Upload page is non-standard: Step 1 = drop zone, Step 2 = row review, Step 3 = output types, Step 4 = STEP upload. Steps 13 are on the Upload page; Step 4 redirects to the order. A persistent step indicator would reduce user disorientation.
  • Missing: no "progress saved" state — if the user navigates away after Step 2, the preview result is lost (React state only, no URL state).

Core flow: Product wizard → create order → dispatch renders

  • 3-step wizard with clear header and "Next" / "Back" navigation is clean.
  • The "global apply" toggles for output types across all products is a significant time-saver for bulk ordering.
  • The sticky bottom bar continuously shows job count and estimated price — very helpful.
  • The back navigation from Step 3 to Step 2 correctly preserves all selections.

Navigation efficiency:

  • The sidebar's primary CTA "New Order" goes to /orders/new which is a choice page (Excel vs Product Wizard). This extra click could be eliminated if the system knew which flow the user typically uses.
  • The "Open →" hover text on Kanban cards appears only on hover with an opacity transition — keyboard users or users on touch devices will not see it.
  • There is no global "search" — orders search is inside the Orders page, product search is inside the Products page. Power users working across both might want a unified command palette or global search.

Performance Observations

Polling and refresh intervals:

  • worker-activity query: refetchInterval: 60_000 in Layout (for badge indicators), but WorkerActivity page does not set refetchInterval — it relies on manual invalidation or navigation. The description says "Auto-refresh every 5 s" but the code at line 25 shows useQuery with no refetchInterval. This appears to be a documentation/UI string that does not match the implementation.
  • Queue status: refetchInterval: 5_000 in WorkerActivity's QueuePanel, 10_000 in WorkerManagement — different refresh rates for the same data on different pages.
  • Orders list: refetchInterval: 15000 — reasonable.
  • Notifications: refetchInterval: 5_000 in getNotifications call on the Notifications page; getUnreadCount in NotificationCenter polls at 15s. Good differentiation.

Data loading:

  • Product Library loads up to 200 products at once with thumbnails — on a populated system this could result in a significant number of parallel image requests. Consider lazy-loading thumbnails (Intersection Observer) for the grid view.
  • Dashboard loads 15 widget types, each making their own API calls. On first load this creates a large burst of parallel requests. The WidgetContainer + useQuery pattern handles this gracefully via React Query's deduplication, but server-side combined endpoints could reduce round-trips.
  • NewProductOrder Step 1 uses keepPreviousData: keepPreviousData only on the price estimate query, not on the product search — navigating back to Step 1 from Step 2 may show a loading state while the same products re-fetch.

Bundle considerations:

  • OrderDetail.tsx at 80KB source is the largest single component file and will have a substantial compiled bundle weight. Code-splitting at the route level (React.lazy) is not visible in App.tsx — all routes appear to be eagerly imported.
  • Admin.tsx at 79KB is similarly large. Splitting it at the tab level would reduce initial load.