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

flowiseai/flowise

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

Add an accessible name to the sidebar toggle avatar

See the fix

Verdict

Solid MUI structure keeps getting undercut by icon-only controls with no accessible names and dead-end error states. The sidebar toggle's missing accessible name is the clearest sign that accessibility is an afterthought, not a practice.

Files Rams reviewed

packages/agentflow/src/features/canvas/components/AgentflowHeader.tsx

packages/agentflow/src/features/canvas/components/ConnectionLine.tsx

packages/agentflow/src/features/canvas/components/NodeIcon.tsx

packages/agentflow/src/features/canvas/components/NodeInputHandle.tsx

packages/agentflow/src/features/canvas/components/NodeModelConfigs.tsx

packages/agentflow/src/features/canvas/components/NodeOutputHandles.tsx

packages/agentflow/src/features/canvas/components/NodeStatusIndicator.tsx

packages/agentflow/src/features/canvas/components/NodeToolIcons.tsx

packages/agentflow/src/features/canvas/components/NodeToolbarActions.tsx

packages/agentflow/src/features/canvas/components/ValidationFeedback.tsx

packages/observe/src/features/executions/components/ChatMessageBubble.tsx

packages/observe/src/features/executions/components/ExecutionDetail.tsx

packages/observe/src/features/executions/components/ExecutionTreeSidebar.tsx

packages/observe/src/features/executions/components/ExecutionsListTable.tsx

packages/observe/src/features/executions/components/ExecutionsViewer.tsx

packages/observe/src/features/executions/components/FulfilledConditionsBlock.tsx

packages/observe/src/features/executions/components/HitlPanel.tsx

packages/observe/src/features/executions/components/NodeContentRenderer.tsx

packages/observe/src/features/executions/components/NodeExecutionDetail.tsx

packages/observe/src/features/executions/components/RawJsonPanel.tsx

packages/observe/src/features/executions/components/ToolAccordionList.tsx

packages/observe/src/features/executions/components/UsedToolChips.tsx

packages/ui/src/views/assistants/custom/CustomAssistantLayout.jsx

packages/ui/src/views/assistants/openai/OpenAIAssistantLayout.jsx

packages/ui/src/index.jsx

packages/ui/src/layout/MainLayout/Header/WorkspaceSwitcher/index.jsx

packages/ui/src/layout/MainLayout/Header/index.jsx

packages/ui/src/layout/MainLayout/Sidebar/MenuList/NavCollapse/index.jsx

packages/ui/src/layout/MainLayout/Sidebar/MenuList/NavGroup/index.jsx

packages/ui/src/layout/MainLayout/Sidebar/MenuList/NavItem/index.jsx

94/100

UX

3 serious
UX·packages/ui/src/views/assistants/custom/CustomAssistantLayout.jsx:145Serious

Empty state gives no way to add the first assistant

When there are no custom assistants, the empty state renders the `AssistantEmptySVG` image and a plain `<div>No Custom Assistants Added Yet</div>` with no action. - No button, no link back to `addNew` - User must scroll back up to the header's "Add" button to act

Why it matters

New users land on a dead end with no next step visible in the same view, which increases onboarding friction right at the moment they most need direction.

Fix

Give the empty state its own call-to-action wired to the same addNew handler as the header button.

    <div>No Custom Assistants Added Yet</div>
</Stack>
    <div>No Custom Assistants Added Yet</div>
    <StyledPermissionButton permissionId={'assistants:create'} variant='contained' sx={{ mt: 2, borderRadius: 2 }} onClick={addNew} startIcon={<IconPlus />}>
        Add New Custom Assistant
    </StyledPermissionButton>
</Stack>
UX·packages/ui/src/layout/MainLayout/Header/WorkspaceSwitcher/index.jsx:331Serious

Error dialog offers only Logout, no way to retry the switch

The "Workspace Switch Error" dialog shows the error message in a Typography, then a single DialogActions button reading "Logout". The dialog also sets `disableEscapeKeyDown` and `disableBackdropClick`, so this is the only exit.

Why it matters

A transient network error during a workspace switch now forces every user out of the app entirely. Users who hit a recoverable failure lose their session instead of just retrying the action.

Fix

Give error dialogs a retry path alongside any destructive fallback action.

<DialogActions>
    <Button onClick={handleLogout} variant='contained' color='primary'>
        Logout
    </Button>
</DialogActions>
<DialogActions>
    <Button onClick={() => setShowErrorDialog(false)} variant='outlined'>
        Try again
    </Button>
    <Button onClick={handleLogout} variant='contained' color='primary'>
        Logout
    </Button>
</DialogActions>
UX·packages/ui/src/layout/MainLayout/Sidebar/MenuList/NavGroup/index.jsx:58Serious

Sidebar crashes with no fallback when no 'primary' group exists

`renderPrimaryItems()` calls `item.children.find((child) => child.id === 'primary')` and immediately reads `.children` off the result with no null check. If a caller passes a menu config without a `primary` group, this throws and the entire sidebar fails to render.

Why it matters

