Rams MCP · The full engine, now in your coding agent
antiwork on GitHub

antiwork/gumroad

Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.

30 files reviewed·July 23, 2026

View on GitHub

Elevated

Design risk in this codebase.

6issues
Critical & Serious · top 5 shown below

Top fix

Add a visible label to the email input, don't rely on placeholder text

See the fix

Verdict

Layout code is disciplined and component-driven, but the one form users actually fill out is the weak point. An unlabeled, mistyped email input with no error feedback is the single biggest risk to both accessibility and completion.

Files Rams reviewed

app/javascript/pages/HelpCenter/Layout.tsx

app/javascript/pages/UrlRedirects/ConfirmPage.tsx

app/javascript/pages/UrlRedirects/DownloadPage.tsx

app/javascript/pages/UrlRedirects/UnavailablePageLayout.tsx

app/javascript/components/AffiliatedPage.tsx

app/javascript/components/Analytics/AnalyticsLayout.tsx

app/javascript/components/ApiDocumentation/Endpoints/PublicProductPage.tsx

app/javascript/components/Authentication/Layout.tsx

app/javascript/components/BundleEdit/Layout.tsx

app/javascript/components/CheckoutDashboard/Layout.tsx

app/javascript/components/Collaborators/Layout.tsx

app/javascript/components/CollabsPage.tsx

app/javascript/components/Developer/Layout.tsx

app/javascript/components/Discover/Layout.tsx

app/javascript/components/Download/PageListLayout.tsx

app/javascript/components/EmailAction/Layout.tsx

app/javascript/components/EmailsPage/Layout.tsx

app/javascript/components/Library/Layout.tsx

app/javascript/components/Product/Layout.tsx

app/javascript/components/ProductsLayout.tsx

app/javascript/components/ProductsPage.tsx

app/javascript/components/Profile/EditPage.tsx

app/javascript/components/Profile/Layout.tsx

app/javascript/components/Public/LookupLayout.tsx

app/javascript/components/Settings/Layout.tsx

app/javascript/components/WorkflowsPage/Layout.tsx

app/javascript/components/server-components/ProductEditPage.tsx

app/javascript/pages/Admin/ActionCallDashboard/Index.tsx

app/javascript/pages/Admin/ApiTokens/Index.tsx

app/javascript/pages/Admin/Base/Index.tsx

95/100

Accessibility

1 critical1 serious
AccessibilityCritical

app/javascript/pages/UrlRedirects/ConfirmPage.tsx:97

Email input has no label, only a placeholder that vanishes on focus

The `Input` at line 97-103 renders with only `placeholder="Email address"` and no `<label>`, `aria-label`, or `aria-labelledby`. Once the user starts typing, the field's name disappears for sighted users and was never announced to screen readers in the first place.

Why it matters

Screen reader users hear an unnamed text field on the page whose entire purpose is collecting this one email address, and sighted users lose the field name the moment they start typing, so a distracted user re-checking the form has no name to confirm against.

Fix

Give every form input a persistent, programmatically associated label and keep the placeholder for format hints only.

<Input
  type="text"
  name="email"
  placeholder="Email address"
  onChange={(e) => setData("email", e.target.value)}
  defaultValue={confirmation_info.email ?? ""}
/>
<label htmlFor="email" className="sr-only">
  Email address
</label>
<Input
  id="email"
  type="email"
  name="email"
  placeholder="name@example.com"
  autoComplete="email"
  onChange={(e) => setData("email", e.target.value)}
  defaultValue={confirmation_info.email ?? ""}
/>
AccessibilitySerious

app/javascript/pages/HelpCenter/Layout.tsx:20

Search link's duplicate title and aria-label risk a double screen reader announcement

The icon-only search control carries both `aria-label="Search"` and `title="Search"` with identical text: - `<Link href={Routes.help_center_root_path()} aria-label="Search" title="Search">` Some screen reader and browser combinations (notably Safari + VoiceOver) announce the `title` attribute as a tooltip in addition to the accessible name, so the control is read out twice.

Why it matters

Screen reader users hear "Search, Search" on an icon-only control, adding noise to every page that shows the search action and making the control feel unpolished for assistive tech users specifically.

Fix

Drop the redundant title attribute and let aria-label be the single source of the accessible name for icon-only controls.

