Before / After
Recorded with control-openhands, production Canvas builds, Chrome, and the same real local Agent Server 1.54.0. The film compares a common base with all four proposal heads combined in an isolated worktree, including the still-open fix. The lint-only scenes show actual ESLint results. Credential fields stayed empty; no model execution was needed.
What an agent is being asked to respect
Put a rule in the repository where every contributor can run it, give the finding an actionable message, and define the exceptions where the value is owned. A warning exposes migration work. An error makes a new violation fail lint.
| Rule | Enforcement |
|---|---|
no-unknown-classes | Error for unknown utilities. |
require-static-classes | Error on shared UI consumers, with documented narrow exceptions. |
no-restyle | Error for the adopted divider, toggle, and caret contracts. |
no-raw-colors | Warning while palette colors are migrated by semantic role. |
no-inline-styles | Warning while dynamic styles are reviewed by their owners. |
no-arbitrary-values | Warning; legitimate one-off values still need judgment. |
Primitive implementations own their appearance, so no-restyle and require-static-classes are off inside src/ui. Layout remains available to callers. Stateful carets expose isOpen for rotation; the generic caret icon retains caller orientation.
Numeric CSS custom properties and semantic token references pass the inline-style rule. Measured dimensions, portal positioning, accessibility helpers, and renderer contracts need an owner audit. The exact agent-server-ui-root.tsx exception preserves its public, caller-owned style and styleOverrides API.
The rollout so far
Put Canvas tokens in the Tailwind theme
Merged · 21 September#17435 established the first shadcn-lint pass and mapped the existing
--oh-*theme tokens into Tailwind. It gave the linter the same named colors the app uses.Reject classes Tailwind does not know
Merged · 4 October#17644 enabled unknown-class errors with verified theme loading, so a plausible-looking but nonexistent utility cannot pass silently.
Make shared UI classes inspectable
Merged · 5 October#17916 required static classes on Canvas UI consumers. Primitive variant helpers stay under the component owner's control; documented exceptions account for the plugin's analysis limits.
Upgrade the analysis before adding more rules
Merged · 5 October#17987 pinned
@shadcn/lint0.2.0. The dependency upgrade and each later rule activation remain separate changes.Expose raw palette colors as warnings
Merged · 6 October#18006 staged
no-raw-colorsat warning severity, with eight exact legacy-token exceptions. #18004 tracks the migration that must precede error enforcement.Give dividers and toggles one appearance owner
Merged · 10 October#18194 enabled
no-restyleerrors for the first settled component contracts. Callers keep layout; the toggle button also permits opacity for disabled or pending state.Make MCP feedback readable across themes
Merged · 11 October#18270 moved error and success text to semantic feedback tokens, including darker text colors for the light palettes. Nine raw-color findings were removed. See the film at 0:04 and 0:14.
Let the caret own its rotation
Merged · 11 October#18274 extended the appearance guard to carets. Stateful callers use
isOpen; a caller-suppliedrotate-*override now produces an actionable lint error. Existing UI appearance is preserved.Audit inline styles before enforcing them
Merged · 11 October#18275 turned
no-inline-styleson as warnings. Its audit recorded 87 findings across 45 files after the exact embedding-root exception. Those are migration findings at that audited revision, rather than a claim about today's total lint count.Use the shared switch for secret selection
Open · 11 October#18278 replaces the fixed bright-green “Also save as secret” appearance with the shared toggle and theme tokens. The secret name and help control wrap together on phones, and keyboard focus stays visible. Six raw-color findings are removed by this proposal.
The selected name's contrast changes from 1.70:1 to 12.55:1 in Light+, and from 1.56:1 to 10.40:1 in Solarized Light. The PR is approved and its checks pass at this snapshot; it still needs to merge. See 0:24 and 0:34 in the film.
The two visual changes together remove 15 raw-color findings. Inline-style warnings expose a separate existing backlog; these figures do not describe the net warning count.
What's next, in order
- Finish the secret-selection proposal. Merge #18278 after the remaining maintainer decision. Its implementation, live evidence, and checks are ready.
- Continue the color migration by role. Work through #18004 in bounded batches: feedback and status, warning text, neutral text, and scrims. Choose semantic tokens and check the actual backdrop in all five palettes. Geometric nearest-color suggestions do not decide a color's meaning.
- Promote raw-color warnings when the migration is ready. Rerun the audit on the then-current head, account for each remaining finding and narrow exception, and verify rendering before changing
no-raw-colorsto an error. - Expand ownership contracts carefully. Typography, menus, and
Preneed their own appearance decisions. Review dynamic-style owners before strengthening inline-style enforcement; preserve measured layout and public renderer interfaces.
Each batch needs a linked issue, focused behavior or rule tests, a passing lint/build path, and real app evidence when the UI changes. The rule probes prove what ESLint rejects; the recordings prove what people see.
Sources and reproduction
- Checked lint configuration and contributor guidance at
fddb0ddc, including the exact rule scopes and exceptions. - Raw-color migration tracker #18004. Its original 148-findings inventory is a historical audit, not the current remaining count.
- Video provenance and dated PR-status snapshot: source revisions, real-backend setup, chapter timings, media hash, and the original music method.
- The control-openhands CLI and the verification work, which provide the app-driving and evidence loop used here.