A single malformed or edited menu config takes down navigation for every user on the page, with no error boundary or empty state to recover from.

Fix

Guard the lookup and return an empty array when no primary group is found.

const renderPrimaryItems = () => {
    const primaryGroup = item.children.find((child) => child.id === 'primary')
    return primaryGroup.children
}
const renderPrimaryItems = () => {
    const primaryGroup = item.children.find((child) => child.id === 'primary')
    return primaryGroup?.children ?? []
}
95/100

Accessibility

1 critical1 serious
Accessibility·packages/ui/src/layout/MainLayout/Header/index.jsx:261Critical

Sidebar toggle avatar has no accessible name for screen readers

The menu toggle is an `Avatar` wrapped in `ButtonBase`, showing only `<IconMenu2 />` with `onClick={handleLeftDrawerToggle}`. No `aria-label` names the action.

Why it matters

Screen reader users hear only "button" with no indication it opens the sidebar, so a primary navigation control becomes unusable without sight.

Fix

Give icon-only interactive controls an aria-label describing the action.

onClick={handleLeftDrawerToggle}
    color='inherit'
>
    <IconMenu2 stroke={1.5} size='1.3rem' />
onClick={handleLeftDrawerToggle}
    color='inherit'
    aria-label='Toggle sidebar'
>
    <IconMenu2 stroke={1.5} size='1.3rem' aria-hidden='true' />
Accessibility·packages/ui/src/layout/MainLayout/Sidebar/MenuList/NavCollapse/index.jsx:60Serious

Expandable menu button never announces open or closed state

The `ListItemButton` toggles a `Collapse` panel via `handleClick` and swaps chevron icons (`IconChevronUp`/`IconChevronDown`) to show state, but the button itself carries no `aria-expanded` or `aria-controls`.

Why it matters

Screen reader users get no indication the control expands a submenu or whether it's currently open, since the state is conveyed only through a visual icon swap.

Fix

Add aria-expanded bound to the open state and aria-controls pointing at the submenu list.

<ListItemButton
    sx={{
        borderRadius: `${customization.borderRadius}px`,
        mb: 0.5,
        alignItems: 'flex-start',
        backgroundColor: level > 1 ? 'transparent !important' : 'inherit',
        py: level > 1 ? 1 : 1.25,
        pl: `${level * 24}px`
    }}
    selected={selected === menu.id}
    onClick={handleClick}
>
<ListItemButton
    sx={{
        borderRadius: `${customization.borderRadius}px`,
        mb: 0.5,
        alignItems: 'flex-start',
        backgroundColor: level > 1 ? 'transparent !important' : 'inherit',
        py: level > 1 ? 1 : 1.25,
        pl: `${level * 24}px`
    }}
    selected={selected === menu.id}
    onClick={handleClick}
    aria-expanded={open}
    aria-controls={`nav-collapse-${menu.id}`}
>

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
Components·packages/ui/src/views/assistants/openai/OpenAIAssistantLayout.jsx:130Serious

Button radius and height hardcoded inline instead of using a shared style

Both "Load" and "Add" buttons repeat `sx={{ borderRadius: 2, height: 40 }}` inline rather than pulling from a shared button variant or theme token.

Why it matters

Every future button on this page (or a sibling assistant layout) has to remember and re-type the same two magic numbers, so any future sizing change means hunting down every copy instead of updating one place.

Fix

Extract repeated inline sx overrides into a shared constant or theme-level button variant.

<PermissionButton
    permissionId={'assistants:create'}
    variant='outlined'
    onClick={loadExisting}
    startIcon={<IconFileUpload />}
    sx={{ borderRadius: 2, height: 40 }}
>
    Load
</PermissionButton>
<PermissionButton
    permissionId={'assistants:create'}
    variant='outlined'
    onClick={loadExisting}
    startIcon={<IconFileUpload />}
    sx={headerButtonSx}
>
    Load
</PermissionButton>

Typography

No issues found

Color

No issues found

Spacing

No issues found

Motion

No issues found

Craft

No issues found

Working well

  • Splitting menu rendering into `renderPrimaryItems()` and `renderNonPrimaryGroups()` with a shared `shouldDisplayMenu` permission/display check keeps the visibility logic in one place instead of scattering conditional checks across the JSX. That's the right instinct: one source of truth for 'should this render' means permission changes only need one edit.
  • The header conditionally renders WorkspaceSwitcher, OrgWorkspaceBreadcrumbs, and the Upgrade button based on `isEnterpriseLicensed`, `isCloud`, and admin status rather than hiding them with CSS. This keeps the DOM lean per deployment context instead of shipping unused interactive elements to every user.
  • The header correctly pairs an outlined "Load" button with a contained "Add" button. This is the right hierarchy: the filled button carries more visual weight for the more common creation path, while the outline keeps the secondary import flow available without competing for attention.
  • The loading skeleton uses `Skeleton variant='rounded' height={160}` in the same 3-column grid as the real content, so the layout doesn't jump when data arrives. This is the right pattern: skeletons should mirror the final layout's shape, not just show a generic spinner.

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.