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

tldraw/tldraw

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.

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

Top fix

Replace native confirm() with the app's own delete dialog

See the fix

Verdict

Admin tooling that works but never quite feels considered, from browser-native confirms to loading states that silently vanish. The biggest risk is destructive actions relying on confirm() instead of the app's own review flow.

Files Rams reviewed

apps/dotcom/client/src/components/ErrorPage/ErrorPage.tsx

packages/tldraw/src/lib/ui/components/primitives/layout.tsx

apps/docs/app/(docs)/[...slug]/page.tsx

apps/docs/app/layout.tsx

apps/dotcom/client/src/pages/admin/index.tsx

apps/dotcom/client/src/tla/pages/local-file-index.tsx

templates/chat/src/app/layout.tsx

templates/chat/src/app/page.tsx

templates/nextjs/src/app/layout.tsx

templates/nextjs/src/app/page.tsx

apps/dotcom/client/src/components/LoginRedirectPage/LoginRedirectPage.tsx

apps/dotcom/client/src/pages/admin/FlagsSection.tsx

apps/dotcom/client/src/pages/admin/SystemSection.tsx

apps/dotcom/client/src/pages/admin/UsersSection.tsx

apps/dotcom/client/src/pages/admin/admin.module.css

apps/dotcom/client/src/pages/admin/shared.tsx

apps/dotcom/client/src/pages/dev-browser-run-thumbnail.tsx

apps/dotcom/client/src/pages/dev-reset-local-state.tsx

apps/dotcom/client/src/tla/pages/file-history-snapshot.tsx

apps/dotcom/client/src/tla/pages/file-history.tsx

apps/dotcom/client/src/tla/pages/file-pierre-history-snapshot.tsx

apps/dotcom/client/src/tla/pages/file-pierre-history.tsx

apps/dotcom/client/src/tla/pages/file.tsx

apps/dotcom/client/src/tla/pages/import.tsx

apps/dotcom/client/src/tla/pages/invite.tsx

apps/dotcom/client/src/tla/pages/legacy-history-snapshot.tsx

apps/dotcom/client/src/tla/pages/legacy-history.tsx

apps/dotcom/client/src/tla/pages/legacy-readonly-old.tsx

apps/dotcom/client/src/tla/pages/legacy-readonly.tsx

apps/dotcom/client/src/tla/pages/legacy-room.tsx

91/100

UX

1 critical3 serious
UXCritical

apps/dotcom/client/src/tla/pages/local-file-index.tsx:24

Delete action gated by native confirm() instead of app's own dialog

The "delete" button in the local files list calls `confirm('Are you 100% sure you want to delete this file? This action cannot be undone.')` directly. This is the only confirmation step before an irreversible IndexedDB delete, and it bypasses whatever dialog system the rest of the app uses.

Why it matters

Native confirm() dialogs can be suppressed by the browser (repeated dialogs, embedded contexts) and offer no styling, focus management, or accessible labeling consistent with the rest of the product, so a destructive delete can proceed with a confirmation step the user never actually saw.

Fix

Replace native confirm() with the app's accessible confirmation dialog component so destructive actions get consistent, guaranteed confirmation UI.

if (confirm('Are you 100% sure you want to delete this file? This action cannot be undone.')) {
	await deleteDB(STORE_PREFIX + persistenceKey)
	navigate('.', { replace: true })
}
const confirmed = await showAppConfirmDialog({
	title: 'Delete file',
	message: 'Are you sure you want to delete this file? This action cannot be undone.',
})
if (confirmed) {
	await deleteDB(STORE_PREFIX + persistenceKey)
	navigate('.', { replace: true })
}
UXSerious

apps/dotcom/client/src/pages/admin/index.tsx:19

Admin route redirects home before user auth state finishes loading

`Component` in admin/index.tsx checks `if (!user?.isTldraw) return <Navigate to="/" replace />` immediately after calling `useTldrawCurrentUser()`, with no check for a loading state.

Why it matters

If the hook returns undefined while the user fetch is still in flight, a legitimate admin gets redirected to "/" before their auth resolves, forcing a reload or re-navigation to reach admin tools.

Fix

Check an explicit loading state from the auth hook and defer the redirect decision until that state resolves.

const user = useTldrawCurrentUser()
const { section } = useParams()

if (!user?.isTldraw) {
	return <Navigate to="/" replace />
}
const { user, isLoading } = useTldrawCurrentUser()
const { section } = useParams()

if (isLoading) {
	return <div>Loading...</div>
}
if (!user?.isTldraw) {
	return <Navigate to="/" replace />
}
UXSerious

apps/dotcom/client/src/pages/admin/SystemSection.tsx:39

