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

toeverything/affine

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

Convert clickable tree row div to a real button element

See the fix

Verdict

Radix primitives sit unused right beside hand-rolled divs that quietly drop keyboard access and screen reader support. The underlying architecture is sound, but every interactive surface skips the accessibility basics its own dependencies would have handled for free.

Files Rams reviewed

packages/frontend/core/src/desktop/pages/theme-editor/components/color-cell.tsx

packages/frontend/core/src/desktop/pages/theme-editor/components/tree-node.tsx

packages/frontend/core/src/desktop/pages/theme-editor/components/variable-list.tsx

packages/frontend/core/src/desktop/pages/workspace/all-page/all-page.tsx

packages/frontend/core/src/desktop/pages/workspace/layouts/workspace-layout.tsx

packages/frontend/core/src/desktop/pages/workspace/share/share-page.tsx

packages/frontend/core/src/desktop/pages/workspace/trash-page.tsx

packages/frontend/core/src/mobile/pages/workspace/layout.tsx

packages/frontend/component/src/components/affine-other-page-layout/layout.tsx

packages/frontend/component/src/components/auth-components/change-email-page.tsx

packages/frontend/component/src/components/auth-components/change-password-page.tsx

packages/frontend/component/src/components/auth-components/onboarding-page.tsx

packages/frontend/component/src/components/auth-components/set-password-page.tsx

packages/frontend/component/src/components/auth-components/sign-up-page.tsx

packages/frontend/component/src/components/member-components/accept-invite-page.tsx

packages/frontend/component/src/components/member-components/failed-to-send-page.tsx

packages/frontend/component/src/components/member-components/join-failed-page.tsx

packages/frontend/component/src/components/member-components/request-to-join-page.tsx

packages/frontend/component/src/components/member-components/sent-request-page.tsx

packages/frontend/component/src/components/not-found-page/not-found-page.tsx

packages/frontend/core/src/components/page-list/docs/select-page.tsx

packages/frontend/core/src/components/page-list/selector/selector-layout.tsx

packages/frontend/core/src/desktop/pages/theme-editor/components/empty.tsx

packages/frontend/core/src/desktop/pages/theme-editor/components/simple-color-picker.tsx

packages/frontend/core/src/desktop/pages/theme-editor/components/string-cell.tsx

packages/frontend/core/src/desktop/pages/404/index.tsx

packages/frontend/core/src/desktop/pages/ai-upgrade-success/index.tsx

packages/frontend/core/src/desktop/pages/import-clipper/index.tsx

packages/frontend/core/src/desktop/pages/import-template/index.tsx

packages/frontend/core/src/desktop/pages/index/index.tsx

92/100

Accessibility

2 critical1 serious
Accessibility·packages/frontend/core/src/desktop/pages/theme-editor/components/tree-node.tsx:31Critical

Tree row is a clickable div, so keyboard users can't navigate or select themes

The row wrapping the icon and `{node.label}` is a `<div onClick={onClick}>` with no `role`, `tabIndex`, or key handler. This is the only way to select a theme node or expand a branch in this tree.

Why it matters

Keyboard-only and screen-reader users cannot Tab to a theme node, expand a branch, or select a variable set at all: the entire theme editor tree becomes unusable without a mouse.

Fix

Use a native `<button>` for the clickable row so it gets focus, Enter/Space activation, and a role for free.

<div
  data-checked={node === checked}
  data-active={isActive?.(node)}
  data-customized={isCustomized?.(node)}
  className={styles.treeNode}
  onClick={onClick}
>
<button
  type="button"
  data-checked={node === checked}
  data-active={isActive?.(node)}
  data-customized={isCustomized?.(node)}
  aria-expanded={node.children ? open : undefined}
  className={styles.treeNode}
  onClick={onClick}
>
Accessibility·packages/frontend/core/src/desktop/pages/theme-editor/components/color-cell.tsx:66Critical

Icon-only menu trigger has no accessible name for screen readers

The `<IconButton size="14" icon={<MoreHorizontalIcon />} />` that opens the color menu has no `aria-label`, `title`, or visible text.

Why it matters

Screen reader users hear only "button" with no indication this opens color editing options, so they can't discover or operate the color-override menu at all.

Fix

Give every icon-only control an aria-label that names its action.

<IconButton size="14" icon={<MoreHorizontalIcon />} />
<IconButton size="14" icon={<MoreHorizontalIcon />} aria-label="Edit color" />
Accessibility·packages/frontend/core/src/desktop/pages/workspace/share/share-page.tsx:393Serious

Decorative logo icon next to 'built with' text has no aria-hidden

