MAPLE · UI Audit

Product Design Audit · Real-estate underwriting app

Good bones, generic paint.

MAPLE was measured against how the best modern software actually looks and behaves — and against the specific tells that make an app read as generic, template, AI-generated "slop." The foundation is genuinely well-built. Three global cosmetic decisions and a handful of craft gaps are what hold it back.

Scope 28 pages · 157 components Method code audit + design research Date Aug 2026 Stack React · Tailwind · shadcn/ui
11 /22
Competent · Generic

The one-paragraph read

MAPLE scores 11 of 22 on a premium-vs-slop rubric — the "competent but generic" band. That undersells the foundation and oversells the flaws, which is the point: the bones are premium-adjacent (a custom blue brand instead of the AI-default purple, a sophisticated hand-built hover system, tight considered corner radii, honest handling of low-confidence valuations, near-total real iconography). What caps it at "generic" is almost entirely subtractive and cheap to fix: three decorative decisions applied globally — glassmorphism on every card, purple gradient blobs on every page, and a purple pulsing glow on the AI button — plus number-alignment, keyboard-focus, and motion gaps.

Translation for a first impression: strip the purple, tighten the tables, and this jumps a full tier without a redesign. It is safe to put in front of a broker today; the fixes below are polish, not rescue.

The AI assistant reads as a thoughtful feature wearing a generic-chatbot costume. The substance is crafted and product-specific; the paint is the slop.

01 — Scorecard

22 criteria, scored against the codebase

A binary rubric synthesized from Nielsen Norman Group, Baymard, and the Stripe / Linear / Vercel / Raycast design-craft literature. Pass full marks · Partial right idea, uneven execution · Fail a recognized slop tell is present. Items marked are critical — a miss there caps the whole grade regardless of total.

Color & Typography1 pass · 4 partial · 1 fail
PartialCustom palette, not the default violet / indigo blue primary (good) — but purple mesh + purple AI gradients ship alongside
PartialNear-greyscale + one accent used sparinglya second, decorative purple color family competes with the blue brand
PassColor assigned semantically, not decorativelydestructive / status / chart tokens are correct; mesh + glow are the exception
PartialDeliberate typeface + tight type scale (≤6 sizes)Inter + JetBrains Mono is fine; card-title and KPI sizes drift screen to screen
FailBody text passes WCAG AA contrast invisible "text-muted" separator; 50%-opacity muted rows; a panel with no dark-mode variant
FailNo gradient text or colored glowspurple ai-glow pulse, a gradient hero circle, a purple-glow voice button
Layout & Component Craft2 pass · 2 partial · 2 fail
PassNo single-side colored "accent rail" card bordersthe single most-mocked AI tell — MAPLE avoids it entirely
PassRestrained, consistent corner radii3 / 6 / 9px system; the blobby rounded-2xl look appears in just 1 of 157 files
FailNot the canned hero → 3-feature-cards templatelanding is exactly that + a 6-card grid + spec numbers dressed as "social proof"
PartialConsistent spacing; intentional hierarchy three hand-rolled steppers, five copies of formatCurrency, two KPI type-scales on one screen
PartialReal iconography, no emoji-as-icons Lucide throughout — but 🚗/📍 emoji in the comps panel, and Sparkles over-used as "AI did a thing"
FailNo decorative glassmorphismbackdrop-blur is baked into the base Card — every card in the app is frosted glass
Interaction & States1 pass · 2 partial · 2 fail
PartialDesigned hover + visible keyboard-focus ring the custom hover "elevate" system is excellent; focus rings missing on many custom buttons + icon tooltips
PartialAll 5 data states (skeleton / empty / error / success / disabled) skeletons + empty states are good; the AI chat has no error branch; edit-deal uses a bare spinner
PassEmpty states are actionable, never fabricatedfirst-run CTAs + a guard that refuses to show fake numbers when comps are missing
FailPurposeful motion — no bounce / pulse / marqueeinfinite purple glow, bouncing typing dots, "staggered" pulse dots that actually fire in unison
PartialLoading uses layout-matched skeletons, not spinners18 files use skeletons, 43 still use spinners — inconsistent
Data & Financial UI1 pass · 2 partial · 0 fail
PartialNumbers right-aligned + tabular figures; text left the summary card does it perfectly; the main comps table left-aligns raw numbers; tabular-nums used in 1 place
PartialCritical numbers carry an exact label + freshness / sourcesource badge yes; computed ARV confidence is never surfaced; no "as of" timestamp
PassConfidence shown honestly, not fabricated precisionthe manual-underwrite guard is exactly the right call for a valuation tool
Copy & Mobile1 pass · 0 partial · 1 fail
FailNo buzzwords / hype / "it's not X, it's Y" copy"institutional grade", "10X your underwriting speed", a stale "launching February 2026"
PassMobile: ≥44px tap targets, reachable nav 44px targets, safe-area insets, responsive type — genuinely handled

