Work through each section during the heuristic walkthrough. For each item, check it off when evaluated and record observations below the section. Rate severity:
Not every item will apply to every product — use judgment. The goal is a rich findings list, not a complete score. Code scan data (hardcoded values, token mapping) is appended at the end.
What a user feels in the first 30 seconds — and whether the product looks and feels like one thing.
Aesthetic-Usability Effect: Users perceive aesthetically pleasing design as more usable. A polished first impression raises the user's confidence before they've done anything.
Law of Similarity / Law of Uniform Connectedness: Inconsistent visual treatment signals inconsistent relationships and creates cognitive friction.
The Von Restorff Effect suggests that key actions and information should stand out from surrounding content. If everything competes for attention, nothing wins.
Overall: strong. Consistent Optics usage (32/35 stylesheets), only 15 hardcoded design values total, and a coherent brand.
Pattern Light theme hardcoded. Layout ships data-theme-mode="light"; Optics dark-mode support is unused. (app/views/layouts/application.html.slim:2)
Pattern A few off-token chart colors. Timeline-chart uses hardcoded hex outside the token system. (app/assets/stylesheets/components/timeline-chart.scss:39-45)
Strength to protect: the 12-page customer Proposal is polished and on-brand.
Can users find what they need, understand where they are, and get back if they get lost?
Jakob's Law: Users spend most of their time on other sites and expect your product to work like the ones they already know. Deviation has a cost.
Nielsen's principle: Users shouldn't have to remember where they are or how they got there.
Nielsen's principle: Users need a clearly marked "emergency exit" from unwanted states.
High Flat 14-item sidebar, no grouping. Daily workflow entry (Sites) is visually identical to 11 reference/config screens. Role-scoped — non-admins see 4 items. (app/views/application/_sidebar.html.slim:6) Verified live.
High 3-level hierarchy collapses to one indicator. Site / Estimate / Tank all light up the single "Sites" link; the global nav gives no depth signal. (_sidebar.html.slim:8)
Medium No global search or recents. The only route into active work is via Sites; no way to jump to a recent estimate.
Medium Dead-end panels & new-tab escapes. Comments/edit panels expose only a Close button (no back); some edits open in new tabs. (app/views/layouts/panel.html.slim)
Pattern Fragile active-state matching. Active nav is URL-prefix string matching, hand-synced with routes. (app/helpers/sidebar_helper.rb:8)
Working well: breadcrumbs populate correctly on deep pages — the one solid wayfinding mechanism.
How hard is the product making users think? Are we asking for more mental effort than necessary?
Hick's Law: The time it takes to make a decision increases with the number and complexity of choices. Miller's Law: The average person can hold 7 (±2) items in working memory.
Law of Common Region and Law of Proximity: Grouping related elements reduces cognitive effort and helps users build accurate mental models.
Occam's Razor: The simplest solution that accomplishes the goal is usually the right one. Every extra field is a cost to the user.
Nielsen's principle: The product should speak the user's language, not the system's.
Goal-Gradient Effect: People move faster toward a goal as they perceive themselves getting closer. Zeigarnik Effect: People remember and feel pulled toward incomplete tasks.
High Estimate view is "one screen, two jobs". It interleaves estimator knobs (margin / contingency / commission / field wage) with sales/customer data on one undifferentiated sheet, identical for both roles and every status. (app/views/estimates/show.html.slim) Verified live.
Medium Tank details read-view is ungrouped. A flat 20+-field list with no subsections — inconsistent with the well-sectioned edit form. (app/views/tanks/_details_sheet.html.slim:3)
Lens note: domain jargon (hoppers, portal frames, rings, BOM/BOL/RFQ) is acceptable for estimators (engineers) but raises the barrier for sales, who are ≈half of daily users.
Walk through the most critical user journeys. Can users accomplish what they came to do? Identify the top 2–3 core user tasks before starting this section and test each one.
Core tasks being evaluated:
Estimator: build an estimate — Site → Estimate → Tank → Assemblies → priceFitts's Law: The time to acquire a target is a function of its distance and size. Small or distant targets increase error rates and friction.
Mental Model principle: When a product's structure doesn't match how users think about the task, they make more errors and feel less confident.
Nielsen's principle: Error messages should be in plain language, identify the problem, and suggest a solution.
Postel's Law: Products should gracefully handle unexpected input rather than failing.
Peak-End Rule: Users judge an experience by how they felt at its most intense moment and at its end — not by the sum of the whole.
High Landing page has no "start here". The dashboard (home for both roles) offers no New Estimate/Site CTA; its empty state is a dead end. (app/views/dashboards/show.html.slim:33) Verified live.
High Submit-only validation / reload loop. No inline validation; HTML5 validation globally disabled (browser_validations=false), so an error on a long form costs a full reload + scroll-hunt. (config/initializers/simple_form.rb:140) Corrected live: the forms themselves are well-structured and fine for engineers — the friction is the submit/reload loop, not density.
Medium Silent blank-name save. Tank#name has only a uniqueness check, no presence — clearing it saves a nameless tank (blank rows in lists). (app/models/tank.rb:55) Verified live.
Medium Reports buried behind an unlabeled kebab. BOM/BOL/Budget report generation — a top sales goal — hides in the ⋯ menu. (app/views/estimates/_header.html.slim:34) Verified live.
Pattern Modal vs full-page inconsistency. Assemblies add in a modal; tanks/estimates force full-page navigation — unpredictable "add a child".
Does the product keep users informed and in control?
Nielsen's principle (Visibility of System Status): Keep users informed about what's going on through appropriate feedback within a reasonable time. Doherty Threshold: System responses faster than 400ms feel immediate.
Nielsen's principle: User control and freedom.
High Processing state: no progress, no lockout, reactive only. Static "Processing, please wait…" with no spinner; all actions stay enabled; guarded actions are refused only after a click, via a transient alert. (app/controllers/concerns/processing_guard.rb:8) Corrected live: the guard uses redirect_back_or_to — users are not ejected to root as first assumed; they stay on the page with a ~5s alert.
High Pricing shows bare "Loading…" and dead-end errors. The app's primary output renders as unstyled placeholder text (no skeleton/aria-busy), and calc failures render "Calculation Error" with no cause or retry. (app/view_components/lazy_calculation_view.rb:18; app/views/lazy_calculations/show_error.html.slim:2)
High Flash vanishes in ~5s; errors look like successes. No dismiss control, no persistent variant; success/error differ only by color; all use role="alert". (app/views/application/_flash.html.slim:3) Verified live.
High Comment delete has no confirmation. A plain delete with no turbo_confirm while every other delete confirms — one-click, permanent. (app/views/comments/_comment.html.slim:18) Verified live — deleted a comment with no prompt.
High Zero-result index renders a blank page. A trailing if @companies.any? suppresses the helper's built-in empty state. (app/views/companies/index.html.slim:8) Verified live. One-line fix.
Medium Delete confirmations are the least specific. "Are you sure?" for deletes, while duplicate/revise name the record. (app/helpers/context_menu_helper.rb:50)
Does the product behave predictably, and does it follow platform conventions?
Nielsen's principle: Users should not have to wonder whether different words, situations, or actions mean the same thing.
Jakob's Law: Familiar patterns reduce learning curves.
Law of Uniform Connectedness: Consistent visual treatment of interactive elements prevents confusion.
Pattern Mixed control semantics in one menu. Edit is a link; Delete/Duplicate are button_to forms — different focus order and keyboard activation for look-alike items. (app/helpers/context_menu_helper.rb:17,26)
Medium Estimate form lacks the tank form's sectioning. The tank edit form has clear section headers; the estimate edit form is one continuous flow. Empty-state markup is applied three different ways.
Positive model: the tank edit form (sections, required asterisks, conditional fields) is the pattern the rest of the app should follow.
Who is being left out — and what's the effort to fix it? Includes WCAG contrast, ARIA issues, keyboard navigation, and semantic HTML findings from the code scan.
Fitts's Law: Target size directly affects accuracy and ease of interaction.
High No accessible names on icon-only controls. 0 ARIA attributes across 338 views. The icon helper renders Material Symbols ligature text as content (screen readers say "edit_note") and title defaults to the raw icon name (tooltips like "edit"). (app/helpers/icon_helper.rb:24) Verified live.
High No keyboard/focus support; focus rings only on buttons. No keydown/focus/tabindex handling in any custom Stimulus controller; :focus-visible exists only on .btn, so tabs, links, selects and menu triggers show no focus. No <main> landmark or skip-nav.
High Color-only meaning for out-of-spec values. Usable volume below the customer minimum is shown as red text only — no icon/label. (app/views/short_sheets/_show.html.slim:13; details-sheet.scss:61) Verified live.
Medium Placeholder-only search inputs. 17 index search fields have no label. (e.g. app/views/materials/index.html.slim:5) Verified live. Main resource forms use simple_form correctly.
Does the layout adapt gracefully? Are touch targets appropriately sized? Can mobile users complete core tasks?
High Almost no responsive rules outside the sidebar. App-level @media breakpoints appear only in sidebar.scss; main content and forms rely on Optics defaults.
High Wide tables not wrapped for scroll by default. resource_table emits a bare table; only 4 views opt into .scroll-x, so wide pricing tables clip on small screens. (app/helpers/view_component_helper.rb:31)
Medium Icon-only actions lack a guaranteed touch target. No ~44px min-size override on the primary row-action affordances.
We're not doing a technical performance audit — but we note what we can observe.
Doherty Threshold: Productivity and engagement drop when users have to wait. Perceived performance matters even when actual speed isn't our scope.
Medium Lazy values read as "Loading…". On slower links, estimate lists show columns of placeholder text where prices belong.
Positive: turbo-prefetch + morph refresh are configured globally for snappy navigation.
Watch: the application.js build is large (bundles Monaco); confirm it is only loaded where the formula editor is used.
Step back from the individual findings. What's the bigger picture?
Thesis: the product is built as an engineering tool, but ≈half of daily users are sales (less technical). The highest leverage is making the estimate experience role/stage-aware instead of one-size-fits-all.
Assets to protect: the polished customer Proposal and a genuinely clean Optics token system.
Discovery gap (itself a finding): the sales-facing UX has never been validated against real sales behavior. Recommend a short session with 1–2 sales users — what they check on landing, when they generate vs send the proposal, what is missing.
Pattern Proposed sales landing (hypothesis — validate before building). For locked / sales-stage estimates, lead the estimate header with customer + price + one-click Proposal / Scope / Reports, and collapse estimator knobs (margin/contingency/field wage) into a secondary "Pricing inputs" group. Best-guess per request; confirm with sales first.
Quick wins (workflow-independent): fix the blank empty-state (1 line), add confirm to comment delete (2 words), give flashes a dismiss control + persist errors, mark required fields / add inline validation.
Every unique value found in the codebase mapped to its Optics equivalent. Generated by Phase 2 code scan.
15 hardcoded values across 35 files. Sorted by count descending. Bar color: red >20, orange 10–20, green <10.
Current custom components and their closest Optics equivalents.
After completing the walkthrough, list your top findings here. Use this table to prioritize the backlog.
| # | Finding | Section | Severity | Impact | Effort |
|---|
Prompts that worked well on this audit — capture any Claude prompts that generated particularly useful output so we can build a shared library across audits.