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

novuhq/novu

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

30 files reviewed·July 29, 2026

View on GitHub

Elevated

Design risk in this codebase.

4issues
Critical & Serious

Verdict

This file mixes a genuine accessibility gap with a handful of small styling debts. The keyboard-inaccessible preview toggle is the one that actually blocks a class of users and should get fixed first; the hardcoded hex and scattered arbitrary pixel values are lower-stakes drift that make the block's visual language harder to keep consistent over time. The shadow DOM isolation and click-outside handling show the author thought carefully about the harder integration problems, which makes the smaller polish gaps worth closing too.

Files Rams reviewed

packages/novu/src/commands/init/templates/app-react-email/ts/app/components/NotificationToast/Notifications.module.css

playground/nextjs/src/app/agents-mcp/components/flow-diagram.tsx

apps/dashboard/src/components/maily/views/for-view.tsx

apps/dashboard/src/components/maily/views/html-view.tsx

apps/dashboard/src/components/maily/views/maily-variables-list-view.tsx

apps/dashboard/src/components/maily/views/variable-view.tsx

apps/dashboard/src/pages/access-denied-page.tsx

apps/dashboard/src/pages/agent-slack-setup-page.tsx

apps/dashboard/src/pages/agent-telegram-mobile-setup-page.tsx

apps/dashboard/src/pages/agent-whatsapp-signup-page.tsx

apps/dashboard/src/pages/edit-layout.tsx

apps/dashboard/src/pages/inbox-embed-page.tsx

apps/dashboard/src/pages/inbox-embed-success-page.tsx

apps/dashboard/src/pages/inbox-usecase-page.tsx

apps/dashboard/src/pages/integration-store-telegram-mobile-setup-page.tsx

apps/dashboard/src/pages/integrations-list-page.tsx

apps/dashboard/src/pages/server-error-page.tsx

apps/dashboard/src/pages/vercel-integration-page.tsx

apps/dashboard/src/pages/webhooks-page.tsx

apps/dashboard/src/pages/welcome-page.tsx

packages/novu/src/commands/init/templates/app-agent-ai-sdk/ts/app/page.tsx

packages/novu/src/commands/init/templates/app-agent-langchain/ts/app/page.tsx

packages/novu/src/commands/init/templates/app-agent/ts/app/page.tsx

playground/nextjs/src/app/agent-toolkit/page.tsx

playground/nextjs/src/app/novu-agent/page.tsx

apps/dashboard/src/components/agents/agent-integration-guides/agent-integration-guide-layout.tsx

apps/dashboard/src/components/agents/public-token-page.tsx

apps/dashboard/src/components/dashboard-layout.tsx

apps/dashboard/src/components/translations/translation-onboarding-page.tsx

apps/dashboard/src/components/workflow-editor/steps/conditions/edit-step-conditions-layout.tsx

96/100

Spacing

2 serious
Design SystemSerious

apps/dashboard/src/components/maily/views/html-view.tsx:110

Html-tag badge stacks five arbitrary offsets in one class list

The floating "html" badge div uses `right-[-10px] top-[-3px] ... gap-[2px] ... py-[2px]`, four arbitrary pixel values plus the earlier arbitrary min-height, all hand-tuned rather than pulled from a spacing scale.

Why it matters

Every one-off pixel value here is a value nobody else can reuse, so the next badge or overlay in this codebase gets its own hand-tuned offsets instead of a shared pattern, and small inconsistencies in positioning accumulate across the editor.

Fix

Replace the arbitrary negative offsets and gaps with the nearest scale steps (e.g. -right-2.5, -top-1, gap-0.5, py-0.5) so the badge positioning stays on the same scale as the rest of the UI.

<div className="border-soft-100 absolute right-[-10px] top-[-3px] hidden cursor-grab items-center justify-center gap-[2px] rounded border bg-white px-1 py-[2px] group-hover:flex">
<div className="border-soft-100 absolute -right-2.5 -top-1 hidden cursor-grab items-center justify-center gap-0.5 rounded border bg-white px-1 py-0.5 group-hover:flex">
Design SystemSerious

apps/dashboard/src/components/maily/views/html-view.tsx:97

Preview wrapper's min-height is an arbitrary pixel value

The content wrapper div sets `min-h-[42px]`, a one-off pixel value with no relation to the rest of the spacing/sizing scale used in this component.

Why it matters

Arbitrary pixel heights like this can't be reused or reasoned about alongside other block heights in the editor, so future blocks either copy the magic number or introduce yet another one-off, and the editor's vertical rhythm drifts block by block.

Fix

Replace the arbitrary pixel height with a scale token (e.g. min-h-10/min-h-11) or derive it from existing spacing utilities used elsewhere in the editor.

'-mx-2 min-h-[42px] rounded-md border px-2',
'-mx-2 min-h-10 rounded-md border px-2',
97/100

Accessibility

1 critical
AccessibilityCritical

apps/dashboard/src/components/maily/views/html-view.tsx:108

Clickable preview div has no keyboard path into edit mode

The PreviewView wrapper `<div className="group relative cursor-pointer" onClick={onClick}>` is the only way to switch this HTML block from preview into code-editing mode, but it carries no role, no tabIndex, and no onKeyDown handler. A mouse click works; a keyboard user tabbing through the editor has no way to focus this div or trigger onClick from it.

Why it matters

Keyboard-only users and screen reader users cannot enter edit mode on this block at all, which locks them out of a core editing action in the email editor.

Fix

Give the clickable div button semantics: add role="button", tabIndex={0}, and an onKeyDown handler that fires onClick on Enter/Space, or replace the div with a real <button>.

<div className="group relative cursor-pointer" onClick={onClick}>
<div
  role="button"
  tabIndex={0}
  className="group relative cursor-pointer"
  onClick={onClick}
  onKeyDown={(e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      onClick?.();
    }
  }}
>

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

Color

1 serious
Design SystemSerious

apps/dashboard/src/components/maily/views/html-view.tsx:98

Hover border uses raw hex instead of the border token

The content wrapper's hover state sets `group-hover:border-[#E4E4E7]`, a raw hex value, while the sibling html-tag badge just below it uses `border-soft-100` for the same kind of border. Two different sourcing methods for what should be one border color.

Why it matters

When the border token changes (theme update, dark mode), this hardcoded hex won't move with it, so the hover border silently drifts out of sync with the rest of the editor chrome.

Fix

Replace the hardcoded hex with the existing border token used elsewhere in this same component.

'border-transparent group-hover:border-[#E4E4E7]',
'border-transparent group-hover:border-soft-100',

Typography

No issues found

Components

No issues found

Motion

No issues found

UX

No issues found

Craft

No issues found

Working well

  • Using shadow DOM (node.attachShadow) to isolate injected email HTML styles from the editor's own CSS is a smart, deliberate containment technique that prevents email markup from leaking styles into the surrounding editor chrome.
  • The click-outside handler explicitly excludes tippy-box and radix popper wrappers before deciding a click is 'outside,' which shows real care about not closing the code view out from under an open bubble menu.
  • The preview node correctly sets contentEditable={false} on the shadow-DOM host, so the injected HTML never gets swallowed into the editable node content and corrupted by the rich-text editor.

Scored July 29, 2026 with Rams Engine v0.0.3 · Engine changelog

This page is an automated design review of novuhq/novu’s UI code: 30 files read against 258 versioned rules covering accessibility, color, typography, spacing, components, UX, motion, and craft. The score is out of 100; any confirmed critical issue caps it at 59.

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