Rams MCP · The full engine, now in your coding agent
onyx-dot-app on GitHub

onyx-dot-app/onyx

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

30 files reviewed·July 9, 2026

Elevated

Design risk in this codebase.

6issues
Critical & Serious

Top fix

Add a confirmation step before the destructive Reset action fires

See the fix

Verdict

Every screen here has a sound structural skeleton, loading, error, and success states all exist, but the moments users actually notice get abandoned: dead-end errors, unlabeled spinners, and a destructive reset button with zero confirmation. The riskiest gap is that danger-styled button submitting instantly, paired with error text that screen readers never hear at all.

Files Rams reviewed

web/src/app/admin/connectors/[connector]/pages/gdrive/GoogleDrivePage.tsx

web/src/app/admin/connectors/[connector]/pages/gmail/GmailPage.tsx

web/src/app/admin/connectors/[connector]/pages/Advanced.tsx

web/src/app/admin/connectors/[connector]/pages/ConnectorInput/FileInput.tsx

web/src/app/admin/connectors/[connector]/pages/ConnectorInput/NumberInput.tsx

web/src/app/admin/connectors/[connector]/pages/ConnectorInput/SelectInput.tsx

web/src/app/admin/connectors/[connector]/pages/DynamicConnectorCreationForm.tsx

web/src/app/admin/connectors/[connector]/pages/FieldRendering.tsx

web/src/app/admin/connectors/[connector]/pages/gdrive/Credential.tsx

web/src/app/admin/connectors/[connector]/pages/gmail/Credential.tsx

web/src/app/app/components/AppPopup.tsx

web/src/app/app/components/WelcomeMessage.tsx

web/src/app/app/components/files/images/FullImageModal.tsx

web/src/app/app/components/files/images/InMessageImage.tsx

web/src/app/app/components/tools/GeneratingImageDisplay.tsx

web/src/app/components/nrf/SettingsPanel.tsx

web/src/app/craft/components/AgentSwitcher.tsx

web/src/app/craft/components/BigButton.tsx

web/src/app/craft/components/BuildMessageList.tsx

web/src/app/craft/components/BuildWelcome.tsx

web/src/app/craft/components/CometEdge.tsx

web/src/app/craft/components/CompactionMarker.tsx

web/src/app/craft/components/ConnectDataBanner.tsx

web/src/app/craft/components/ContextRing.tsx

web/src/app/craft/components/CraftInputBar.tsx

web/src/app/craft/components/CraftingLoader.tsx

web/src/app/craft/components/IntroBackground.tsx

web/src/app/craft/components/IntroContent.tsx

web/src/app/craft/components/ModelPickerButton.tsx

web/src/app/craft/components/SandboxAsleepNotice.tsx

91/100

Accessibility

1 critical3 serious
Accessibility·web/src/app/admin/connectors/[connector]/pages/ConnectorInput/SelectInput.tsx:27Critical

Label's htmlFor points to an id the select never receives

The `<label htmlFor={name}>` targets an id, but Formik's `<Field as="select" name={name} ...>` only wires up `name`, `value`, `onChange`, and `onBlur`: it does not set `id` unless passed explicitly. The rendered `<select>` has no `id={name}`, so the label-to-control association never forms.

Why it matters

Screen readers won't announce the label when the select receives focus, and clicking the label text won't move focus into the control. Every instance of this component ships an unlabeled form field.

Fix

Pass an explicit id to Field that matches the label's htmlFor.

<Field
  as="select"
  name={name}
  className="w-full p-2 border border-border-03 rounded-08 bg-transparent text-text-04 focus:ring-2 focus:ring-lighter-agent focus:border-lighter-agent focus:outline-hidden"
>
<Field
  as="select"
  id={name}
  name={name}
  className="w-full p-2 border border-border-03 rounded-08 bg-transparent text-text-04 focus:ring-2 focus:ring-lighter-agent focus:border-lighter-agent focus:outline-hidden"
>
Accessibility·web/src/app/admin/connectors/[connector]/pages/gmail/GmailPage.tsx:67Serious

Retry button in the fix above needs an explicit type

The suggested `<button onClick={handleRefresh}>Try again</button>` inside `ErrorCallout` has no `type` attribute, so it defaults to `type="submit"`.

Why it matters

If this button ever renders inside a `<form>`, clicking 'Try again' submits the form unintentionally, causing unrelated side effects the user didn't ask for.

Fix

Set type='button' on any button that isn't intentionally submitting a form.

<button onClick={handleRefresh}>Try again</button>
<button type="button" onClick={handleRefresh}>Try again</button>
Accessibility·web/src/app/admin/connectors/[connector]/pages/ConnectorInput/FileInput.tsx:55Serious

Validation error appears with no live region so screen readers never announce it

