Rams MCP · The full engine, now in your coding agent
windmill-labs on GitHub

windmill-labs/windmill

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

30 files reviewed·July 9, 2026

Top fix

Rebuild file grid with table or list semantics for screen readers

See the fix

Verdict

Interaction sequencing is thoughtful, autofocus, Enter-to-submit, clear Create/Cancel, but the file grid abandons semantic structure entirely, leaving screen reader users without row or column context. The biggest risk is accessibility debt hiding behind polished visuals: a stray blue fails contrast and inputs lack labels.

Files Rams reviewed

frontend/src/lib/components/tutorials/app/BackgroundRunnablesTutorial.svelte

frontend/src/lib/components/tutorials/app/ConnectionTutorial.svelte

ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/FileItem.tsx

ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/FileList.tsx

ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/FolderTree.tsx

ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/Toolbar.tsx

ai_evals/fixtures/frontend/app/initial/shopping_cart/frontend/components/Cart.tsx

frontend/src/lib/components/tutorials/app/ExpressionEvaluationTutorial.svelte

ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/Breadcrumb.tsx

ai_evals/fixtures/frontend/app/initial/shopping_cart/frontend/components/ProductCard.tsx

ai_evals/fixtures/frontend/app/initial/shopping_cart/frontend/components/ProductList.tsx

ai_evals/fixtures/frontend/app/initial/file_manager/frontend/index.tsx

ai_evals/fixtures/frontend/app/initial/inventory_tracker/frontend/index.tsx

ai_evals/fixtures/frontend/app/initial/recipe_book/frontend/index.tsx

ai_evals/fixtures/frontend/app/initial/session_id_chat/frontend/index.tsx

ai_evals/fixtures/frontend/app/initial/shopping_cart/frontend/index.tsx

ai_evals/fixtures/frontend/app/initial/token_heavy_context/frontend/components/ReferencePanel.tsx

frontend/src/lib/components/AIAgentLogViewer.svelte

frontend/src/lib/components/AIProviderPicker.svelte

frontend/src/lib/components/AIReasoningEffortPicker.svelte

frontend/src/lib/components/AddUser.svelte

frontend/src/lib/components/ApiConnectForm.svelte

frontend/src/lib/components/AppConnectDrawer.svelte

frontend/src/lib/components/AppConnectLightweightResourcePicker.svelte

frontend/src/lib/components/ArgEnum.svelte

frontend/src/lib/components/ArgInfo.svelte

frontend/src/lib/components/ArrayTypeNarrowing.svelte

frontend/src/lib/components/AssignableTags.svelte

frontend/src/lib/components/AssignableTagsInner.svelte

frontend/src/lib/components/Auth0Setting.svelte

92/100

Accessibility

4 serious
Accessibility·ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/Toolbar.tsx:21Serious

White text on the blue fill fails AA contrast at this size

