Skip to content

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

Draft
matifali wants to merge 1 commit into
mainfrom
feat/agents-sentence-case-lint
Draft

chore(site): enforce sentence-case UI labels in AgentsPage via Biome plugin#26000
matifali wants to merge 1 commit into
mainfrom
feat/agents-sentence-case-lint

Conversation

@matifali
Copy link
Copy Markdown
Member

@matifali matifali commented Jun 2, 2026

Summary

#25941 converted AgentsPage UI labels to sentence case by hand. Nothing stops Title Case from creeping back in. This adds a lint gate so the linter catches it instead of reviewers.

It's 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. It rides the existing biome lint, so there's no new lint step or dependency, and it shows up in-editor too.

Note

This is a deliberate gate, not full coverage. It catches the attribute/property-shaped labels (~75% of what #25941 fixed) with near-zero false positives. Labels rendered as JSX text (<label>API Key</label>) or built from variables/ternaries are not caught: distinguishing those from ordinary prose needs matching all text, which floods false positives (measured below). Treat it as defense-in-depth, not a guarantee.

Changes

  • Add site/biome-rules/use-sentence-case-labels.grit (diagnostic-only; Biome plugins can't autofix yet).

  • Wire it into site/biome.jsonc via overrides, scoped to AgentsPage non-test files (verified: 0 leakage elsewhere).

  • Fix the residual violations the gate surfaces:

    Before After Where
    API Key API key ProviderForm, MCPServerAdminPanel (×2)
    Custom Headers Custom headers MCPServerAdminPanel
    User OIDC Identity User OIDC identity MCPServerAdminPanel
    Force On / Default On / Default Off Force on / Default on / Default off MCPServerAdminPanel
  • Allowlist one false positive (the Cmd/Ctrl+Enter shortcut label).

  • Document the convention in site/AGENTS.md.

Labels are display-only (value/htmlFor are separate), and no story/test asserts the changed strings, so nothing downstream breaks.

Validation

Ran the rule against the exact before/after commits of #25941:

Check Result
Labels #25941 converted, caught at the pre-PR commit 18 / 24 source labels
Of those caught, still flagged after #25941 0 (rule agrees with the manual fix)
Scope leakage (non-AgentsPage or test files) 0
biome lint --error-on-warnings src/pages/AgentsPage passes
tsc -p . passes
Decision log: why gate-only, and what full coverage would cost

I prototyped both the scoped structural rule and a max-coverage variant and measured them against #25941's before/after commits.

Coverage vs. noise

Scoped rule (this PR) Max-coverage (all strings + JSX text)
Labels from #25941 caught 18 / 24 (75%) 26 / 26 (100%)
Source false positives on already-correct code ~0 (after fixes) 94
Test/story false positives 0 (excluded) 732

The casing regex looks at characters, not meaning, so it can't tell a UI label from a sentence that legitimately contains a proper noun (OpenAI, WebSocket), a CSS font-family value, or an error message. Pushing to 100% recall means gating CI on legitimate prose and maintaining an allowlist of most English sentences. That's a precision-vs-recall wall, so this PR picks precision.

The 6 misses are all JSX children text (<TableHead>Cache read</TableHead>) or computed strings (cond ? "Edit model" : "Add model") — shapes a cheap structural rule can't target without matching all text.

Perf (--profile-rules): the GritQL plugin is the single most expensive rule (~5.1s summed across 1733 files vs ~176ms for the next rule). Scoping it to AgentsPage keeps the real cost small; it's diagnostic-only and beta, so scope matters. Note: capturing-group regex (a|b) silently matches nothing in Biome's GritQL; non-capturing (?:a|b) is required.

Follow-ups (not in this PR):

  • Widen the gate beyond AgentsPage (~106 codebase-wide hits to fix first).
  • A targeted JSX-text tier for known label components (TableHead, TooltipContent, SettingsHeader) to lift coverage toward ~90%.
  • For genuine 100% enforcement, route labels through a centralized/typed strings layer so labels are distinguishable from prose.

Test plan

  • cd site && pnpm exec biome lint --error-on-warnings src/pages/AgentsPage is clean
  • Introduce a Title Case label="Foo Bar" under AgentsPage and confirm biome lint flags it
  • Confirm no diagnostics outside AgentsPage / in *.stories.tsx / *.test.tsx

Related to #25941.

🤖 This PR was created with the help of Coder Agents, and needs a human review. 🧑‍💻

…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.
Comment thread site/biome.jsonc
Comment on lines +9 to +10
"src/pages/AgentsPage/**/*.ts",
"src/pages/AgentsPage/**/*.tsx",
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scoping this to a single area for now, and we can expand this to the full frontend in a follow-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant