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

documenso/documenso

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

30 files reviewed·August 1, 2026

View on GitHub

Elevated

Design risk in this codebase.

8issues
Critical, Serious & Moderate · top 6 shown below

Top fix

Add a reduced-motion escape to the infinite signing spinner

See the fix

Verdict

A signing flow with good bones and unfinished edges: creation and auth screens fall apart the moment something fails or takes time. The infinite spinner with no reduced-motion exit is the sharpest risk, since it strands sensitive users mid-signature with no way out.

Files Rams reviewed

apps/remix/app/components/general/direct-template/direct-template-page.tsx

apps/remix/app/components/general/direct-template/direct-template-signing-auth-page.tsx

apps/remix/app/components/general/document-signing/csc-recipient-blocked-page.tsx

apps/remix/app/components/general/document-signing/csc-recipient-signing-in-progress-page.tsx

apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx

apps/remix/app/components/general/envelope-editor/envelope-editor-preview-page.tsx

apps/remix/app/components/general/generic-error-layout.tsx

apps/docs/src/app/(home)/page.tsx

apps/docs/src/app/docs/[[...slug]]/page.tsx

apps/docs/src/app/docs/layout.tsx

apps/docs/src/app/layout.tsx

apps/remix/app/components/general/direct-template/direct-template-invalid-page.tsx

apps/remix/app/components/dialogs/account-delete-dialog.tsx

apps/remix/app/components/dialogs/admin-document-delete-dialog.tsx

apps/remix/app/components/dialogs/admin-organisation-create-dialog.tsx

apps/remix/app/components/dialogs/admin-organisation-delete-dialog.tsx

apps/remix/app/components/dialogs/admin-organisation-member-delete-dialog.tsx

apps/remix/app/components/dialogs/admin-organisation-member-update-dialog.tsx

apps/remix/app/components/dialogs/admin-organisation-sync-subscription-dialog.tsx

apps/remix/app/components/dialogs/admin-swap-subscription-dialog.tsx

apps/remix/app/components/dialogs/admin-team-member-delete-dialog.tsx

apps/remix/app/components/dialogs/admin-user-create-dialog.tsx

apps/remix/app/components/dialogs/admin-user-delete-dialog.tsx

apps/remix/app/components/dialogs/admin-user-disable-dialog.tsx

apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx

apps/remix/app/components/dialogs/admin-user-reset-two-factor-dialog.tsx

apps/remix/app/components/dialogs/ai-features-enable-dialog.tsx

apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx

apps/remix/app/components/dialogs/ai-recipient-detection-dialog.tsx

apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx

94/100

UX

3 serious
UXSerious

apps/remix/app/components/general/direct-template/direct-template-page.tsx:121

Failed document creation shows an error toast with no way forward

The catch block for `createDocumentFromDirectTemplate` shows a destructive toast with `title`/`description` from `getDirectTemplateErrorMessage`, then rethrows. There is no retry button or recovery action attached to the toast itself.

Why it matters

A signer who just finished filling out every field sees an error and has no explicit next step; they have to guess that re-clicking the original submit button is safe rather than being offered a clear retry.

Fix

Pair destructive error toasts with an explicit action the user can take, such as a retry button, rather than a message alone.

toast({
  title: _(errorMessage.title),
  description: _(errorMessage.description),
  variant: 'destructive',
});
toast({
  title: _(errorMessage.title),
  description: _(errorMessage.description),
  variant: 'destructive',
  action: (
    <ToastAction altText="Retry" onClick={() => onSignDirectTemplateSubmit(fields, nextSigner)}>
      <Trans>Retry</Trans>
    </ToastAction>
  ),
});
UXSerious

apps/remix/app/components/general/direct-template/direct-template-signing-auth-page.tsx:45

"Login" button typed submit with no form to submit into

The "Login" `Button` on the direct template auth page has `type="submit"` and its click behavior is entirely driven by an `onClick` handler calling `handleChangeAccount()`. There's no `<form>` element anywhere in this component.

Why it matters

A submit-typed button outside a form does nothing extra today, but if this component is ever nested inside a form (a common refactor), it will trigger an unrelated form submission the author never intended.

Fix

Type buttons as type="button" when their action is handled entirely by onClick and there is no form to submit.

<Button
  className="mt-4 w-full"
  type="submit"
  onClick={async () => handleChangeAccount()}
  loading={isSigningOut}
>
  <Trans>Login</Trans>
</Button>
<Button
  className="mt-4 w-full"
  type="button"
  onClick={async () => handleChangeAccount()}
  loading={isSigningOut}
>
  <Trans>Login</Trans>
</Button>
UXSerious

apps/remix/app/components/general/direct-template/direct-template-page.tsx:86

No submitting state on document creation lets signers double-submit