02 — The standard being applied

What people love, what they hate, and what reads as "slop"

Before judging MAPLE, we pinned down the bar. "AI slop" was Macquarie Dictionary's 2025 word of the year; designers claim they spot it in about three seconds. Here is what the research says separates crafted software from the template look.

What users love

  • Progressive disclosure — show what's needed now, defer the rest. Cuts "can't find feature X" tickets and speeds first tasks (NN/g).
  • Perceived speed > raw speed — layout-matched skeletons make an app feel faster and more trustworthy than a silent one.
  • All five states on every data view — loading, empty, error+retry, success, disabled. Missing states is the top polish failure.
  • Actionable empty states that tell you the next move — one case study moved week-one activation 23% → 38%.
  • Interaction density, not visual density — Linear/Stripe are visually sparse but every element responds to hover, focus, keyboard.

What users hate

  • Form & checkout friction — the #1 documented conversion killer (~70% abandonment; Baymard).
  • Accessibility failures dressed as taste — gray-on-gray text, removed focus rings, keyboard-dead forms, tiny tap targets.
  • Modal overload — stacking, focus-trapping, no clear dismiss.
  • Silence during async work — no loading feedback reads as broken.
  • Flattened hierarchy — uniform padding, radius, and card height everywhere, so nothing signals what matters.

The "AI slop" tells (2025–26)

  • Purple / violet gradients — the origin tell. Tailwind's creator publicly apologized for training AI to "turn every interface purple."
  • Untouched shadcn defaults + glassmorphism on everything; rounded-2xl + shadow-lg + Inter as the giveaway signature.
  • One-sided colored card borders — named repeatedly as the single most recognizable AI tell.
  • Canned page — centered hero → 3 identical feature cards → logo strip; emoji as section markers; everything centered.
  • Colored glows, pulsing dots, gradient text on metrics, and buzzword copy ("supercharge", "enterprise-grade").

What premium tools do (Linear / Stripe / Raycast)

  • Near-greyscale + one deliberate accent used sparingly and assigned semantically first.
  • Six real micro-states per interactive element, plus a designed, high-contrast focus ring — never the browser default.
  • Tabular figures, right-aligned numeric columns; decision-critical numbers pair an exact value with a freshness timestamp (the Bloomberg convention).
  • Skeletons matched to layout; empty states designed, not stubbed; motion on a defined curve, never bounce/elastic.
  • Restraint as confidence — let the product be the hero.

03 — The audit

Keep, change, remove, missing

Every item below is anchored to a real file and line in the codebase. Grouped by action so the build order is obvious.

KEEP — proven strengths, protect them CHANGE — mediocre or slop, fixable REMOVE — delete outright MISSING — a premium tool would have it

KEEP — the real foundation

KeepThe custom "elevate" hover systemdesign system

A layered ::after-overlay interaction layer driven by design tokens, wired cleanly into the button and badge primitives. Genuinely sophisticated — most shadcn clones never build this. It is the app's real design signature.

index.css:278–317button.tsx:9
KeepBlue brand, not slop-violetdesign system

Primary is a professional blue (hsl(210 85% 45%)) on every logo lockup and CTA — the deliberate choice that keeps the app off the AI-default palette. This is the single biggest thing protecting MAPLE's first impression.

index.css:39
KeepThe honest low-confidence guardquick-analyze

When comps are missing, the app refuses to render MAO / score and shows an amber explainer instead of a misleading number. This is exactly the judgment a valuation tool must have — never fabricate precision. Do not "simplify" it away.

quick-analyze.tsx:187–254
KeepThe result skeleton + the summary-row number patterncore screens

A real content-shaped loading skeleton (not a spinner), and the one row primitive that gets numbers right: label left, figure right-aligned in font-mono tabular-nums. This row is the template every other numeric row should copy.

quick-analyze.tsx:425–444DealSummaryTopCard.tsx:64–70
KeepFormula-breakdown tooltips + injection-safe AI renderingcomponents