System health section shows nothing while data is still loading

The "System health" section only renders `error` or `replicatorData` once the fetch resolves; there is no loading state tracked or shown between the `fetch('/api/app/admin/replicator')` call and its resolution.

Why it matters

Admins loading the page see an empty section with just the heading until the request finishes, unable to tell whether the section is loading, broken, or simply has nothing to show.

Fix

Add an explicit loading state that renders visible feedback while the fetch is in flight.

<section className={styles.adminSection}>
	<h3 className={styles.sectionTitle}>System health</h3>
	{error && <div className={styles.errorMessage}>{error}</div>}
	{replicatorData && <StructuredDataDisplay data={replicatorData} />}
</section>
<section className={styles.adminSection}>
	<h3 className={styles.sectionTitle}>System health</h3>
	{isLoading && <p>Loading system health...</p>}
	{error && <div className={styles.errorMessage} role="alert">{error}</div>}
	{replicatorData && <StructuredDataDisplay data={replicatorData} />}
</section>
UXSerious

apps/dotcom/client/src/pages/admin/FlagsSection.tsx:216

Percentage input forces a full keyboard for a 0-100 number entry

The percentage flag input is `<input type="text" value={pct} onChange={...} ...>` with no `inputMode` or `pattern` set, even though the onChange handler clamps the value to a 0-100 number.

Why it matters

On mobile, admins editing a rollout percentage get the full alphanumeric keyboard instead of a numeric one, slowing down what should be a quick numeric edit to a live production flag.

Fix

Add inputMode="numeric" and a digit pattern to text inputs that only accept numbers, so mobile keyboards default to digits.

<input
	type="text"
	value={pct}
	onChange={(e) => {
		const n = Number(e.target.value)
		if (!Number.isNaN(n)) setPct(Math.max(0, Math.min(100, n)))
	}}
	disabled={isSaving || !flagValue.enabled}
	className={styles.searchInput}
	style={{ width: 60 }}
/>
<input
	type="text"
	inputMode="numeric"
	pattern="[0-9]*"
	value={pct}
	onChange={(e) => {
		const n = Number(e.target.value)
		if (!Number.isNaN(n)) setPct(Math.max(0, Math.min(100, n)))
	}}
	disabled={isSaving || !flagValue.enabled}
	className={styles.searchInput}
	style={{ width: 60 }}
/>
98/100

Accessibility

1 serious
AccessibilitySerious

apps/dotcom/client/src/tla/pages/local-file-index.tsx:46

Delete button's zero padding shrinks its tap target below usable size

The delete `<button>` next to each file link is styled with `style={{ padding: 0 }}`, giving it a hit area barely larger than the word "delete" itself.

Why it matters

A hit area well under the 44x44px accessible minimum increases mis-taps on touch devices and for motor-impaired users, which is especially risky on a destructive, irreversible action.

Fix

Give interactive controls a minimum comfortable hit area even when visually compact, using padding rather than zero.

<button onClick={() => onDelete(persistenceKey)} style={{ padding: 0 }}>
	delete
</button>
<button onClick={() => onDelete(persistenceKey)} style={{ padding: '8px 12px' }}>
	delete
</button>

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
ComponentsSerious

apps/dotcom/client/src/pages/admin/FlagsSection.tsx:200

Percentage flag label re-implements a class that already exists

The `<label>` for each percentage flag hardcodes `style={{ cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 8 }}`, which duplicates the layout already defined on `.featureFlagLabel` in admin.module.css, and this label is nested one level inside a div already using that class.

Why it matters

Any future edit to `.featureFlagLabel` in admin.module.css won't apply to this inline copy, so this specific label's layout silently drifts out of sync with every other flag label over time, growing maintenance cost.

Fix

Reuse the existing .featureFlagLabel class on this element instead of re-declaring its layout inline.

<label
	htmlFor={flagName}
	style={{ cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 8 }}
>
<label htmlFor={flagName} className={styles.featureFlagLabel}>

Typography

No issues found

Color

No issues found

Spacing

No issues found

Motion

No issues found

Craft

No issues found

Working well

  • The admin sidebar's active state is computed through NavLink's `isActive` callback (`isActive ? styles.adminSidebarItemActive : styles.adminSidebarItem`) rather than manual path string matching, so it stays correct automatically as routes change.
  • The local-file link's inline color still resolves through `var(--tl-color-primary)` rather than a hardcoded hex, keeping it tied to the theme token even though it's set inline.
  • The percentage flag's save button is disabled until `pct !== currentPct`, which prevents pointless no-op writes to a flag that affects all users immediately.

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

This page is an automated design review of tldraw/tldraw’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