fix(webapp): enforce keyboard interaction safeguards (#4702)

## Summary

Enable keyboard-event and static-element interaction safeguards across
the dashboard.

Earlier stack changes move actionable behavior to native controls. This
final enforcement keeps narrowly documented exceptions for focus
forwarding, scoped Escape handling, CodeMirror focus, and pointer-driven
table column resizing.

`jsx-a11y/no-autofocus` remains disabled.

Base: [#4701](https://github.com/triggerdotdev/trigger.dev/pull/4701)
This commit is contained in:
Chris Arderne
2026-08-19 16:35:47 +01:00
committed by GitHub
parent 5e50d2f80d
commit 23c5619dd1
5 changed files with 28 additions and 14 deletions
+2 -2
View File
@@ -76,7 +76,7 @@
"react/no-unknown-property": "error",
"jsx-a11y/alt-text": "error",
"jsx-a11y/aria-role": "error",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/click-events-have-key-events": "error",
"jsx-a11y/control-has-associated-label": [
"error",
{
@@ -87,7 +87,7 @@
"jsx-a11y/label-has-associated-control": "error",
"jsx-a11y/no-autofocus": "off",
"jsx-a11y/no-noninteractive-element-interactions": "error",
"jsx-a11y/no-static-element-interactions": "off",
"jsx-a11y/no-static-element-interactions": "error",
"jsx-a11y/prefer-tag-over-role": "off",
"jsx-a11y/anchor-ambiguous-text": "error",
"jsx-a11y/anchor-has-content": "error",
@@ -264,6 +264,7 @@ export function TSQLEditor(opts: TSQLEditorProps) {
const showButtons = showClearButton || showCopyButton || showFormatButton || additionalActions;
/* oxlint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- The CodeMirror mount forwards pointer focus to CodeMirror's own keyboard-accessible editor. */
return (
<div
className={cn("relative flex h-full flex-col", opts.className)}
@@ -337,6 +338,7 @@ export function TSQLEditor(opts: TSQLEditorProps) {
</div>
);
}
/* oxlint-enable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
// SQL keywords that legitimately appear before parentheses with a space
const SQL_KEYWORDS_BEFORE_PAREN = new Set([
@@ -14,6 +14,7 @@ import {
type ColumnFiltersState,
type ColumnResizeMode,
type FilterFn,
type Header,
type SortDirection,
type SortingState,
} from "@tanstack/react-table";
@@ -1045,6 +1046,24 @@ function FilterCell({
);
}
/* oxlint-disable jsx-a11y/no-static-element-interactions -- Column resizing is a pointer-drag interaction provided by TanStack Table. */
function ColumnResizeHandle({ header }: { header: Header<RowData, unknown> }) {
return (
<div
onDoubleClick={() => header.column.resetSize()}
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
className={cn(
"absolute right-0 top-0 h-full w-0.5 cursor-col-resize touch-none select-none",
"opacity-0 group-hover/header:opacity-100",
"bg-surface-control hover:bg-indigo-500",
header.column.getIsResizing() && "bg-indigo-500 opacity-100"
)}
/>
);
}
/* oxlint-enable jsx-a11y/no-static-element-interactions */
export const TSQLResultsTable = memo(function TSQLResultsTable({
rows,
columns,
@@ -1237,18 +1256,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({
>
{flexRender(header.column.columnDef.header, header.getContext())}
</HeaderCellContent>
{/* Column resizer */}
<div
onDoubleClick={() => header.column.resetSize()}
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
className={cn(
"absolute right-0 top-0 h-full w-0.5 cursor-col-resize touch-none select-none",
"opacity-0 group-hover/header:opacity-100",
"bg-surface-control hover:bg-indigo-500",
header.column.getIsResizing() && "bg-indigo-500 opacity-100"
)}
/>
<ColumnResizeHandle header={header} />
</th>
);
})}
@@ -581,6 +581,7 @@ export function DashboardAgentPanel({
// Not filtered to active: the wake banner needs watches that already fired.
const chatWatches = activeChat?.watches ?? [];
/* oxlint-disable jsx-a11y/no-static-element-interactions -- Escape handling intentionally bubbles from focused controls inside the panel. */
return (
<div
ref={panelRef}
@@ -667,3 +668,4 @@ export function DashboardAgentPanel({
</div>
);
}
/* oxlint-enable jsx-a11y/no-static-element-interactions */
@@ -95,6 +95,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
const inputClassName = variants[variant].input;
const variantIconClassName = variants[variant].iconSize;
/* oxlint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- The wrapper only forwards pointer focus to its nested input. */
return (
<div
className={cn(
@@ -125,6 +126,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
);
}
);
/* oxlint-enable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
Input.displayName = "Input";
export { Input };