`onSignDirectTemplateSubmit` calls `createDocumentFromDirectTemplate` directly with no loading flag tracked around the mutation. Whatever button triggers this handler has no way to disable itself or show a busy state while the request is in flight.

Why it matters

A signer who clicks again during the round trip can trigger a duplicate document creation, which is exactly the kind of state corruption a signing flow can't afford.

Fix

Track an isSubmitting flag around async mutations and disable the triggering control until it resolves.

const onSignDirectTemplateSubmit = async (
  fields: DirectTemplateLocalField[],
  nextSigner?: { name: string; email: string },
) => {
  try {
    let directTemplateExternalId = searchParams?.get('externalId') || undefined;
const [isSigningSubmitting, setIsSigningSubmitting] = useState(false);

const onSignDirectTemplateSubmit = async (
  fields: DirectTemplateLocalField[],
  nextSigner?: { name: string; email: string },
) => {
  setIsSigningSubmitting(true);
  try {
    let directTemplateExternalId = searchParams?.get('externalId') || undefined;
96/100

Spacing

2 serious
SpacingSerious

apps/remix/app/components/general/direct-template/direct-template-page.tsx:149

Arbitrary calc height on the signing form container has no token backing

The `DocumentFlowFormContainer` on the direct template signing page uses `className="lg:h-[calc(100vh-6rem)]"`, a one-off bracket value with a magic 6rem offset that isn't tied to any header/nav measurement in the file.

Why it matters

If the surrounding chrome (header, banner) ever changes height, this hardcoded offset silently drifts out of sync and either clips the form or leaves a dead gap, and nothing in the codebase enforces the relationship.

Fix

Derive layout offsets from a shared token or CSS variable rather than a hand-picked rem value.

<DocumentFlowFormContainer className="lg:h-[calc(100vh-6rem)]" onSubmit={(e) => e.preventDefault()}>
<DocumentFlowFormContainer className="lg:h-[calc(100vh-var(--header-height,6rem))]" onSubmit={(e) => e.preventDefault()}>
SpacingSerious

apps/remix/app/components/general/direct-template/direct-template-signing-auth-page.tsx:31

Auth-required screen locks to an arbitrary 70vh instead of content height

The root container on the direct template auth page is `h-[70vh]`, a one-off viewport-relative height with no relationship to its content (a heading, a line of text, and one button).

Why it matters

On short viewports (landscape mobile, small laptop windows with browser chrome) 70vh can crop content or force scrolling for a three-line message; on tall viewports it leaves an oversized empty page for no reason.

Fix

Size containers to their content with min-height rather than a fixed viewport-percentage height.

<div className="mx-auto flex h-[70vh] w-full max-w-md flex-col items-center justify-center">
<div className="mx-auto flex min-h-[50vh] w-full max-w-md flex-col items-center justify-center">

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
97/100

Motion

1 critical
MotionCritical

apps/remix/app/components/general/document-signing/csc-recipient-signing-in-progress-page.tsx:87

Infinite signing spinner has no reduced-motion escape for sensitive users

The `Loader2Icon` shown while "Applying your signature" is in progress uses `animate-spin` with no `motion-reduce` guard. It spins continuously for the entire signing wait, however long that takes, with no way to opt out.

Why it matters

Vestibular-sensitive users get no way to disable this motion, and unlike a one-shot transition this one runs indefinitely on a page they're stuck on until the signature finalizes.

Fix

Respect prefers-reduced-motion on any infinite animation by pairing animate-spin with motion-reduce:animate-none.

<Loader2Icon className="h-12 w-12 animate-spin text-primary" />
<Loader2Icon className="h-12 w-12 animate-spin text-primary motion-reduce:animate-none" />

Accessibility

No issues found

Typography

No issues found

Color

No issues found

Components

No issues found

Craft

No issues found

Working well

  • Wrapping the mutation in try/catch with AppError.parseError and a mapped user-facing toast message is a solid recoverable-error pattern rather than a raw console error.
  • The fire-once guard uses a ref with a documented comment explaining exactly why useState would double-fire under StrictMode: that's a deliberate, well-reasoned choice.
  • Branching the heading and body copy per AppErrorCode instead of one generic message gives recipients specific, actionable context for each failure mode.
  • The empty state for a missing document uses an icon plus two lines of explanatory text instead of a blank void, giving users a clear next step.

Scored August 1, 2026 with Rams Engine v0.0.3 · Engine changelog
First scored July 9, 2026: 59/100. This rescore on v0.0.3: 59/100.

This page is an automated design review of documenso/documenso’s UI code: 30 files read against 291 versioned rules covering accessibility, color, typography, spacing, components, UX, motion, and craft. The score is out of 100; confirmed criticals cap it — one at 59, two at 49, three or more at 39.

More design scores

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 a design review on every pull requestInstall Rams