mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
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:
@@ -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
|
||||
(`<Foo prop />`) instead of `<Foo prop={true} />`. The two are
|
||||
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
|
||||
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
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
@@ -3,5 +3,16 @@
|
||||
"files": {
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@ export const ProviderForm: FC<ProviderFormProps> = ({
|
||||
>
|
||||
<div className="space-y-5">
|
||||
<ProviderField
|
||||
label="API Key"
|
||||
label="API key"
|
||||
htmlFor={apiKeyInputId}
|
||||
required={requiresAPIKey}
|
||||
description={apiKeyDescription}
|
||||
|
||||
@@ -67,25 +67,25 @@ const TRANSPORT_OPTIONS = [
|
||||
const AUTH_TYPE_OPTIONS = [
|
||||
{ value: "none", label: "None" },
|
||||
{ value: "oauth2", label: "OAuth2" },
|
||||
{ value: "api_key", label: "API Key" },
|
||||
{ value: "custom_headers", label: "Custom Headers" },
|
||||
{ value: "user_oidc", label: "User OIDC Identity" },
|
||||
{ value: "api_key", label: "API key" },
|
||||
{ value: "custom_headers", label: "Custom headers" },
|
||||
{ value: "user_oidc", label: "User OIDC identity" },
|
||||
] as const;
|
||||
|
||||
const AVAILABILITY_OPTIONS = [
|
||||
{
|
||||
value: "force_on",
|
||||
label: "Force On",
|
||||
label: "Force on",
|
||||
description: "Always injected into every conversation.",
|
||||
},
|
||||
{
|
||||
value: "default_on",
|
||||
label: "Default On",
|
||||
label: "Default on",
|
||||
description: "Pre-selected but users can opt out.",
|
||||
},
|
||||
{
|
||||
value: "default_off",
|
||||
label: "Default Off",
|
||||
label: "Default off",
|
||||
description: "Available but users must opt in.",
|
||||
},
|
||||
] as const;
|
||||
@@ -784,7 +784,7 @@ const ServerForm: FC<ServerFormProps> = ({
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="API Key" htmlFor={`${formId}-apikey-value`}>
|
||||
<Field label="API key" htmlFor={`${formId}-apikey-value`}>
|
||||
<Input
|
||||
id={`${formId}-apikey-value`}
|
||||
className="h-9 font-mono text-[13px] [-webkit-text-security:disc]"
|
||||
|
||||
Reference in New Issue
Block a user