hcengineering/platform
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·July 22, 2026
Elevated
Design risk in this codebase.
More findings
Verdict
Solid token-driven visuals sit on top of mouse-only interaction logic, with keyboard navigation built but never connected to the one action that matters: moving a card. The bigger worry is a drag-cancel bug that can silently drop a card off the board, a data-integrity issue disguised as a polish gap.
Files Rams reviewed
packages/hls/src/components/HlsVideo.svelte
packages/kanban/src/components/Kanban.svelte
packages/kanban/src/components/KanbanRow.svelte
packages/panel/src/components/Panel.svelte
packages/presentation/src/components/ActionContext.svelte
packages/presentation/src/components/AttributeBarEditor.svelte
packages/presentation/src/components/AttributeEditor.svelte
packages/presentation/src/components/AttributesBar.svelte
packages/presentation/src/components/Card.svelte
packages/presentation/src/components/DocPopup.svelte
packages/presentation/src/components/DownloadFileButton.svelte
packages/presentation/src/components/DrawingBoard.svelte
packages/presentation/src/components/DrawingBoardColorSelectorIcon.svelte
packages/presentation/src/components/DrawingBoardToolbar.svelte
packages/presentation/src/components/DrawingBoardToolbarColorIcon.svelte
packages/presentation/src/components/FilePreview.svelte
packages/presentation/src/components/FilePreviewPopup.svelte
packages/presentation/src/components/FileTypeIcon.svelte
packages/presentation/src/components/HTMLViewer.svelte
packages/presentation/src/components/IconWithEmoji.svelte
packages/presentation/src/components/Image.svelte
packages/presentation/src/components/InlineAttributeBar.svelte
packages/presentation/src/components/InlineAttributeBarEditor.svelte
packages/presentation/src/components/LiteMessageViewer.svelte
packages/presentation/src/components/MessageBox.svelte
packages/presentation/src/components/MessageViewer.svelte
packages/presentation/src/components/NavLink.svelte
packages/presentation/src/components/ObjectPopup.svelte
packages/presentation/src/components/ObjectSearchPopup.svelte
packages/presentation/src/components/PDFViewer.svelte
Accessibility
packages/kanban/src/components/Kanban.svelte:285
Moving a card between columns only works with a mouse drag
The column drop zone `.panel-container` is a plain `<div>` wired only to `on:dragover`/`on:drop`, with an explicit `<!-- svelte-ignore a11y-no-static-element-interactions -->` suppressing the linter's warning. The `select()` function already gives keyboard users focus movement between cards, but there is no keyboard path that actually calls `move(state)` to change a card's category.
Why it matters
Keyboard-only and motor-impaired users can select and view cards but have no way to perform the board's core action: moving a card to another state. The interaction is entirely inaccessible to them, not just inconvenient.
Fix
Give the drop zone a keyboard-triggerable path to the same `move(state)` action, e.g. a focusable target with `role="group"`, `aria-label` naming the column, and a keydown handler that invokes `move` for the currently selected card.
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="panel-container"
bind:this={stateRefs[si]}
on:dragover={(event) => {
panelDragOver(event, state)
}}
on:drop={() => {
void move(state).then(() => {
isDragging = false
})
}}
><div
class="panel-container"
role="group"
aria-label={typeof state === 'object' ? state.name : String(state)}
bind:this={stateRefs[si]}
on:dragover={(event) => {
panelDragOver(event, state)
}}
on:drop={() => {
void move(state).then(() => {
isDragging = false
})
}}
on:keydown={(event) => {
if (event.key === 'Enter' && dragCard !== undefined) {
void move(state).then(() => { isDragging = false })
}
}}
>packages/kanban/src/components/KanbanRow.svelte:117
Card container is a div with drag, context-menu, and selection logic but no keyboard path
The `.card-container` div carries `on:contextmenu`, `on:dragstart`, `class:selection`, and `class:checked` state, with `<!-- svelte-ignore a11y-no-static-element-interactions -->` suppressing the lint warning. It has no `role`, no `tabindex`, and `on:focus={() => {}}` is a no-op, so keyboard and screen-reader users can never focus a card, select it, or open its context menu.
Why it matters
Keyboard-only and screen-reader users cannot select a kanban card, open its actions menu, or move it, which locks a whole class of users out of the board's core interaction.
Fix
Give the interactive card a role, a tab stop, a real focus handler, and a keydown path to the same menu the mouse triggers.
<div
class="card-container"
class:selection={selection !== undefined ? objects[selection]?._id === object._id : false}
class:checked={checkedSet.has(object._id)}
on:mouseover={mouseAttractor(() => dispatch('obj-focus', object))}
on:mouseenter={mouseAttractor(() => dispatch('obj-focus', object))}
on:focus={() => {}}
on:contextmenu={(evt) => {
showMenu(evt, object)
}}
draggable={true}
class:draggable={true}<div
class="card-container"
class:selection={selection !== undefined ? objects[selection]?._id === object._id : false}
class:checked={checkedSet.has(object._id)}
role="button"
tabindex="0"
aria-selected={selection !== undefined ? objects[selection]?._id === object._id : false}
on:mouseover={mouseAttractor(() => dispatch('obj-focus', object))}
on:mouseenter={mouseAttractor(() => dispatch('obj-focus', object))}
on:focus={mouseAttractor(() => dispatch('obj-focus', object))}
on:contextmenu={(evt) => {
showMenu(evt, object)
}}
on:keydown={(evt) => {
if (evt.key === 'Enter' || evt.key === 'ContextMenu') {
showMenu(evt, object)
}
}}
draggable={true}
class:draggable={true}packages/hls/src/components/HlsVideo.svelte:130
Captions are force-disabled and the compiler's own warning is silenced
The template opens with `<!-- svelte-ignore a11y-media-has-caption -->` above the `<video>` element, there is no `<track>` child, and `options.captions = { active: false }` explicitly turns off Plyr's caption toggle. The `controls` array passed to Plyr also omits `'captions'` entirely.
Why it matters
Deaf and hard-of-hearing users get no way to follow the video's audio content at all: this is a WCAG 1.2.2 failure, not a nice-to-have, and suppressing the linter's warning means the gap will never resurface in code review.
Fix
Ship at least one caption track and let Plyr's caption toggle stay available instead of forcing it off.
options.captions = {
active: false
}options.captions = {
active: true
}UX
packages/kanban/src/components/Kanban.svelte:164
Cancelling a drag can silently drop the card from the board
In `panelDragLeave`, restoring a card's original position calls `setGroupByValues(groupByDocs, dragCardInitialPosition, newArr)`. The third parameter should be the category (`dragCardInitialState`), but `dragCardInitialPosition` (a number) is passed instead: the restored array is written under the wrong key.
Why it matters
When a user drags a card out of its column and releases it somewhere that cancels the move (or drags back over the origin), the card can be written into a bogus bucket instead of its original column, making it disappear from the board with no error or recovery path.
Fix
Pass the actual category value, not the numeric position, as the key argument to `setGroupByValues`.
if (dragCardInitialPosition !== undefined) {
const newArr = getGroupByValues(groupByDocs, dragCardInitialState)
newArr.splice(dragCardInitialPosition, 0, dragCard)
setGroupByValues(groupByDocs, dragCardInitialPosition, newArr)
}if (dragCardInitialPosition !== undefined) {
const newArr = getGroupByValues(groupByDocs, dragCardInitialState)
newArr.splice(dragCardInitialPosition, 0, dragCard)
setGroupByValues(groupByDocs, dragCardInitialState, newArr)
}packages/kanban/src/components/KanbanRow.svelte:124
Card's action menu only opens on right-click, with no keyboard equivalent
`on:contextmenu={(evt) => { showMenu(evt, object) }}` is the only entry point into a card's menu (line 124). There is no visible menu button and no keydown handler tied to the same `showMenu` function, so the action list is reachable only with a mouse right-click.
Why it matters
Any per-card action reachable exclusively through `showMenu` is unavailable to keyboard and switch-control users, and it's undiscoverable since nothing in the rendered card signals a menu exists.
Fix
Route the same `showMenu` handler through a keydown listener (or an icon-button trigger) so the menu opens without a pointer.
on:contextmenu={(evt) => {
showMenu(evt, object)
}}on:contextmenu={(evt) => {
showMenu(evt, object)
}}
on:keydown={(evt) => {
if (evt.key === 'ContextMenu' || (evt.shiftKey && evt.key === 'F10')) {
showMenu(evt, object)
}
}}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 freeComponents
packages/panel/src/components/Panel.svelte:198
Inline style:align-self bypasses the class-based layout system used everywhere else
The header tools row uses `class="buttons-group xsmall-gap ml-4" style:align-self={'flex-start'}`: every other row in this file (aside actions, aside-tabs, custom-attributes) expresses layout purely through utility classes, but this one row reaches for an inline style directive instead.
Why it matters
A one-off inline style can't be themed, overridden by a parent layout change, or picked up by future utility-class refactors: it's a silent exception future maintainers won't know to look for.
Fix
Express alignment through a utility class consistent with the rest of the file instead of an inline style directive.
<div class="buttons-group xsmall-gap ml-4" style:align-self={'flex-start'}><div class="buttons-group xsmall-gap ml-4 self-start">Typography
Color
Spacing
Motion
Craft
Working well
- The `select()` function already builds real keyboard traversal across cards and categories (vertical and horizontal), dispatching `obj-focus` and calling `scrollIntoView` on the right panel. That's the harder half of accessible kanban navigation done correctly; wiring the actual column-move action to a key handler is a much smaller lift given this foundation.
- `.checked` and `.selection` use two different visual mechanisms, a solid background fill versus a box-shadow ring, instead of just swapping one hue for another. That gives the two states a distinct shape as well as a distinct color, which helps them read as different states rather than just different intensities of the same one.
- Every color in the stylesheet (`var(--theme-kanban-card-bg-color)`, `var(--highlight-select)`, `var(--primary-button-default)`, etc.) is a theme token rather than a hardcoded hex value. That's what makes this component survive a theme change or dark-mode variant without a single line of CSS being touched.
- The component leans entirely on `Scroller`, `HeaderAdaptive`, and the imported `Panel` primitive for scroll/header mechanics rather than hand-rolling scroll containers or a custom header bar: this avoids re-implementing focus and scroll-restoration logic that the shared primitives already own.
Scored July 22, 2026 with Rams Engine v0.0.2 · Engine changelog
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 this review on every PR.