Risk / tax metrics expose their exact formula and substituted numbers on hover — load-bearing information, not decoration; the strongest crafted feature in the app. The AI chat sanitizes with DOMPurify + an explicit tag allowlist, escaping before markdown conversion. Correct and secure.

RiskRewardCard.tsx:182–226AiChatMessage.tsx:34–39
KeepThe verify-email state machineauth

Four explicit states — loading / success / error / expired — each with its own icon, color, and recovery action. Most apps ship one generic screen; this is premium-grade. The auth flows overall (validation, resend cooldown, privacy-safe reset copy) are already at a good bar.

verify-email.tsx:103–213check-email.tsx:37–46

CHANGE — mediocre or slop, but fixable in place

ChangePurple mesh-gradient blobs on every unauthenticated page15+ pages

The mesh-background blobs are literally violet (hue 260° / 280°) — the exact purple-gradient-blob signature — and they clash with the blue brand. Used on landing, login, signup, onboarding, and more.

Fix → drop mesh-background for a flat bg-background (Linear/Stripe auth pages are flat), or retune all three blob hues into the brand's blue (205–225°) at ≤0.08 opacity. Two pages already use a flat background — make that the standard.

index.css:19–21App.tsx:220
ChangeGlassmorphism baked into the base Cardevery card

The base Card hard-codes the glass class, so every card gets a 16px backdrop-blur. That is the glassmorphism tell applied globally — and a real performance cost with dozens of blur layers on a data-dense page.

Fix → remove glass from the base Card; make it an opt-in variant="glass" used only on overlays / sheets where a blur behind actually reads.

card.tsx:12
ChangeThe whole AI surface defects to purpleAI assistant

The assistant is a wall of from-purple-600 to-indigo-600 gradients, purple avatars, and purple hover text — while the entire rest of the app is blue. It reads as a bolted-on ChatGPT clone with its own identity rather than part of MAPLE.

Fix → recolor every gradient to bg-primary / text-primary / bg-primary/10. Same code, and the assistant suddenly reads as MAPLE's own strategist.

AiAssistantSheet.tsx:239–340PostActionSuggestion.tsx:14–33
ChangeThe main comps table left-aligns its numbersdeal-detail

The fetched-comps table renders price, sqft, beds/baths, DOM, and distance as default left-aligned, non-tabular text — visibly worse than the better comps table directly below it, and this is the first table users see after generating comps.

Fix → add text-right font-mono tabular-nums to numeric cells, text-right to the headers, and render price through the shared formatCurrency.

deal-detail.tsx:1854–1919
ChangeEmoji as icons; Sparkles as "AI did a thing"comps · 6 screens

🚗 and 📍 emoji stand in for icons in the comps panel; the Sparkles glyph is sprinkled across six screens as a generic AI signifier — both classic slop tells in a financial UI.

Fix → swap the emoji for Lucide <Car> / <MapPin> (already imported); keep Sparkles on at most one primary AI entry point and use domain icons elsewhere.

CompFilterPanel.tsx:455,457
ChangeDuplicated logic that will drift6 files

Five copies of formatCurrency, two byte-identical score-color helpers, and three hand-rolled step indicators with different sizes. On a tool whose entire value is number consistency, this is a real divergence risk.

Fix → move formatters into the existing lib/numberFormat; extract one shared <Stepper>.

dashboard.tsx:63quick-analyze.tsx:59deal-detail.tsx:110
ChangeTruth-in-copy on the landing pagelanding · welcome

"Institutional grade", "the same tools institutional shops use", "10X your underwriting speed", and a stale "launching February 2026" — hype claims plus spec numbers relabeled as "social proof."

Fix → soften to defensible language ("institutional-style comps scoring", "analyze deals in minutes"), drive the date from config, and relabel the spec tiles honestly — never dress product specs as adoption.

landing.tsx:184,282,305

REMOVE — delete outright

RemoveThe purple AI-glow pulseAI button · voice

An infinite @keyframes ai-glow in literal rgba(147, 51, 234) — the exact violet the research calls out — pulsing forever on the floating AI button and the voice button. The textbook "AI thing must glow purple forever" tell.

Fix → delete the keyframes, the utility class, and both usages. A static shadow-md is enough.

index.css:435–446VoiceButton.tsx:37
RemoveTwo stacked, identical comps tablesdeal-detail

The Comps tab renders the same comp set twice — the poorly-formatted inline table (flagged above) and, ~40 lines below, the richer panel with $/sqft, score, condition, and selection. Users scroll past two tables of the same data. Highest-value single cut: it removes redundancy and the worst-formatted table at once.