When `meta.touched && meta.error` becomes true, `{meta.error}` is inserted into a plain `<div>` with no `role="alert"` or `aria-live`.

Why it matters

Sighted users see the red error text appear under the file field, but screen reader users get no announcement that validation failed, so they submit again with no idea what went wrong.

Fix

Add role="alert" (or aria-live="polite") to the conditionally rendered error so assistive tech announces it when it mounts.

<div className="text-red-500 text-sm mt-1">{meta.error}</div>
<div role="alert" className="text-error-500 text-sm mt-1">{meta.error}</div>
Accessibility·web/src/app/admin/connectors/[connector]/pages/ConnectorInput/NumberInput.tsx:39Serious

Error text below the input has no programmatic link to it for screen readers

The `<input>` has no `id` or `aria-describedby`, and the `<ErrorMessage>` div rendered below it has no matching `id`. The two elements are only related visually by proximity.

Why it matters

Screen reader users focusing the number input never hear the associated error text, so they get no explanation of why the value is invalid or what to fix, unlike sighted users who see the pink border and error line together.

Fix

Give the input an id and point `aria-describedby` at a matching id on the error element so assistive tech announces the error with the field.

<input
  {...field}
  type="number"
  min="-1"
  onChange={handleChange}
  value={
    field.value === undefined || field.value === null ? "" : field.value
  }
<input
  {...field}
  id={name}
  type="number"
  min="-1"
  onChange={handleChange}
  aria-describedby={meta.error ? `${name}-error` : undefined}
  value={
    field.value === undefined || field.value === null ? "" : field.value
  }
95/100

UX

1 critical1 serious
UX·web/src/app/admin/connectors/[connector]/pages/Advanced.tsx:41Critical

Danger-styled Reset button submits instantly with no confirmation step

The `Button variant="danger"` labeled "Reset" has `type="submit"` and no confirmation dialog or intercepting handler. Clicking it fires the form submit directly.

Why it matters

A destructive, danger-styled action wired straight to form submission means one accidental click triggers whatever the reset does (likely clearing pruning/refresh config) with no way to back out.

Fix

Wrap destructive submits in a confirmation step before the mutating action runs.

<Button variant="danger" icon={SvgTrash} type="submit">
  Reset
</Button>
<Button
  variant="danger"
  icon={SvgTrash}
  type="button"
  onClick={() => {
    if (window.confirm("Reset all advanced configuration for this connector?")) {
      formikSubmitReset();
    }
  }}
>
  Reset
</Button>
UX·web/src/app/admin/connectors/[connector]/pages/gdrive/GoogleDrivePage.tsx:58Serious

Failed credential loads dead-end users with no retry option

Both error branches render `<ErrorCallout errorTitle="Failed to load credentials." />` and `<ErrorCallout errorTitle="Failed to load Google Drive credentials." />` with no action attached. The component already has `refreshCredentials` in scope from `usePublicCredentials`, but neither error state offers it.

Why it matters

A user who hits either error state has no way to recover except a full page reload, so setup friction compounds right at the connector configuration entry point.

Fix

Pass a retry action into the error state so users can recover in place.

if (credentialsError || !credentialsData) {
  return <ErrorCallout errorTitle="Failed to load credentials." />;
}
if (credentialsError || !credentialsData) {
  return (
    <ErrorCallout
      errorTitle="Failed to load credentials."
      errorMsg={<button onClick={refreshCredentials} className="underline">Try again</button>}
    />
  );
}

Typography

No issues found

Color

No issues found

Spacing

No issues found

Components

No issues found

Motion

No issues found

Craft

No issues found

Working well

  • The `isZip` and `multiple` props are consistently threaded into both `setSelectedFiles` (deciding array vs single value) and the `multiple`/`accept` props passed to `FileUpload`: the two places that need to agree on 'is this a single-zip upload' actually do agree, which avoids a common bug where the accept filter and the value-shape logic drift apart.
  • Deriving `selectedFiles` with `Array.isArray(field.value) ? field.value : field.value ? [field.value] : []` handles the single-file, multi-file, and empty-value cases in one clear expression instead of scattering null checks through the render: good defensive normalization at the boundary between Formik's value shape and the FileUpload prop contract.
  • Handling the two credential sources (`usePublicCredentials` and `useGoogleCredentials`) as separate loading and error checks before ever reaching the main render is the right structure: it guarantees `DriveAuthSection` only ever mounts once both dependencies are confirmed present, avoiding null-reference bugs downstream.
  • The disabled and invalid states use visually distinct treatments (`disabled:bg-background-50` with muted text vs a bordered/colored invalid state) rather than a single dimmed look for both, so users can tell 'can't edit this' apart from 'fix this' at a glance.

Score your own repo with Rams.

Free on public repos. Rams reviews every pull request for accessibility, color, type, spacing, components, motion, UX, and craft, and posts inline fixes.