fix(webapp): require explicit native button types (#4692)

## Summary

Add explicit types to native dashboard buttons and enforce
`react/button-has-type`.

This prevents action buttons from accidentally submitting a surrounding
form. Shared button primitives retain their caller-selected submit and
reset semantics with documented lint exceptions.

Base: [#4691](https://github.com/triggerdotdev/trigger.dev/pull/4691)
This commit is contained in:
Chris Arderne
2026-08-19 16:35:42 +01:00
committed by GitHub
parent a7a1e74fcb
commit dda9504bdd
13 changed files with 17 additions and 0 deletions
+1
View File
@@ -115,6 +115,7 @@
{
"files": ["apps/webapp/app/**/*.ts", "apps/webapp/app/**/*.tsx"],
"rules": {
"react/button-has-type": "error",
"react/jsx-no-useless-fragment": "error",
"react/no-unstable-nested-components": "error",
"react/rules-of-hooks": "error",
@@ -970,6 +970,7 @@ function HeaderCellContent({
)}
{onFilterClick && (
<button
type="button"
onClick={(e) => {
e.stopPropagation();
onFilterClick();
@@ -240,6 +240,7 @@ export function SideMenuItem({
}
/** Button styled to match {@link SideMenuItem}, for entries that open a dialog rather than navigate. */
/* oxlint-disable react/button-has-type -- Callers can select button, reset, or submit semantics. */
export const SideMenuItemButton = forwardRef<
HTMLButtonElement,
{
@@ -276,3 +277,4 @@ export const SideMenuItemButton = forwardRef<
</button>
);
});
/* oxlint-enable react/button-has-type */
@@ -67,6 +67,7 @@ export function AppliedFilter({
</div>
{removable && (
<button
type="button"
className={cn(
"group flex size-6 items-center justify-center focus-custom",
variantClassName.clear
@@ -374,6 +374,7 @@ type ButtonPropsType = Pick<
> &
React.ComponentProps<typeof ButtonContent>;
/* oxlint-disable react/button-has-type -- Callers can select button, reset, or submit semantics. */
export const Button = forwardRef<HTMLButtonElement, ButtonPropsType>(
({ type, disabled, autoFocus, onClick, "aria-label": ariaLabel, ...props }, ref) => {
const innerRef = useRef<HTMLButtonElement>(null);
@@ -435,6 +436,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonPropsType>(
return buttonElement;
}
);
/* oxlint-enable react/button-has-type */
type LinkPropsType = Pick<
LinkProps,
@@ -51,6 +51,7 @@ function PopoverSectionHeader({
);
}
/* oxlint-disable react/button-has-type -- The trigger supports form button semantics. */
const PopoverMenuItem = React.forwardRef<
HTMLButtonElement | HTMLAnchorElement,
{
@@ -141,6 +142,7 @@ const PopoverMenuItem = React.forwardRef<
}
);
PopoverMenuItem.displayName = "PopoverMenuItem";
/* oxlint-enable react/button-has-type */
function PopoverCustomTrigger({
isOpen,
@@ -582,6 +582,7 @@ export function SelectButtonItem({
}: SelectButtonItemProps) {
const render = (
<button
type="button"
onClick={onClick}
className={cn("block w-full text-left", selectItemClasses, props.className)}
/>
@@ -433,6 +433,7 @@ export const TableCell = forwardRef<HTMLTableCellElement, TableCellProps>(
<div className={cn(flexClasses, "gap-2")}>
{leadingContent}
<button
type="button"
onClick={onClick}
className={cn(
"inline-flex cursor-pointer items-center gap-2 focus:outline-hidden",
@@ -446,6 +447,7 @@ export const TableCell = forwardRef<HTMLTableCellElement, TableCellProps>(
</div>
) : (
<button
type="button"
onClick={onClick}
className={cn("cursor-pointer focus:outline-hidden", flexClasses, actionClassName)}
tabIndex={isTabbableCell ? 0 : -1}
@@ -128,6 +128,7 @@ export function ToastUI({
{actionNode}
</div>
<button
type="button"
className={cn(
"-mr-1 ms-auto rounded p-2 text-text-dimmed transition hover:text-text-bright",
title && "-mt-1"
@@ -344,6 +344,7 @@ export function ToolUseRow({ tool }: { tool: ToolUse }) {
>
{availableTabs.map((tab) => (
<button
type="button"
key={tab}
onClick={() => handleTabClick(tab)}
className={`px-2.5 py-1 text-[11px] capitalize transition-colors ${
@@ -47,6 +47,7 @@ function ToolDefRow({ def, wasCalled }: { def: ToolDefinition; wasCalled: boolea
{def.parametersJson && (
<div className="pl-3.5">
<button
type="button"
onClick={() => setShowSchema(!showSchema)}
className="text-[10px] text-text-link hover:underline"
>
@@ -436,6 +436,7 @@ function Upgradable({
<span>
Save your changes or{" "}
<button
type="button"
className="inline text-indigo-500 hover:text-indigo-300"
onClick={() => {
setAllocation(initialAllocation(environments));
@@ -164,6 +164,7 @@ export default function Story() {
<div className="flex flex-wrap gap-2">
{exampleQueries.map((example) => (
<button
type="button"
key={example.name}
onClick={() => setQuery(example.query)}
className="rounded bg-background-raised px-3 py-1.5 text-sm text-text-dimmed transition hover:bg-surface-control hover:text-text-bright"