Fix → delete the inline table (and its mobile twin); keep the richer panel.

deal-detail.tsx:1854–1919deal-detail.tsx:1807–1852
RemoveDead code and dead stubsonboarding · gauges

Two unused onboarding step components, an unused duplicate gauge, undefined animation-delay-150/300 classes (which is why the "staggered" pulse dots actually blink in unison), and a raw JSON tab shown to end users as a first-class view.

Fix → delete the unused files and no-op classes; move the JSON dump behind an admin flag.

RiskRewardGauge.tsxlanding.tsx:210,215DealSummaryCard.tsx:400–406
RemoveThe "Coming soon" video box on the first onboarding screenonboarding

New users hit a dashed-border "Video walkthrough / Coming soon" placeholder on the very first screen — a dead deliverable stub in the highest-visibility spot in the app.

Fix → remove the placeholder until a real video exists; keep the component for when a URL is supplied.

WelcomeStep.tsx:31–36

MISSING — what a premium tool of this type would have

MissingReduced-motion support — absent entirelywhole app

Zero prefers-reduced-motion rules anywhere, yet the app runs infinite glow, bouncing typing dots, and pulsing loops. A WCAG 2.2 (2.3.3) gap and a polish gap.

Fix → add one global @media (prefers-reduced-motion: reduce) block that neutralizes the looping animations.

MissingSurfaced ARV confidence + data freshnessdeal-detail · quick-analyze

The valuation confidence is computed but never shown — the "Choose ARV" card displays only a source badge. For a tool people cite to clients, decision-critical numbers should carry a Low / Med / High confidence badge and an "as of" timestamp.

Fix → badge confidence next to the anchor number; add a comp-set freshness date.

deal-detail.tsx:2135–2192
MissingSortable + sticky comps columnscomps

Neither comps table lets you sort by $/sqft, distance, DOM, or score — the four fields an underwriter actually sorts on — and scrolled table headers disappear.

Fix → clickable sort headers with an indicator; sticky top-0 on the header row.

CompFilterPanel.tsx:491
MissingKeyboard-focusable controls + AI chat error statecomponents

Icon-only tooltip triggers are bare SVGs with no tabindex — mouse-only. Many custom buttons lack a visible focus-visible ring. And if an AI message fails to send, the user gets silence with no retry.

Fix → wrap icon triggers in a focusable <button aria-label>; add focus-visible:ring-2 ring-primary globally; add an inline error/retry row to the chat.

InfoTooltip.tsx:14–15AiAssistantSheet.tsx:213–219
MissingDashboard KPI trends + tabular-nums everywheredashboard

KPI cards show a static number and a static icon; the monthly-trend data is already fetched but unused — a sparkline or "+N this month" delta is what separates an analytics dashboard from a counter. And only one component uses tabular-nums, so big figures jitter when they update.

Fix → add an inline sparkline / delta to the KPI cards; add tabular-nums to every mono figure class.

dashboard.tsx:521–573

04 — Sequenced

The build order

Ordered by impact-per-hour. The whole of P0 is subtractive — deleting or recoloring, no new design work — and moves the grade the most.

P0De-slop — half a day, mostly deletionbiggest grade jump
  1. Delete the purple ai-glow keyframes + both usages.
  2. Recolor the AI assistant from purple/indigo gradients to the blue brand tokens.
  3. Kill the purple mesh — flat bg-background, or retune blobs to blue at low opacity.
  4. Un-bake glassmorphism from the base Card; make it opt-in.
  5. Swap the 🚗 / 📍 emoji for Lucide icons; thin out Sparkles.
  6. Delete the duplicate comps table and the dead onboarding / gauge files.
P1Tighten the data surface — 1–2 dayscraft + trust
  1. Right-align + tabular-nums every numeric column and figure.
  2. Surface ARV confidence (Low/Med/High badge) + a comp-set freshness date.
  3. Consolidate the five formatCurrency copies and three steppers into shared code.
  4. Fix contrast — the invisible separator, the 50%-opacity rows, the dark-mode-less panel.
  5. Truth-in-copy pass on the landing page + stale date.
P2Premium polish — as time allowscatches up to Linear/Stripe bar
  1. Global prefers-reduced-motion block + visible focus-visible rings.
  2. Sortable + sticky comps table headers.
  3. Skeletons in the few places still using bare spinners.
  4. KPI sparklines / deltas on the dashboard.
  5. AI chat error / retry state + keyboard-focusable tooltip triggers.