The "Create" and "New Folder" buttons render white text (#ffffff) on `bg-blue-500` (#3b82f6). At normal text weight and size that pair computes to 3.68:1.

Why it matters

WCAG AA requires 4.5:1 for normal-size text; this button falls short for users with low vision, and it's the primary action in the toolbar.

Fix

Raise text-on-fill contrast to at least 4.5:1 for normal-size button labels.

className="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
className="px-3 py-1 bg-gray-900 text-white rounded hover:bg-gray-800"
Accessibility·ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/FileList.tsx:28Serious

File list built from plain divs loses row and column structure for screen readers

The header row (`Name`, `Type`, `Modified`, `Actions`) and each file row below it are plain `<div>` elements laid out with `grid-cols-12`. No `<table>`/`<ul>` semantics or ARIA roles (`role="table"`, `role="row"`, `role="columnheader"`) mark this as a structured listing.

Why it matters

Screen reader users navigating by table or list get no row count, column count, or column-header association here, so this reads as four unrelated lines of text instead of a data grid, even though sighted users clearly see aligned columns.

Fix

Add table/row/columnheader roles (or swap to a native table) so the visual grid has matching semantic structure.

<div className="grid grid-cols-12 gap-4 px-4 py-2 border-b bg-gray-50 font-medium text-sm text-gray-600">
	<div className="col-span-6">Name</div>
	<div className="col-span-2">Type</div>
	<div className="col-span-2">Modified</div>
	<div className="col-span-2">Actions</div>
</div>
<div className="grid grid-cols-12 gap-4 px-4 py-2 border-b bg-gray-50 font-medium text-sm text-gray-600" role="row">
	<div className="col-span-6" role="columnheader">Name</div>
	<div className="col-span-2" role="columnheader">Type</div>
	<div className="col-span-2" role="columnheader">Modified</div>
	<div className="col-span-2" role="columnheader">Actions</div>
</div>
Accessibility·ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/Toolbar.tsx:49Serious

'New Folder' hit area falls well under the 44px touch target

The primary "New Folder" button uses `py-1` (4px top/bottom padding) with default text line-height, putting the tappable height near 24-28px.

Why it matters

This is the toolbar's main entry point for creating content; on a touch surface a target this small increases mis-taps for users with motor impairments.

Fix

Give primary actions at least 44px of tap height, using vertical padding or a min-height utility.

className="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600 flex items-center gap-2"
className="px-3 py-2.5 bg-gray-900 text-white rounded hover:bg-gray-800 flex items-center gap-2"
Accessibility·ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/Toolbar.tsx:22Serious

Folder name input has no accessible label, only a placeholder

The `<input type="text">` for the new folder relies on `placeholder="Folder name"` with no `<label>` or `aria-label`.

Why it matters

Placeholder text disappears the moment the user types and screen readers don't reliably announce it as the field's purpose, so users of assistive tech get an unlabeled text box.

Fix

Give every form input a persistent accessible name via aria-label or a visually-hidden label.

<input
	type="text"
	value={folderName}
	onChange={(e) => setFolderName(e.target.value)}
	onKeyDown={(e) => e.key === 'Enter' && handleCreate()}
	placeholder="Folder name"
	className="border rounded px-3 py-1"
	autoFocus
/>
<input
	type="text"
	value={folderName}
	onChange={(e) => setFolderName(e.target.value)}
	onKeyDown={(e) => e.key === 'Enter' && handleCreate()}
	placeholder="Folder name"
	aria-label="Folder name"
	className="border rounded px-3 py-1"
	autoFocus
/>
96/100

UX

2 serious
UX·ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/FileList.tsx:14Serious

Empty folder state is a dead end with no path forward

When `files.length === 0`, the component renders a single gray line: "This folder is empty". There is no button, link, or hint pointing the user to their next action.

Why it matters

This is the first thing a user sees in a brand-new or cleared-out folder. Landing on a dead end here means the user has to hunt for the create/upload control elsewhere in the UI instead of being handed it at the moment they need it.

Fix

Give the empty state a primary action tied to folder creation, gated behind an optional callback prop.

return (
	<div className="text-center text-gray-500 py-8">This folder is empty</div>
)
return (
	<div className="text-center py-12">
		<p className="text-gray-500 mb-3">This folder is empty</p>
		{onCreateFolder && (
			<button
				type="button"
				onClick={onCreateFolder}
				className="text-sm font-medium text-gray-900 underline underline-offset-2"
			>
				Create a folder
			</button>
		)}
	</div>
)
UX·ai_evals/fixtures/frontend/app/initial/file_manager/frontend/components/FileList.tsx:13Serious

Component has no loading or error branch between empty and full states

`FileList` only handles two states: `files.length === 0` and a populated array via `sortedFiles.map`. There is no branch for a fetch-in-progress or fetch-failed state, even though `files` is the kind of prop that typically arrives from an async load.

Why it matters

If the parent ever passes an empty array while data is still loading, or after a failed fetch, users see the same "This folder is empty" message as a truly empty folder, so a fetch error looks identical to having no files.

Fix

Accept explicit loading/error props (or a status enum) and render a skeleton or retry state before falling through to the empty-folder message.

if (files.length === 0) {
	return (
		<div className="text-center text-gray-500 py-8">This folder is empty</div>
	)
}
if (isLoading) {
	return <div className="text-center text-gray-500 py-8">Loading files…</div>
}

if (error) {
	return (
		<div className="text-center text-gray-500 py-8">
			Couldn't load this folder. <button type="button" onClick={onRetry} className="underline">Try again</button>
		</div>
	)
}

if (files.length === 0) {
	return (
		<div className="text-center text-gray-500 py-8">This folder is empty</div>
	)
}

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

  • Sorting folders before files, then alphabetically within each group (`if (a.type === 'folder' && b.type !== 'folder') return -1`), is the right call for a file browser: users scan for containers first and files second, so grouping by type before name keeps the list predictable regardless of how items are named.
  • The folder-name input carries `autoFocus`, so the moment 'New Folder' is clicked, the cursor is already in the field ready to type. That's the right call: the user came here to name something, and reaching for the mouse to click into the only input would break the flow at the exact moment intent is highest.
  • `FileListProps` types every callback explicitly (`onDelete: (item: FileItem) => void`, etc.) instead of leaving them as untyped functions. This is what makes the component safe to reuse: any caller gets a compile error the moment they pass the wrong shape, instead of a silent runtime failure.
  • The header grid (`grid-cols-12`, `col-span-6`, `col-span-2`) sticks entirely to Tailwind's standard scale with no arbitrary bracket values, so the column layout stays predictable and easy to adjust from one shared system rather than hand-tuned pixel widths.

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.