diff --git a/AGENTS.md b/AGENTS.md
index 3dfcf13a3..71ba99c07 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -34,7 +34,7 @@
- state management is done via plain `useState` and React Context API
- avoid using `useEffect`
- all texts should be provided translations via the i18next library's `useTranslations` hook's `t` function
-- instead of `&&` operator for conditional rendering, use the ternary operator
+- instead of `&&` operator for conditional rendering, use the ternary operator; enforced by the `no-and-conditional-rendering` Biome plugin
- for localized user-readable time strings use ``, `` or `useFormatDistanceToNow`. If needed use `useDateTimeFormat` directly. NEVER use e.g. `toLocaleString` directly as it does not include users' language selection.
## Forms
diff --git a/app/components/BuildCard.tsx b/app/components/BuildCard.tsx
index d0bc6fec0..b1c673cbd 100644
--- a/app/components/BuildCard.tsx
+++ b/app/components/BuildCard.tsx
@@ -103,7 +103,7 @@ export function BuildCard({
>
{" "}
diff --git a/app/root.tsx b/app/root.tsx
index ad6c5c0fa..4fd550ba3 100644
--- a/app/root.tsx
+++ b/app/root.tsx
@@ -259,7 +259,7 @@ function Document({
- {IS_E2E_TEST_RUN && }
+ {IS_E2E_TEST_RUN ? : null}
diff --git a/biome-plugins/no-and-conditional-rendering.grit b/biome-plugins/no-and-conditional-rendering.grit
new file mode 100644
index 000000000..12fc1bbda
--- /dev/null
+++ b/biome-plugins/no-and-conditional-rendering.grit
@@ -0,0 +1,18 @@
+language js
+
+// `cond && ` renders the left value when it is falsy but not `null` /
+// `undefined` / `false`: a `0` count or an empty string ends up in the DOM as
+// text. The ternary form has no such edge, so it is the repo-wide convention
+// (AGENTS.md, "React"). Matches both `cond && ` anywhere and any `&&`
+// standing directly as a JSX child, which covers helpers like
+// `{icon && React.cloneElement(icon, {...})}`.
+`$cond && $rendered` as $expression where {
+ or {
+ $rendered <: or {
+ JsxTagExpression(),
+ JsParenthesizedExpression(expression=JsxTagExpression())
+ },
+ $expression <: within JsxExpressionChild(expression=$expression)
+ },
+ register_diagnostic(span=$expression, message="Do not use `&&` for conditional rendering: a falsy left side that is not `null`/`undefined`/`false` (a `0` count, an empty string, `NaN`) renders itself. Use a ternary instead: `cond ? : null`.", severity="error")
+}
diff --git a/biome.json b/biome.json
index 70759a085..f8c0e0c7b 100644
--- a/biome.json
+++ b/biome.json
@@ -221,6 +221,10 @@
{
"includes": ["**", "!app/utils/kysely.server.ts"],
"plugins": ["./biome-plugins/no-kysely-sqlite-helpers.grit"]
+ },
+ {
+ "includes": ["app/**"],
+ "plugins": ["./biome-plugins/no-and-conditional-rendering.grit"]
}
]
}