18 KiB
HartOMat — UX & Quality Audit Report
Date: 2026-03-08 Overall Score: 6.5/10
Executive Summary
HartOMat is a functionally complete internal tool with a solid design system foundation (semantic CSS variables, Tailwind tokens, dark/light theming, role-aware navigation). The core workflows — Excel import → STEP upload → render dispatch — are implemented end-to-end. However, the UI suffers from information density without hierarchy: the Admin page is an undifferentiated ~3000px scroll, the Upload wizard has no progress indicator, and the Activity page mixes live queue data with historical records without clear separation. The most impactful improvements are a redesigned Admin hub with tab navigation, an Upload wizard progress bar, and standardized interaction patterns (replacing window.confirm(), adding debounce to search, and skeleton loading states).
Critical Issues 🔴
1. Admin Page — No Section Navigation
The /admin page contains 10+ distinct sections (Pricing, Users, Blender Settings, GPU Probe, 3D Viewer, Tessellation, SMTP, Render Templates, Output Types, Pricing Tiers, Asset Libraries, Admin Actions) in a single vertical scroll. On a 1080p screen, users must scroll ~3000px to reach lower sections. Sections below the fold are effectively invisible to new users.
Fix: Tab-based navigation grouping: Settings / Users / Render / Templates / Pricing / Actions
2. Upload Wizard — No Step Progress Indicator
The 4-step Excel import flow (Drop → Match Report → Output Type Selection → STEP Upload) has no visible step counter or breadcrumb. Users don't know which step they're on, how many remain, or whether they can go back. This is the primary workflow for adding work to the system.
Fix: Add <StepIndicator step={n} total={4} labels={['Upload', 'Review', 'Configure', 'STEP Files']} /> above the wizard content.
3. Login Page — Hardcoded bg-white Card Breaks Dark Mode
The login card uses bg-white instead of bg-surface, appearing as a jarring white box on the dark theme.
Fix: Replace bg-white with bg-surface in Login.tsx.
4. Badge/Category Colors Not Dark-Mode Safe
Color-coded category badges use hardcoded Tailwind light-mode classes (bg-blue-100 text-blue-700) with no dark mode adaptation. In dark mode these create incorrect contrast ratios.
Fix: Create semantic .badge-category variants in index.css using CSS variables, or use the existing .badge-blue, .badge-green etc. that already reference design tokens.
Major Improvements 🟠
5. Floating Action Bars Use Hardcoded Sidebar Offset
Both Orders.tsx and ProductLibrary.tsx use ml-[120px] (half of 240px sidebar) to center bulk-delete action bars. This breaks if the sidebar width changes or on split-screen views.
Fix:
// Replace ml-[120px] with proper centering:
style={{ left: 'calc(240px + (100vw - 240px) / 2)', transform: 'translateX(-50%)' }}
6. Product Library — Immediate Search, 200-Item Hard Cap
Search fires on every keystroke without debounce, potentially triggering 10+ API calls per typed word. A hard limit of 200 products means large libraries are silently truncated with no indication.
Fix: Add 300ms debounce to the search onChange. Add cursor-based pagination (24/48/96 per page) with prev/next buttons.
7. window.confirm() vs. ConfirmModal Inconsistency
The Activity page's "Purge All Queue" action uses native window.confirm(), breaking visual consistency with the custom ConfirmModal component used everywhere else for destructive actions.
Fix: Replace all window.confirm() calls with <ConfirmModal>.
8. German "Anpassen" Label in English UI
The Dashboard customize button reads "Anpassen" — the only German string in an otherwise English interface.
Fix: Rename to "Customize" or "Edit Widgets".
9. Submitted Orders Deletable Without Extra Warning
Bulk delete allows selecting submitted status orders. Deleting submitted orders (potentially being processed) shows the same confirmation as deleting drafts.
Fix: Show a stronger warning: "⚠️ N orders have been submitted and may be processing. Delete anyway?"
10. Admin — Multiple Section-Level Save Buttons with Unclear Scope
Four independent draft objects (blenderDraft, viewerDraft, tessellationDraft, smtpDraft) each have a save button that only appears when changes exist. Users may save one section without realizing another has unsaved changes.
Fix: Add a persistent "Unsaved changes in: Blender Settings, SMTP" notification banner at the top with a "Save All" option.
11. Kanban View — No Horizontal Scroll Affordance on Mobile
The 5-column kanban (each 280px+ wide = 1400px+ total) silently overflows on narrow screens. Mobile users see a cut-off view with no hint to scroll horizontally.
Fix: Auto-switch to list view when window.innerWidth < 768. Add a horizontal scroll hint (fade gradient at edges) on small desktops.
12. Dashboard Widgets — 15 Independent API Calls on Load
Each dashboard widget fetches data independently on mount, creating 15 simultaneous API requests and a cascade of skeleton → content transitions.
Fix: Batch widget data into a single /api/dashboard/data?widgets=... endpoint. Or stagger widget loading with short delays to avoid request flood.
Minor Refinements 🟡
13. Login — No Password Visibility Toggle, No Return URL Redirect
No eye icon to show/hide password. After login, always redirects to / instead of the originally requested URL.
Fix: Add <Eye>/<EyeOff> icon button. Capture location.state.from and redirect post-login.
14. Activity Page — Blender Log Fixed Max Height
The Blender stdout log panel has max-h-64 (256px), creating a scroll-within-a-scroll for long logs.
Fix: Add "Expand log" toggle that removes the max-height constraint.
15. Sidebar — Admin-Only Links Not Visually Separated
Admin-only items (Notification Settings, Tenants) are visually mixed with project_manager-accessible items, making role distinction unclear.
Fix: Add a small "Admin Only" section label above the admin-exclusive links.
16. Upload Wizard — Step 3 JSX Appears After Step 4 in Source
Upload.tsx renders Step 3 (Output Type Selection) JSX after Step 4 (STEP Upload) in the source file, creating a maintenance hazard.
Fix: Reorder JSX blocks to match display order: Step 1 → 2 → 3 → 4.
17. Upload — "Gew. Produkt" Column Has No Tooltip
The abbreviated German column header is not explained. New operators won't know this means "Gewähltes Produkt" (Selected Product).
Fix: Add <HelpTooltip text="Gewähltes Produkt — the specific product variant selected in the Excel file" /> next to the column header.
18. Media Browser — Fragmented Filter UI
Two separate filter areas exist: "Media type" segmented tabs and "Technical types" checkboxes. Their relationship is visually unclear.
Fix: Group all filters under a unified collapsible "Filters" panel with labeled sub-groups.
19. Product Library — No Sort Controls
Products can be filtered but not sorted (by name, PIM-ID, date, or render status).
Fix: Add sort dropdown: "Sort: Newest / A–Z / Status".
20. No Skeleton Loading States on Key Pages
Orders list, Product library, and Activity page show empty state or nothing briefly before data arrives, causing layout shift.
Fix: Add skeleton rows/cards matching the expected content shape on first load.
Wins ✅
- Theme system is production-grade: CSS variable tokens + Tailwind semantic classes +
localStoragepersistence + system preference detection + flash prevention inmain.tsxbefore React hydration. - Role-aware navigation: Privileged links correctly hidden from clients; admin sections conditionally rendered.
- Order kanban progress bars: Color-coded segments (green/blue/red/orange/gray) give instant visual feedback on render completion state.
- Notification Center: Bell dropdown with badge, portal rendering, unread count, relative timestamps, and 15s polling — complete and polished.
- Hover-to-play video thumbnails: Both
OrderDetailandProductDetailhave smooth hover-to-play with play icon overlay — good progressive disclosure. - Upload validation dialog: Traffic-light summary with per-row issues and "Save as alias" is a sophisticated, context-aware feature.
- Activity expand rows: Full timing breakdown, renderer settings, part count, and syntax-colored Blender log are invaluable for debugging.
- Excel import wizard: Header-driven column detection handles format variance gracefully.
- Render template system: Lighting-only mode, shadow catcher, material replacement — powerful and well-integrated.
- STL cache convention: Prevents repeated STEP→STL conversions on re-renders.
Prioritized Recommendation List
| Priority | Area | Issue | Suggestion | Effort |
|---|---|---|---|---|
| 1 | Admin | 3000px undifferentiated scroll | Tab navigation: Settings / Users / Render / Templates / Pricing / Actions | Medium |
| 2 | Upload | No step progress indicator | Add <StepIndicator> component above wizard |
Low |
| 3 | Login | bg-white breaks dark mode |
Replace with bg-surface CSS token |
Low |
| 4 | Global | Category badges not dark-mode safe | Use semantic badge classes with CSS variables | Low |
| 5 | Orders | Submitted orders deletable without warning | Split confirmation messages by status | Low |
| 6 | Products | Search fires on every keystroke | Add 300ms debounce | Low |
| 7 | Products | 200-item hard cap with no pagination | Add cursor-based pagination | Medium |
| 8 | Activity | window.confirm() in queue purge |
Replace with ConfirmModal |
Low |
| 9 | Global | Floating bars use hardcoded ml-[120px] |
Fix with CSS calc centering | Low |
| 10 | Dashboard | "Anpassen" German label | Rename to "Customize" | Low |
| 11 | Orders | Kanban unusable on mobile | Auto-switch to list view < 768px | Low |
| 12 | Login | No password toggle, no return URL | Add Eye icon + capture return URL | Low |
| 13 | Admin | Multiple save buttons unclear scope | Add "Unsaved changes" banner | Low |
| 14 | Products | No sort controls | Add sort dropdown | Low |
| 15 | Global | No skeleton loading on key pages | Add skeletons to Orders, Products, Activity | Medium |
| 16 | Activity | Blender log 256px fixed height | Add "Expand log" toggle | Low |
| 17 | Sidebar | Admin-only links not separated | Add "Admin Only" section label | Low |
| 18 | Media | Fragmented filter UI | Unified collapsible filters panel | Medium |
| 19 | Dashboard | 15 independent API calls on load | Batch widget data endpoint | High |
| 20 | Upload | Step 3 JSX after Step 4 in source | Reorder JSX blocks | Low |
Theme & Visual Consistency Report
Overall: Strong foundation with CSS custom properties driving all surfaces, text, borders, and status colors.
Light Theme ✅
Surfaces differentiated in 4 levels (bg-app → bg-surface → bg-surface-alt → bg-muted). Text in 3 weights. Status colors (success green, warning amber, error red, info blue) correctly use bg+text pairs.
Dark Theme ✅ (with caveat)
Dark mode applies slate palette correctly. CSS variables swap on <html class="dark">. Flash prevention in main.tsx synchronously reads localStorage before React hydration.
Issues in dark mode:
- Login card:
bg-whitehardcoded — white box on dark background (fix:bg-surface) - Category badges:
bg-blue-100 text-blue-700etc. — wrong contrast in dark mode (fix: semantic badge classes) - Some inline Tailwind classes in pages bypass the design token system
Component Consistency
- Buttons:
.btn-primary/.btn-secondary/.btn-dangerdefined inindex.css— consistent ✅ - Cards:
.cardclass used consistently ✅ - Badges: Mixed —
.badge-green/.badge-bluesemantic classes exist but not always used; raw Tailwind color classes appear throughout pages - Inputs:
.input-basedefined but pages sometimes use inlineborder border-border-default rounded-md px-3 py-2directly — minor inconsistency - Modals: Single
Modal.tsxcomponent used consistently ✅
Accent Color System
5 presets (green/blue/purple/amber/teal) applied via data-accent on <html>. Applied to: active nav links, primary buttons, checkboxes, focus rings, status indicators. Consistent ✅.
Functional QA Report
Modal & Dialog Behavior
Modal.tsx: Escape to close ✅, backdrop click to close ✅, size variants ✅, focus handling ✅ConfirmModal.tsx: Focus trapping ✅, Tab/Shift-Tab cycling ✅, Escape to cancel ✅window.confirm()in queue purge: Inconsistent ❌ — replace with ConfirmModal
Form Validation
- Excel upload: File type validated pre-submit ✅
- User creation: No password strength indicator ❌
- Output type checkboxes in Upload Step 3: No indeterminate state for partially-checked columns ❌
Loading & Error States
| Page | Loading State | Error State |
|---|---|---|
| Dashboard | Skeleton ✅ | Toast ✅ |
| Orders | None ❌ | Toast ✅ |
| Products | None ❌ | Empty state ✅ |
| Activity | None ❌ | Toast ✅ |
| Upload | Spinner on file parse ✅ | Inline validation ✅ |
| Admin | None ❌ | Toast ✅ |
Keyboard Navigation
- Sidebar: Tab-navigable, Enter activates ✅
- Modals: Focus trapped ✅
- Kanban cards: Click-only, no keyboard activation ❌
- Data tables: Tab through rows not supported ❌
404 Handling
No custom 404 page — unknown routes redirect to / silently. Fix: Add a * catch-all route with a friendly "Page not found" component.
Mobile Report
Responsive Layout
- Sidebar: Correctly collapses to drawer on mobile ✅
- Fixed mobile header:
h-12with hamburger + app name + notification bell ✅ - Breakpoint: Tailwind
md(768px)
Issues
| Issue | Severity | Location |
|---|---|---|
| Kanban 5 columns = 1400px+ wide, no mobile adaptation | High | Orders.tsx |
| Table checkboxes 16×16px (min 44×44px touch target) | High | Orders, Products table views |
| Upload Step 3 output type table requires horizontal scroll on mobile | Medium | Upload.tsx |
| Search placeholder text clipped on 320px screens | Low | Multiple pages |
| Admin page is ~5000px scroll on mobile with no section navigation | High | Admin.tsx |
Touch Target Audit
- NavLink items: Full-width ~40px height — ✅ adequate
- "New Order" CTA button: Full-width 48px — ✅ correct
- Table checkboxes: 16×16px — ❌ too small
- Filter chips: ~32px height — ⚠️ borderline (minimum 44px recommended)
- Kanban "Open →" text link: Small — ❌ too small for touch
User Flow Efficiency Report
Flow 1: Excel Import → Dispatch Renders (Primary Workflow)
| Step | Action |
|---|---|
| 1 | Sidebar → Upload |
| 2 | Drop Excel file |
| 3 | Review product matches |
| 4 | Select output types |
| 5 | "Create Order" button |
| 6 | Upload STEP files (or skip) |
| 7 | Navigate to created order |
| 8 | Click "Dispatch Renders" |
Total: 8 steps across 2 page transitions. Verdict: Reasonable. Biggest friction = no step indicator (users don't know where they are in Step 3).
Flow 2: Re-render Failed Job
| Step | Action |
|---|---|
| 1 | Notice failure in Activity or Dashboard |
| 2 | Click order link in Activity row |
| 3 | Find failed render line |
| 4 | Click retry button |
Total: 4 steps. Verdict: Good — direct links from Activity to orders work well. Could add "Retry All Failed" bulk action.
Flow 3: Change Renderer Settings
| Step | Action |
|---|---|
| 1 | Sidebar → Admin |
| 2 | Scroll to "Blender Render Settings" (~400px) |
| 3 | Change setting |
| 4 | Wait for save button to appear |
| 5 | Click Save |
Total: 5 steps. Friction: Step 2 scroll discovery. With tab navigation, this becomes 4 steps with zero scroll.
Flow 4: Find Product by PIM-ID
| Step | Action |
|---|---|
| 1 | Sidebar → Products |
| 2 | Type PIM-ID in search |
| 3 | Click product card |
Total: 3 steps. Verdict: Efficient ✅.
Flow 5: Add Material Alias After Render Failure
| Step | Action |
|---|---|
| 1 | Notice wrong material in Activity |
| 2 | Sidebar → Materials |
| 3 | Find material by name |
| 4 | Expand material row |
| 5 | Click "+" / type alias / Enter |
Total: 5 steps. Improvement: "Add alias for: [part_name]" shortcut directly from Activity row detail would save 3 steps.
Performance Observations
API Call Pattern
- Dashboard: 15 independent widget API calls on mount — waterfall of loaders
- Activity: Every 5s polling — appropriate for live monitoring
- Notifications: Every 15s polling — appropriate
- Products: Immediate search (no debounce) — excessive API calls while typing
Image Loading
- Thumbnails:
Cache-Control: max-age=3600✅ - Product grid: No
loading="lazy"on 200 simultaneous thumbnail requests ❌ — Fix: Addloading="lazy"to all thumbnail<img>tags - Videos:
preload="metadata"correctly loads only first frame ✅
Layout Shift
- Dashboard: Widget skeleton height → actual content causes shift
- Products: Empty state → full grid shift (no skeleton)
- Activity: Stats cards appear before timeline rows
Optimistic UI Opportunities
| Action | Current | Optimistic |
|---|---|---|
| Mark notification read | Wait for API | Decrement badge immediately |
| Bulk delete orders | Wait for all deletes | Remove from list immediately |
| Submit order | Wait for response | Show "submitted" badge immediately |
Blocking Interactions
- "Dispatch Renders" button: Correctly disabled during dispatch with loading spinner ✅
- "Save Settings" button: No loading state during save — button disappears on success (draft resets) which feels abrupt. Fix: Show spinner in button during save, then confirm with a brief checkmark before disappearing.
Report generated 2026-03-08 via codebase analysis. Next: Implement Priority 1 (Admin tabs) and Priority 2 (Upload step indicator) first for maximum UX impact.