The footer link renders `<span className={styles.linkText}>{t['...built-with']()}</span>` immediately followed by `<Logo1Icon fontSize={20} />` with no `aria-hidden`. The icon adds no information beyond the adjacent label.

Why it matters

Screen readers announce the icon as an unlabeled image or generic name right after reading the link text, adding noise to an already simple footer link.

Fix

Mark decorative icons that sit beside their own text label as aria-hidden.

<span className={styles.linkText}>
  {t['com.affine.share-page.footer.built-with']()}
</span>
<Logo1Icon fontSize={20} />
<span className={styles.linkText}>
  {t['com.affine.share-page.footer.built-with']()}
</span>
<Logo1Icon fontSize={20} aria-hidden="true" />
96/100

UX

2 serious
UX·packages/frontend/core/src/desktop/pages/workspace/all-page/all-page.tsx:384Serious

The "Save" button silently switches between two different actions

The filter panel's `<Button onClick={handleSaveFilters}>{t['save']()}</Button>` always reads "Save," but `handleSaveFilters` branches on `selectedCollectionId`: with a collection selected it silently updates that collection's rules, with none selected it opens a prompt modal asking the user to name and create a brand-new collection.

Why it matters

Users can't predict from the label whether clicking commits instantly or launches an extra naming step, so the same button feels inconsistent depending on hidden state they can't see.

Fix

Label the commit button by the outcome it actually performs in each state, not a generic verb.

<Button onClick={handleSaveFilters}>{t['save']()}</Button>
<Button onClick={handleSaveFilters}>
  {selectedCollectionId
    ? t['save']()
    : t['com.affine.editCollection.saveCollection']()}
</Button>
UX·packages/frontend/core/src/desktop/pages/theme-editor/components/variable-list.tsx:15Serious

Node with no variables renders a bare header and no explanation

`const variables = node.variables ?? [];` falls back to an empty array with no branch for that case, so when a `node` has no variables the `Scrollable.Viewport` renders zero rows below the "Name / Light / Dark" header.

Why it matters

Users who select an empty node see what looks like a broken or loading panel, with no confirmation that the node genuinely has no theme variables and no next step.

Fix

Render a designed empty state (message, no action needed here since variables are data-driven) when the variables array is empty.

const variables = node.variables ?? [];
const variables = node.variables ?? [];
const isEmpty = variables.length === 0;

Want this on your repo?

Rams reviews your next PR automatically and posts inline fix suggestions.

Review my public repo for free
98/100

Components

1 serious
Design System·packages/frontend/core/src/desktop/pages/theme-editor/components/color-cell.tsx:47Serious

Inline flex styles on the menu list bypass the existing style system

The menu's `<ul style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>` and the `Menu`'s `contentOptions={{ style: { background: cssVar('white') } }}` set layout and background inline instead of through the `theme-editor.css` classes already used elsewhere in this file (`styles.colorCellRow`, `styles.colorCellValue`).

Why it matters

Every inline style is a rule the design system can't see or theme, so dark mode, spacing-scale updates, and future refactors have to special-case this one component instead of inheriting from the shared stylesheet.

Fix

Move inline layout and color declarations into a named class in theme-editor.css.

contentOptions={{ style: { background: cssVar('white') } }}
items={
  <ul style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
contentOptions={{ className: styles.colorCellMenuContent }}
items={
  <ul className={styles.colorCellMenuList}>

Typography

No issues found

Color

No issues found

Spacing

No issues found

Motion

No issues found

Craft

No issues found

Working well

  • The `useAllDocsOptions` hook reads `viewMode`, `displayPreference`, and `selectedCollectionId` only once via lazy `useState` initializers instead of subscribing to shared state, which is exactly right for the split-view case the file's own comment describes: each pane gets its own independent copy instead of fighting over one shared source.
  • `<Filters key={selectedCollectionId ?? 'all'} ... />` forces a full remount whenever the selected collection changes. This is the correct fix for stale-state bugs: instead of manually resetting every internal field of `Filters` when the collection switches, the key change lets React discard and recreate the component cleanly.
  • The footer 'built with' link hides itself when `isPresent || loginStatus === 'authenticated'`. That's a sharp contextual call: authenticated users already know the product and presenters don't need a promo link competing with their content, so the footer only shows up for the audience it's actually meant to persuade.
  • `EditorOutlineViewer` is gated on `currentPublishMode === 'page'`, keeping the outline affordance out of edgeless mode where a linear document outline doesn't make sense. Conditioning UI on the actual content mode instead of always rendering it keeps the interface honest about what's usable in each state.

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.