knecht-works/knecht-cloud
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·November 10, 2026
Elevated
Design risk in this codebase.
More findings
Verdict
Strong component thinking collides with a systematic gap: interactive state lives almost exclusively in hover and animation surfaces that keyboard and vestibular users cannot reach. The infinite animation with no reduced-motion escape is the single highest-risk pattern, present across files with no override anywhere.
Files Rams reviewed
app/pages/join/[token].vue
app/pages/login.vue
app/pages/projects/[id]/index.vue
app/pages/projects/[id]/settings.vue
app/pages/projects/[id]/settings/advanced.vue
app/pages/projects/[id]/settings/agent.vue
app/pages/projects/[id]/settings/integrations.vue
app/pages/projects/index.vue
app/pages/reset/[token].vue
app/pages/sessions/index.vue
app/pages/settings/advanced.vue
app/pages/settings/agent.vue
app/pages/settings/index.vue
app/pages/setup.vue
app/pages/system.vue
app/pages/workflows/index.vue
app/components/KAppSearch.vue
app/components/KAuthForm.vue
app/components/KChatTranscript.vue
app/components/KEnvLifecycle.vue
app/components/KIntegrationConnection.vue
app/components/KLogo.vue
app/components/KPageHeader.vue
app/components/KPanel.vue
app/components/KPasswordModal.vue
app/components/KPreviewBrowser.vue
app/components/KProfilePanel.vue
app/components/KProjectCard.vue
app/components/KProjectHeader.vue
app/components/KProjectHero.vue
Motion
app/pages/settings/index.vue:273
Infinite animation runs with no reduced-motion escape anywhere in these files
`animate-spin` loops forever, and no prefers-reduced-motion guard exists in any file of this review. For vestibular-sensitive users this is not a preference, it is a barrier.
Why it matters
Motion that cannot be turned off turns some visitors away at the door.
animate-spin@media (prefers-reduced-motion: reduce) { animation: none; }app/pages/projects/[id]/index.vue:194
Infinite spinner ignores prefers-reduced-motion, harms vestibular users
The diagnosis-pending `UIcon` at line 194 of `projects/[id]/index.vue` carries `class="animate-spin"` with no `prefers-reduced-motion` guard in this file or in a global Tailwind config override visible here. The spinner runs indefinitely regardless of OS motion preference.
Why it matters
Continuous rotation causes nausea and disorientation for vestibular disorder users who have set the OS reduced-motion flag.
Fix
Guard infinite animations with motion:animate-none or a CSS media query so they stop when prefers-reduced-motion is set.
class="mr-1 inline-block size-3 animate-spin align-[-1px] text-dimmed"class="mr-1 inline-block size-3 animate-spin motion-reduce:animate-none align-[-1px] text-dimmed"UX
app/pages/projects/[id]/settings/advanced.vue:24
Success toast verb "disconnected" breaks the single-verb flow set by the UI
In `advanced.vue` line 24, `toast.add` fires with `title: 'Project disconnected'`. The trigger button uses `label="Disconnect"`, the row uses `label="Disconnect project"`, and the modal title uses `"Disconnect project"`. Four surfaces, three different phrasings of the same verb.
Why it matters
Inconsistent verb forms across trigger, confirm, and feedback make the action harder to recognize in support logs and user recall.
Fix
Use one noun-qualified verb phrase across the trigger button, confirm modal, and success toast.
toast.add({ title: 'Project disconnected', description: 'Its environments are being removed in the background.', color: 'success' })toast.add({ title: 'Project disconnected', description: 'Its environments are being removed in the background.', color: 'success' })app/pages/login.vue:45
Forgot-password hint is a dead end with no actionable next step
In `login.vue` at line 45-47, the paragraph "Forgot your password? Ask an owner for a reset link." is styled `text-xs text-dimmed` with no link, button, or contact path. The only thing users can do after reading it is close the tab.
Why it matters
Users locked out of their account have no self-serve path, compounding frustration and generating owner support load.
Fix
Replace static hint text with an actionable link or mailto so locked-out users have a concrete next step.
<p class="text-xs text-dimmed">
Forgot your password? Ask an owner for a reset link.
</p><p class="text-xs text-dimmed">
Forgot your password?
<a href="mailto:" class="underline underline-offset-2 hover:text-toned">Ask an owner for a reset link.</a>
</p>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 freeAccessibility
app/pages/projects/[id]/settings/agent.vue:115
Integration chip tooltip is keyboard-inaccessible; account name never surfaces
In `agent.vue`, `UTooltip` wraps a `<span>` chip for each `configuredIntegrations` entry. The `<span>` is not focusable, so keyboard users have no path to trigger the tooltip. The `mentionHint(i)` text: the only place the account handle appears: is therefore unreachable without a pointer device.
Why it matters
Keyboard users cannot discover which account each integration chip represents, breaking the Mentions configuration flow.
Fix
Replace the non-focusable span trigger with a button or add tabindex="0" and a keyboard handler so the hint is reachable.
<span class="k-mono flex items-center gap-1.5 rounded-full border border-default px-2.5 py-1 text-2xs text-muted"><span
tabindex="0"
class="k-mono flex cursor-default items-center gap-1.5 rounded-full border border-default px-2.5 py-1 text-2xs text-muted"
>Typography
Color
Spacing
Components
Craft
Working well
- The `removeProject` handler in `advanced.vue` wraps the DELETE call in `try/finally`, so `removing.value` resets whether the request succeeds or throws. This prevents a permanently-disabled "Disconnect" button on network failure: a subtle but important recovery path that most implementations skip.
- The status card in `projects/[id]/index.vue` derives both its border color and gradient background from the same `statusColor` variable via `color-mix()`. One source drives two surfaces with zero duplication: this is exactly how to keep multi-surface state color in sync.
Scored November 10, 2026 with Rams Engine v0.0.4 · Engine changelog
This page is an automated design review of knecht-works/knecht-cloud’s UI code: 30 files read against 365 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.