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

mckaywrigley/chatbot-ui

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

30 files reviewed·July 25, 2026

View on GitHub

Elevated

UX needs attention.

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

Top fix

Fix invalid Tailwind classes so brand logo actually centers

See the fix

Verdict

Minimal chrome hides real fragility: a logo that won't center, a label pointing nowhere, and workspace fetches that silently go stale or freeze on error. The look is clean but the plumbing beneath it is unguarded, turning small oversights into user-facing dead ends.

Files Rams reviewed

app/[locale]/[workspaceid]/chat/page.tsx

app/[locale]/[workspaceid]/layout.tsx

app/[locale]/layout.tsx

app/[locale]/login/page.tsx

app/[locale]/setup/page.tsx

app/[locale]/[workspaceid]/chat/[chatid]/page.tsx

app/[locale]/[workspaceid]/page.tsx

app/[locale]/help/page.tsx

app/[locale]/login/password/page.tsx

app/[locale]/page.tsx

app/[locale]/globals.css

components/chat/assistant-picker.tsx

components/chat/chat-command-input.tsx

components/chat/chat-files-display.tsx

components/chat/chat-help.tsx

components/chat/chat-hooks/use-chat-handler.tsx

components/chat/chat-hooks/use-chat-history.tsx

components/chat/chat-hooks/use-prompt-and-command.tsx

components/chat/chat-hooks/use-scroll.tsx

components/chat/chat-hooks/use-select-file-handler.tsx

components/chat/chat-input.tsx

components/chat/chat-messages.tsx

components/chat/chat-retrieval-settings.tsx

components/chat/chat-secondary-buttons.tsx

components/chat/chat-settings.tsx

components/chat/chat-ui.tsx

components/chat/file-picker.tsx

components/chat/prompt-picker.tsx

components/chat/quick-setting-option.tsx

components/chat/quick-settings.tsx

89/100

UX

1 critical4 serious
UXCritical

app/[locale]/[workspaceid]/chat/page.tsx:31

Brand logo never actually centers due to invalid Tailwind classes

The Brand wrapper on the empty chat state uses `top-50%`, `left-50%`, `-translate-x-50%`, and `-translate-y-50%`. None of these are valid Tailwind utilities (percentage values need bracket syntax like `top-[50%]` or the fraction classes `top-1/2`/`translate-x-1/2`), so Tailwind's JIT compiler drops all four rules silently. The `<Brand>` logo sits at its default top-left position inside the absolutely positioned wrapper instead of being centered on screen.

Why it matters

The empty chat state's one visual anchor, the product logo, renders in the wrong spot for every user who opens a fresh chat, making the first screen look broken before any message is sent.

Fix

Use Tailwind's fraction-based centering utilities so the transform rules actually compile.

<div className="top-50% left-50% -translate-x-50% -translate-y-50% absolute mb-20">
<div className="absolute left-1/2 top-1/2 mb-20 -translate-x-1/2 -translate-y-1/2">
UXSerious

app/[locale]/[workspaceid]/chat/page.tsx:49

ChatHelp disappears entirely on mobile with no alternative access

The `<ChatHelp>` wrapper uses `hidden md:block`, so below the md breakpoint the help/onboarding entry point is removed from the DOM's visible render with no other trigger for it anywhere in the empty chat state.

Why it matters

Mobile users, who often need onboarding help more than desktop users, lose access to help content completely rather than getting a repositioned or condensed version of it.

Fix

Keep a visible, repositioned trigger for ChatHelp at small breakpoints instead of hiding it outright.

<div className="absolute bottom-2 right-2 hidden md:block lg:bottom-4 lg:right-4">
  <ChatHelp />
</div>
<div className="absolute bottom-2 right-2 lg:bottom-4 lg:right-4">
  <ChatHelp />
</div>
UXSerious

app/[locale]/[workspaceid]/layout.tsx:130

Workspace data fetch has no error handling, freezing the screen on failure

`fetchWorkspaceData` awaits a long chain of sequential calls (`getWorkspaceById`, `getAssistantWorkspacesByWorkspaceId`, `getChatsByWorkspaceId`, `getCollectionWorkspacesByWorkspaceId`, `getFoldersByWorkspaceId`, `getFileWorkspacesByWorkspaceId`, `getPresetWorkspacesByWorkspaceId`, `getPromptWorkspacesByWorkspaceId`) with no try/catch anywhere in the sequence.

