chore(site): enforce sentence-case UI labels in AgentsPage via Biome plugin

Add a Biome GritQL plugin scoped to src/pages/AgentsPage non-test files that flags Title Case in label-bearing JSX attributes (label/title/sectionLabel/aria-label) and label:/title: object properties. Fix the residual violations it surfaces (API key, Custom headers, User OIDC identity, Force on, Default on, Default off) and document the convention in site/AGENTS.md.
This commit is contained in:
Atif Ali
2026-06-02 20:22:03 +00:00
parent 887ea21237
commit 9153c4e22a
5 changed files with 63 additions and 8 deletions
+9
View File
@@ -117,6 +117,15 @@ Debug logs and pprof dumps use the same job name and commit SHA convention.
- For JSX boolean props that are `true`, use the shorthand form - For JSX boolean props that are `true`, use the shorthand form
(`<Foo prop />`) instead of `<Foo prop={true} />`. The two are (`<Foo prop />`) instead of `<Foo prop={true} />`. The two are
equivalent; the shorthand is the React convention and reduces noise. equivalent; the shorthand is the React convention and reduces noise.
- Use **sentence case** for user-facing UI labels: capitalize only the
first word and proper nouns (`Personal instructions`, `API key`), not
Title Case (`Personal Instructions`, `API Key`). Under
`src/pages/AgentsPage`, a Biome GritQL plugin
(`biome-rules/use-sentence-case-labels.grit`) enforces this for
`label`/`title`/`sectionLabel`/`aria-label` attributes and `label:`/
`title:` object properties. It cannot see labels rendered as JSX text
or built from variables, so those still need a human eye. Add genuine
proper nouns to the allowlist in that file.
- **Avoid unnecessary indirection.** Inline single-use module-level - **Avoid unnecessary indirection.** Inline single-use module-level
constants, single-use aliases, and one-line helpers that just return a constants, single-use aliases, and one-line helpers that just return a
single field at the call site. Do not create wrapper hooks that only single field at the call site. Do not create wrapper hooks that only
@@ -0,0 +1,35 @@
// Enforce sentence case for user-facing UI labels.
//
// Flags Title Case in label-bearing JSX attributes (label, title,
// sectionLabel, aria-label) and object-property labels (label:, title:,
// sectionLabel:). Sentence case capitalizes only the first word and
// proper nouns: "Personal instructions", not "Personal Instructions".
//
// Heuristic: a space followed by a capitalized word ([A-Z][a-z]). This
// also catches acronym-prefixed Title Case like "MCP Servers". Genuine
// multi-word proper nouns (e.g. "VS Code") are false positives and must
// be added to the allowlist below.
//
// Diagnostic-only: Biome GritQL plugins cannot autofix yet.
language js
or {
JsxAttribute(name = $name) as $node where {
$name <: r"^(?:label|title|sectionLabel|aria-label)$",
$node <: contains JsxString() as $value
},
JsPropertyObjectMember() as $node where {
$node <: contains JsLiteralMemberName() as $key,
$key <: r"^(?:label|title|sectionLabel)$",
$node <: contains JsStringLiteralExpression() as $value
}
} where {
$value <: r".* [A-Z][a-z].*",
// Allowlist: proper nouns / product names that are correctly multi-cap.
not $value <: r".*(?:VS Code|Coder Agents|GitHub Actions|JetBrains Fleet|Cmd/Ctrl).*",
register_diagnostic(
span = $value,
message = "Use sentence case for UI labels (e.g. \"Personal instructions\"), not Title Case. If this is a proper noun, add it to the allowlist in use-sentence-case-labels.grit.",
severity = "warn"
)
}
+11
View File
@@ -3,5 +3,16 @@
"files": { "files": {
"includes": ["!e2e/**/*Generated.ts", "!scripts/*.mjs"] "includes": ["!e2e/**/*Generated.ts", "!scripts/*.mjs"]
}, },
"overrides": [
{
"includes": [
"src/pages/AgentsPage/**/*.ts",
"src/pages/AgentsPage/**/*.tsx",
"!**/*.stories.tsx",
"!**/*.test.tsx"
],
"plugins": ["./biome-rules/use-sentence-case-labels.grit"]
}
],
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json" "$schema": "./node_modules/@biomejs/biome/configuration_schema.json"
} }
@@ -256,7 +256,7 @@ export const ProviderForm: FC<ProviderFormProps> = ({
> >
<div className="space-y-5"> <div className="space-y-5">
<ProviderField <ProviderField
label="API Key" label="API key"
htmlFor={apiKeyInputId} htmlFor={apiKeyInputId}
required={requiresAPIKey} required={requiresAPIKey}
description={apiKeyDescription} description={apiKeyDescription}
@@ -67,25 +67,25 @@ const TRANSPORT_OPTIONS = [
const AUTH_TYPE_OPTIONS = [ const AUTH_TYPE_OPTIONS = [
{ value: "none", label: "None" }, { value: "none", label: "None" },
{ value: "oauth2", label: "OAuth2" }, { value: "oauth2", label: "OAuth2" },
{ value: "api_key", label: "API Key" }, { value: "api_key", label: "API key" },
{ value: "custom_headers", label: "Custom Headers" }, { value: "custom_headers", label: "Custom headers" },
{ value: "user_oidc", label: "User OIDC Identity" }, { value: "user_oidc", label: "User OIDC identity" },
] as const; ] as const;
const AVAILABILITY_OPTIONS = [ const AVAILABILITY_OPTIONS = [
{ {
value: "force_on", value: "force_on",
label: "Force On", label: "Force on",
description: "Always injected into every conversation.", description: "Always injected into every conversation.",
}, },
{ {
value: "default_on", value: "default_on",
label: "Default On", label: "Default on",
description: "Pre-selected but users can opt out.", description: "Pre-selected but users can opt out.",
}, },
{ {
value: "default_off", value: "default_off",
label: "Default Off", label: "Default off",
description: "Available but users must opt in.", description: "Available but users must opt in.",
}, },
] as const; ] as const;
@@ -784,7 +784,7 @@ const ServerForm: FC<ServerFormProps> = ({
disabled={isDisabled} disabled={isDisabled}
/> />
</Field> </Field>
<Field label="API Key" htmlFor={`${formId}-apikey-value`}> <Field label="API key" htmlFor={`${formId}-apikey-value`}>
<Input <Input
id={`${formId}-apikey-value`} id={`${formId}-apikey-value`}
className="h-9 font-mono text-[13px] [-webkit-text-security:disc]" className="h-9 font-mono text-[13px] [-webkit-text-security:disc]"