<Link href={Routes.help_center_root_path()} aria-label="Search" title="Search">
  <Search className="size-5" />
</Link>
<Link href={Routes.help_center_root_path()} aria-label="Search">
  <Search className="size-5" aria-hidden="true" />
</Link>
96/100

UX

2 serious
UXSerious

app/javascript/pages/UrlRedirects/ConfirmPage.tsx:60

Form has no inline validation or error feedback if the confirmation fails

`useForm` (line 60) only destructures `data, setData, post, processing`: `errors` is never pulled out or rendered anywhere near the `Input` or `Button`. If the submitted email doesn't match, or is malformed, there's no visible path back to the user.

Why it matters

A rejected submission gives the user zero feedback about what went wrong, so they resubmit the same value or abandon the flow with no idea why access failed.

Fix

Destructure errors from useForm and render the relevant message next to the field it belongs to.

const { data, setData, post, processing } = useForm({
const { data, setData, post, processing, errors } = useForm({
UXSerious

app/javascript/pages/UrlRedirects/ConfirmPage.tsx:98

Email field uses type="text" with no autocomplete, breaking mobile keyboards and autofill

Line 98 sets `type="text"` instead of `type="email"`, and the input has no `autoComplete` attribute at all.

Why it matters

Mobile users get the generic keyboard instead of the @-symbol email layout, browsers skip native email format validation, and password managers/autofill have nothing to match against: on a page whose sole job is capturing a returning customer's email, that friction directly works against the page's purpose.

Fix

Set type="email" and autoComplete="email" on email inputs so the platform's keyboard, validation, and autofill all engage.

<Input
  type="text"
  name="email"
<Input
  type="email"
  name="email"
  autoComplete="email"

Get this score on every PR.

Rams reviews each pull request on your repo and posts inline one-click fixes — about a minute per review.

Install Rams free
98/100

Components

1 serious
Design SystemSerious

app/javascript/pages/UrlRedirects/ConfirmPage.tsx:89

Form width set with an inline style instead of a Tailwind token

The `<form>` at line 85-91 carries `style={{ width: "calc(min(428px, 100%))" }}` alongside its `className="flex flex-col gap-4"`: a hardcoded pixel value living outside the spacing/width scale the rest of the file uses.

Why it matters

A one-off inline width bypasses the design system's responsive scale, so this form's max width can silently drift from every other form on the site and can't be themed or adjusted from one place.

Fix

Replace hardcoded inline width with a Tailwind max-width utility from the existing scale.

className="flex flex-col gap-4"
style={{ width: "calc(min(428px, 100%))" }}
className="flex w-full max-w-md flex-col gap-4"

Typography

No issues found

Color

No issues found

Spacing

No issues found

Motion

No issues found

Craft

No issues found

Working well

  • The polling lifecycle is scoped correctly: audioDurationsPoll only starts when hasUnprocessedAudio is true and mediaLocationsPoll only starts when hasMediaFiles is true, with matching stop() calls in the else branch. This means users on plain digital-download pages with no rich media never pay the cost of a background timer running in the tab.
  • The is_gift conditional (lines 76-83) rewrites both the heading and the supporting paragraph together, so a gift recipient reads copy about "the email this gift was sent to" instead of generic purchase language: matching the message to the actual scenario reduces confusion for a first-time gift recipient landing on an unfamiliar page.
  • Wrapping the page in a single min-h-screen flex column and delegating all rendering to <WithContent> keeps this file as pure composition and data plumbing. That separation is what makes the actual visual design (hierarchy, spacing, CTA treatment) reviewable in one place instead of scattered across a page-level component.
  • The search control pairs a bare icon (`<Search className="size-5" />`) with `aria-label="Search"` on the Link instead of leaving the button silent to assistive tech. This is the correct pattern for icon-only affordances: the visual icon communicates to sighted users, the label communicates to everyone else.

Scored July 23, 2026 with Rams Engine v0.0.2 · Engine changelog

Score your own repo.

Free on public repos, no account. The same engine that scored this page reads your UI code and mints a score page like this one.

Public repos only. The full engine reviews the UI code and mints a public score page — we email you the link too. Already-scored repos open instantly.

Or get this review on every PR.