Why it matters

If any single request fails (network blip, expired session, server error), the promise chain throws unhandled and the user is left on a blank or perpetually loading workspace with no error message and no retry action.

Fix

Wrap the fetch sequence in try/catch and surface a recoverable error state instead of letting the throw propagate silently.

const workspace = await getWorkspaceById(workspaceId)
    setSelectedWorkspace(workspace)

    const assistantData = await getAssistantWorkspacesByWorkspaceId(workspaceId)
try {
      const workspace = await getWorkspaceById(workspaceId)
      setSelectedWorkspace(workspace)

      const assistantData = await getAssistantWorkspacesByWorkspaceId(workspaceId)
      // ...remaining fetches
    } catch (error) {
      setLoadError("Couldn't load this workspace. Retry?")
    }
UXSerious

app/[locale]/[workspaceid]/layout.tsx:74

Overlapping workspace fetches on rapid switching can overwrite fresh state with stale data

The `useEffect` on `[workspaceId]` fires `fetchWorkspaceData(workspaceId)` with no cancellation token or request-id guard. If a user switches workspaces twice in quick succession, both async calls run concurrently and whichever resolves last wins, regardless of which workspace is actually selected.

Why it matters

A user who quickly clicks through two workspaces can end up viewing chats, files, or presets from the wrong workspace because a slower, stale response overwrote the correct one after the fact.

Fix

Guard the async effect with a cancellation flag or request id so a stale response can't overwrite state after a newer request has resolved.

useEffect(() => {
    ;(async () => await fetchWorkspaceData(workspaceId))()

    setUserInput("")
useEffect(() => {
    let cancelled = false
    ;(async () => {
      await fetchWorkspaceData(workspaceId, () => cancelled)
    })()

    setUserInput("")
    // ...
    return () => { cancelled = true }
UXSerious

app/[locale]/login/page.tsx:185

Login inputs miss autoComplete, breaking password manager autofill

Both the email `<Input name="email">` and password `<Input type="password" name="password">` have no `autoComplete` attribute set.

Why it matters

Without autoComplete="email" and autoComplete="current-password", browsers and password managers can't reliably offer to save or autofill credentials, adding friction for every returning user on this login form.

Fix

Set autoComplete to the correct token (email, current-password) on each credential field.

<Input
  className="mb-6 rounded-md border bg-inherit px-4 py-2"
  type="password"
  name="password"
  placeholder="••••••••"
/>
<Input
  className="mb-6 rounded-md border bg-inherit px-4 py-2"
  type="password"
  name="password"
  autoComplete="current-password"
  placeholder="••••••••"
/>
98/100

Accessibility

1 serious
AccessibilitySerious

app/[locale]/login/page.tsx:175

Email label points to an input id that doesn't exist

The `<Label htmlFor="email">Email</Label>` targets `id="email"`, but the `<Input>` right below it only sets `name="email"` with no `id` prop at all.

Why it matters

Screen reader users tabbing to the field hear no accessible name, and clicking the visible "Email" text won't focus the input, breaking a basic form interaction pattern for assistive tech users.

Fix

Add a matching id to every input that has an associated label using htmlFor.

<Input
  className="mb-3 rounded-md border bg-inherit px-4 py-2"
  name="email"
  placeholder="you@example.com"
  required
/>
<Input
  id="email"
  className="mb-3 rounded-md border bg-inherit px-4 py-2"
  name="email"
  placeholder="you@example.com"
  required
/>

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

  • QuickSettings and ChatSettings sit at symmetric top-left/top-right offsets in the empty chat state, giving the screen a balanced frame even before any content loads: a small consistency detail that keeps the empty state from feeling accidental.
  • The workspaceId-change effect resets setUserInput, setChatMessages, setSelectedChat, and the file/image state arrays together, correctly preventing one workspace's draft input or chat history from leaking into the next.
  • On the login form, "Login" is a solid blue-700 button with white text (6.7:1 contrast, passes AA) while "Sign Up" is an outlined secondary button: a clear, correctly differentiated primary/secondary CTA pair.

Scored July 25, 2026 with Rams Engine v0.0.3 · Engine changelog
First scored May 13, 2026: 63/100. This rescore on v0.0.3: 59/100.

This page is an automated design review of mckaywrigley/chatbot-ui’s UI code: 30 files read against 308 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