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.
aria-label into the Icon/Button component, aria-current into the active_link_to helper, and a clearer error path into the shared ConfirmButton each propagate across the whole app from a single change. Effort is modest: the Critical and most High items are low-effort, high-leverage edits rather than a redesign.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.
--op-* uses across 114/118 stylesheets), so the product reads as one consistent system. The brand palette is defined in one place as HSL primitives — themes/landone_theme.scss (primary H:201 S:67% L:29%, a deep teal-blue) — rather than scattered hex values.rgba(…,1) values defined twice — components/mobile-profile-sheet.scss:59-67 and components/profile-menu.scss:159-167. They sit outside the Optics palette so they don't theme, and the duplication risks drift. (Medium)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.
NavLink model abstracts the nav, active_link_to drives a clear visual active state (primary-tinted background + indicator bar — layouts/sidebar.scss:100-115, layouts/bottom_nav_rail.scss:45-63), breadcrumbs collapse responsively, and the sidebar swaps to a bottom rail on mobile.aria-current. The active page is obvious visually but carries no aria-current="page"; there are zero occurrences anywhere in the codebase, so screen-reader users don't get the "current page" semantic. Best fixed centrally in the active_link_to helper output. (Medium)shared/navigation/_bottom_nav_rail.html.slim:9-32) collapses overflow items into a drawer with no count or label hint. (Low)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.
estimate_request/EstimateRequestLeftPanelWizard.jsx:40-45) but renders no "Step 2 of 4" stepper; progress is inferred from headings and the back/forward buttons alone. (Medium — Goal-Gradient / Zeigarnik)EstimateRequestStep3.jsx:68-101; sections/new.html.slim:7 renders "Name (Required)" as plain text). The forward button gates on completion (EstimateRequestLeftPanelWizard.jsx:291-299), so users discover requirements by being blocked rather than by scanning. (Medium)master_materials/MasterMaterialForm.jsx presents ~15 inputs in a flat list with no grouping; inline error display is wired only for the name field (lines 192-202, 238-245). (Low)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:
1. Create an estimate request — 4-step wizard (boundaries → features → details → review).Fitts'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.
estimate_request/BulkOrderProcessingProgress.jsx:44-82 shows a step-by-step progress ring with per-stage icons and surfaces unverified addresses explicitly. Forward buttons disable when required data is missing, preventing invalid submissions.common/ConfirmButton.jsx:69-76 surfaces "Something went wrong!" with no cause and no recovery step. Because ConfirmButton is shared, surfacing the real error reason + an explicit retry path is a single centralized win. (High — Nielsen: error recovery)EstimateRequestLeftPanelWizard.jsx:328-344); others such as MasterMaterialForm.jsx close without confirmation. (Medium — user control & freedom)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.
markup/DynamicToolToast.jsx:48-78 implements clean three-state feedback (searching spinner → success → error) with icon + text and auto-dismiss. Action acknowledgement and state-dependent button disabling are consistent across flows.shared/_flash.html.slim:1-6), so a message's severity isn't legible at a glance. The data-turbo-cache=false hint also suggests past flash/Turbo race issues. Style by type using Optics --op-color-alerts-* tokens with a distinct icon + color per level. (Medium)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.
chunks/application/components/LandOneModal.jsx is a shared wrapper over Shoelace SlDialog/SlDrawer (focus-trap and aria-modal handled), and 28 of 32 *Modal*.jsx files compose it. Form labels are properly associated via htmlFor, and status badges render human-readable text (not color-only).<sl-button> (~61 uses), the Optics .btn--small class (~284 uses), and raw <button> tags in Slim views. This is maintainability/consistency drift rather than a user-facing bug, but button behavior and styling have no single source of truth. Pick one canonical wrapper and migrate incrementally. (Pattern)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.
shared/_head.html.slim:5 sets the viewport to maximum-scale=1, user-scalable=no, so low-vision users on mobile cannot zoom in. WCAG 1.4.4 (Resize Text). It's also internally inconsistent — visual_diffs.html.slim uses an unrestricted viewport. Fix: drop maximum-scale=1, user-scalable=no (one line). (Critical)chunks/application/components/LandOneIconFactory.jsx:96,104) sets a title from hoverText, defaulting to the raw icon name — so a screen reader may announce a machine string like "keyboard_backspace", and title is an unreliable accessible-name source. Some call sites pass hoverText="", leaving no accessible name at all (e.g. EstimateRequestModal.jsx:157). WCAG 4.1.2 / 1.1.1. Fix by wiring an explicit aria-label on icon-only buttons, centralized in the Icon/Button component. (High)outline:none uses correctly pair with a :focus-visible box-shadow ring (base/base.scss:13 and others — safe). Two component stylesheets strip the outline with no replacement: layouts/markup_left_panel.scss and components/material_list.scss, removing the keyboard focus indicator there. WCAG 2.4.7. (High)<div>s have no keyboard equivalent. lightning-cad-skin/views/ToolView.jsx:54 and markup/GraphicalScaleControl.jsx:185 use <div onClick> with no role/tabIndex/onKeyDown. WCAG 2.1.1. (The other 489 onClick handlers are on real buttons/components — this is not endemic. The GraphicalScaleControl case may be a propagation-only wrapper, i.e. a possible false positive — verify when fixing.) (High)<main> landmarks exist server-side but no skip link targets them. WCAG 2.4.1. (Medium)Does the layout adapt gracefully? Are touch targets appropriately sized? Can mobile users complete core tasks?
components/sheet.scss:80), primary sheet actions 48px, buttons upsize on mobile (components/button.scss:122-129). Mobile detection is CSS-first via a matchMedia React context (recent removal of the hardcoded isMobile prop, commit 72df7657f). Adaptive reflow is used in places (flex → grid, thumbnail-card.scss:384-434).@media queries use inconsistent hardcoded breakpoints (768 / 1024 / 1280 / 390 / 512 / 1140 / 1225 / 1420) with no central variable set; thumbnail-card.scss:45-46 defines its own local breakpoint vars, and Shoelace overrides use a different syntax. Optics exposes --op-breakpoint-* (referenced in comments but not used as values). (Medium).hide-on-mobile { display:none !important; } utility (base/utilities.scss:68) and a hidden filter header (base/mobile.scss:82) remove functionality on small screens instead of relocating it (e.g. into a filters sheet). (Medium)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.
LandOneModal guards rendering with a renderModal flag (LandOneModal.jsx:120) so modal contents aren't mounted until visible — good for perceived performance. Minor: components/leaflet_overrides.scss:35 uses font-size: 2rem !important, a heavy override worth a glance. No deeper performance claims made without a running app. (No findings raised.)Step back from the individual findings. What's the bigger picture?
LandOneModal/Shoelace) are a real asset. This foundation means UX and accessibility improvements can be made centrally rather than screen-by-screen.title, missing aria-current, occasional focus-outline removal, and no skip link. None are architectural; all are addressable.aria-label into the Icon/Button component so every icon-only button gets a real accessible name from one change. (3) Add aria-current="page" in the active_link_to helper. (4) Improve the shared ConfirmButton error path. Three of these four are single-source fixes that propagate app-wide.PropertyIntel is already on Optics — this table maps the few values that escape the token system, not a migration. The brand palette lives correctly in themes/landone_theme.scss as HSL primitives feeding the --op-color-* scales; those are the token source, not drift.
| Current value | Where | Optics token | Fit |
|---|---|---|---|
| Brand HSL primitives (primary H:201 S:67% L:29%, neutral, warning, danger, aspire) | themes/landone_theme.scss | --op-color-{primary,neutral,…}-h/s/l | Exact — token source |
rgba(255,239,224,1) / rgba(82,39,0,1) (orange chip), yellow pair | mobile-profile-sheet.scss, profile-menu.scss | No Optics equivalent (decorative); define once or map to --op-color-alerts-warning-* | Miss — duplicated |
radial-gradient(rgba(187,222,242,1) → rgba(246,251,253,1)) | layouts/login.scss | Could derive from --op-color-primary-plus-* | Close |
21 × rgba(0,0,0,0.0x) shadow-color alphas | various (box-shadow) | Roll into --op-shadow-* tokens | Close |
| Current value | Count | Optics token | Fit |
|---|---|---|---|
px inside named custom properties (e.g. --attachments-border-radius: calc(var(--op-size-unit) * 8)) | ~120 | Allowed per project CSS conventions (non-grid / sub-pixel values in block-scoped props) | Exact — by convention |
1px borders | 23 | n/a (hairline borders) | Exact |
| Other raw px in spacing/sizing props | ~190 | calc(N * var(--op-size-unit)) where divisible by 4 | Close — case-by-case |
| Current value | Where | Optics token | Fit |
|---|---|---|---|
| All radii tokenized | codebase-wide | --op-radius-small … --op-radius-pill | Exact |
border-radius: unset / none (resets) + one 5000px pill hack (commented) | ~11 spots | n/a (intentional resets) | Exact |
| Current value | Where | Optics token | Fit |
|---|---|---|---|
Font sizes tokenized; scale defined in theme (--op-font-*) | codebase-wide | --op-font-* | Exact |
3 literal font-size (0, leaflet 2rem !important override, 1 comment) | sidebar.scss, leaflet_overrides.scss | n/a (legit / 3rd-party override) | Exact |
| Current value | Count | Optics token | Fit |
|---|---|---|---|
Literal box-shadow (px offsets + rgba(0,0,0,0.x) colors) | ~122 declarations | --op-shadow-x-small … --op-shadow-x-large | Close — mappable |
49 non-tokenized color literals (hex + rgb/rgba/hsl not wrapping an --op token) across 16 files — the meaningful "hardcoded" metric here, since spacing/radius/typography are essentially fully tokenized. The largest entry is the theme source file, where raw HSL values are correct. Bar color: red >20, orange 10–20, green <10.
Most of the app already composes Optics/Shoelace. The opportunities below are about consolidating competing patterns onto one canonical component, not introducing the design system.
After completing the walkthrough, list your top findings here. Use this table to prioritize the backlog.
| # | Finding | Section | Severity | Impact | Effort |
|---|---|---|---|---|---|
| C1 | Pinch-to-zoom disabled app-wide (viewport) | 7 · Accessibility | Critical | High | Low |
| H1 | Icon-only buttons have weak/missing accessible names | 7 · Accessibility | High | High | Medium |
| H2 | Focus outline removed without :focus-visible (2 spots) | 7 · Accessibility | High | Medium | Low |
| H3 | Two clickable <div>s have no keyboard equivalent | 7 · Accessibility | High | Medium | Low |
| H4 | Generic "Something went wrong!" error (shared ConfirmButton) | 5 · Feedback | High | Medium | Low |
| M1 | No skip-to-content link | 7 · Accessibility | Medium | Medium | Low |
| M2 | Active nav state is visual-only — no aria-current | 2 · Navigation | Medium | Medium | Low |
| M3 | Estimate wizard has no progress indicator | 3 · Cognitive Load | Medium | Medium | Medium |
| M4 | Required fields rely on label text alone | 3 · Cognitive Load | Medium | Medium | Low |
| M5 | Flash messages lack visual hierarchy by type | 5 · Feedback | Medium | Low | Low |
| M6 | No unsaved-changes warning on most forms/modals | 4 · Key Flows | Medium | Medium | Medium |
| M7 | Breakpoints scattered and undocumented (internal) | 8 · Mobile | Medium | Low | Medium |
| M8 | Some mobile rules hide content rather than adapt it (internal) | 8 · Mobile | Medium | Medium | Medium |
| M9 | Decorative chip colors hardcoded & duplicated (internal) | 1 · Visual Coherence | Medium | Low | Low |
| P1 | Three button patterns coexist (sl-button / .btn / <button>) (internal) | 6 · Consistency | Pattern | Low | High |
| L1 | Mobile "More" overflow gives no preview of hidden items | 2 · Navigation | Low | Low | Low |
| L2 | Long material form isn't visually sectioned | 3 · Cognitive Load | Low | Low | Medium |
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.
LandOneModal.jsx and the LandOneIconFactory showed the real picture (shared wrapper handles aria-modal; icons get a title, not nothing). Prompt pattern that worked: spawn one Explore agent per audit area asking for "CONFIRMED findings with exact file:line evidence, separate what turned out FINE, do NOT fabricate," then spot-verify the headline claims by reading the source directly.