← Engel's Code Design Notebook

Agentic linting refactoring

Make the design rules something a coding agent can check and act on: known Tailwind classes, readable theme colors, and shared controls whose appearance has one owner. This is the ongoing linting refactor in OpenHands Agent Canvas.

Status checked · The first three PRs in the latest batch are merged; the secret-selection fix is open.

Before / After

Readable errors and success messages, a calmer secret-selection switch, the phone layout, and the two lint guards. Real Canvas recordings, short Before/After labels, and very quiet original music.
Download the video · MP4, 2.7 MB

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.

Current shadcn rules at the checked OpenHands revision
RuleEnforcement
no-unknown-classesError for unknown utilities.
require-static-classesError on shared UI consumers, with documented narrow exceptions.
no-restyleError for the adopted divider, toggle, and caret contracts.
no-raw-colorsWarning while palette colors are migrated by semantic role.
no-inline-stylesWarning while dynamic styles are reviewed by their owners.
no-arbitrary-valuesWarning; 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

  1. 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.

  2. 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.

  3. 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.

  4. Upgrade the analysis before adding more rules

    Merged · 5 October

    #17987 pinned @shadcn/lint 0.2.0. The dependency upgrade and each later rule activation remain separate changes.

  5. Expose raw palette colors as warnings

    Merged · 6 October

    #18006 staged no-raw-colors at warning severity, with eight exact legacy-token exceptions. #18004 tracks the migration that must precede error enforcement.

  6. Give dividers and toggles one appearance owner

    Merged · 10 October

    #18194 enabled no-restyle errors for the first settled component contracts. Callers keep layout; the toggle button also permits opacity for disabled or pending state.

  7. 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.

  8. Let the caret own its rotation

    Merged · 11 October

    #18274 extended the appearance guard to carets. Stateful callers use isOpen; a caller-supplied rotate-* override now produces an actionable lint error. Existing UI appearance is preserved.

  9. Audit inline styles before enforcing them

    Merged · 11 October

    #18275 turned no-inline-styles on 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.

  10. 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

  1. Finish the secret-selection proposal. Merge #18278 after the remaining maintainer decision. Its implementation, live evidence, and checks are ready.
  2. 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.
  3. 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-colors to an error.
  4. Expand ownership contracts carefully. Typography, menus, and Pre need 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