Skip to content

chore(lint): add Prettier, ESLint 9, Husky, and @/ import aliases#137

Merged
Manuel-Jentic merged 10 commits intomainfrom
chore/formatting-linting
Apr 1, 2026
Merged

chore(lint): add Prettier, ESLint 9, Husky, and @/ import aliases#137
Manuel-Jentic merged 10 commits intomainfrom
chore/formatting-linting

Conversation

@Manuel-Jentic
Copy link
Copy Markdown
Contributor

@Manuel-Jentic Manuel-Jentic commented Apr 1, 2026

Summary

Adds complete formatting and linting infrastructure to the UI project. Every source file is now formatted consistently and linted on commit + CI.

What changed:

  • Prettier — tabs, single quotes, semicolons, 100-char lines, Tailwind class sorting
  • ESLint 9 (flat config) — TypeScript, React Hooks, import ordering, unused imports, jsx-a11y accessibility, button-has-type, consistent-type-imports, no-restricted-imports
  • Husky + lint-staged — pre-commit hook auto-formats and lints staged files
  • @/ path alias — all cross-directory imports use @/ (maps to src/), enforced by ESLint no-restricted-imports rule
  • CIformat:check and lint steps added to ci-ui.yml before TypeScript check (blocking)

Bulk changes (can be skimmed):

  • 78 source/test/E2E files reformatted by Prettier
  • ~100 relative imports (../) converted to @/ absolute imports
  • 86 react/button-has-type errors fixed (explicit type attribute on all <button> elements)
  • 2 jsx-a11y/label-has-associated-control errors fixed (labels → fieldset/legend)
  • 1 @typescript-eslint/no-unused-expressions error fixed (ternary → if/else)

Files to review (the important ones)

File What to check
ui/eslint.config.js All rules, test/E2E overrides, globalIgnores
ui/prettier.config.js Formatting options
ui/.editorconfig Editor defaults
ui/.prettierignore Exclusion list
ui/package.json New scripts, devDependencies, lint-staged config
.husky/pre-commit Hook command
ui/vite.config.ts resolve.alias for @/
ui/tsconfig.json baseUrl + paths for @/
.github/workflows/ci-ui.yml New Format check + Lint steps
.claude/CLAUDE.md Updated docs

The remaining 78 files are bulk formatting + import conversion — no logic changes except the lint fixes listed above.

ESLint warnings (143, all pre-existing)

All warnings are @typescript-eslint/no-explicit-any, no-non-null-assertion, and similar. These are deliberately set to warn (not error) for gradual cleanup. CI passes with warnings. No --max-warnings 0 — that would block all PRs until 143+ instances are cleaned up.

Next steps

  • UI component library PR — consolidate and deduplicate shared components (Button, Badge, Card, Input, Select, etc.). Once the library has replacements for raw HTML elements, the ESLint config already has the no-restricted-syntax rule ready to enforce usage of <Button> over <button>, <Link> over <a>, etc. — just needs escalating from warn to error and adding new selectors for <input>, <select>, etc.
  • Gradual any cleanup — address the 143 warnings over time, then consider --max-warnings 0 in CI
  • Vite 5 → 7 upgrade — planned separately

Test plan

  • npx tsc --noEmit — zero TypeScript errors
  • npx prettier --check . — all files formatted
  • npx eslint . — 0 errors, 143 warnings (pre-existing)
  • npx vite build — production build succeeds
  • CI validates on PR push

Made with Cursor

- prettier.config.js: tabs, single quotes, semicolons, Tailwind class sorting
- eslint.config.js: flat config with TypeScript, React, Hooks, import-x,
  unused-imports, jsx-a11y, consistent-type-imports, button-has-type,
  no-restricted-imports (enforce @/ aliases)
- .editorconfig: editor-agnostic indent/encoding defaults
- .prettierignore: exclude generated code, build artifacts, coverage

Made-with: Cursor
Install prettier, eslint@9, typescript-eslint, eslint-plugin-react,
eslint-plugin-react-hooks, eslint-plugin-import-x, eslint-plugin-unused-imports,
eslint-plugin-jsx-a11y, eslint-config-prettier, prettier-plugin-tailwindcss,
globals, husky, and lint-staged.

Update npm scripts: lint, lint:fix, format, format:check, prepare.
Add lint-staged config for pre-commit formatting and linting.

Made-with: Cursor
Run lint-staged on every commit to auto-format and lint staged files.

Made-with: Cursor
Add resolve.alias in vite.config.ts and paths in tsconfig.json
mapping @/ to src/. Enables clean imports like @/components/ui/Badge
instead of ../../components/ui/Badge.

Made-with: Cursor
- Prettier: reformat all source, test, and E2E files (tabs, single quotes,
  trailing commas, Tailwind class sorting)
- Convert ~100 relative parent imports (../) to @/ absolute imports across
  30+ files. Sibling and test-internal imports remain relative.
- Fix 86 react/button-has-type errors: add explicit type attributes
- Fix 2 jsx-a11y/label-has-associated-control errors: add htmlFor/id pairs,
  convert label groups to fieldset/legend
- Fix 1 @typescript-eslint/no-unused-expressions: ternary → if/else
- Fix import ordering violations

Made-with: Cursor
Insert npm run format:check and npm run lint before TypeScript check
in ci-ui.yml. Both are blocking — PRs with formatting violations or
lint errors will fail CI.

Made-with: Cursor
Update CLAUDE.md with formatting/linting section, @/ import convention,
and correct CI filename reference. Update BUILD_REPORT.md with Prettier,
ESLint, and Husky status lines.

Made-with: Cursor
Add || true fallback so npm install doesn't fail in CI, Docker, or
when the package is installed as a dependency without a .git directory.

Made-with: Cursor
@Manuel-Jentic Manuel-Jentic self-assigned this Apr 1, 2026
@Manuel-Jentic Manuel-Jentic added the enhancement New feature or request label Apr 1, 2026
Vitest uses its own config file and doesn't inherit resolve.alias from
vite.config.ts. Without this, all @/ imports fail during test runs.

Made-with: Cursor
Copy link
Copy Markdown
Member

@char0n char0n left a comment

Choose a reason for hiding this comment

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

LGTM

@char0n char0n requested a review from Copilot April 1, 2026 12:32
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds formatting/linting/tooling infrastructure for the UI repo (Prettier + ESLint 9 flat config + Husky/lint-staged) and standardizes imports via an @/ alias across build, test, and source code.

Changes:

  • Introduces Prettier + ESLint 9 (flat config) and wires them into local scripts, pre-commit (Husky/lint-staged), and CI.
  • Adds and enforces @/ path alias via TS config + Vite/Vitest config, and bulk-updates imports accordingly.
  • Applies Prettier formatting across UI source, tests, and E2E specs; includes small lint-driven fixes (e.g., explicit type on <button>).

Reviewed changes

Copilot reviewed 89 out of 92 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
ui/vitest.config.ts Adds @ alias for Vitest and normalizes config formatting.
ui/vite.config.ts Adds @ alias for Vite and reformats config code.
ui/tsconfig.json Adds baseUrl + paths mapping for @/* to src/*.
ui/src/pages/WorkflowsPage.tsx Converts imports to @/ and applies Prettier/a11y formatting updates.
ui/src/pages/TracesPage.tsx Converts imports to @/ and applies Prettier formatting + explicit button types.
ui/src/pages/LoginPage.tsx Converts imports to @/ and applies formatting + explicit button types.
ui/src/pages/JobsPage.tsx Converts imports to @/ and applies formatting + explicit button types.
ui/src/pages/JobDetailPage.tsx Converts imports to @/ and applies formatting + explicit button types.
ui/src/pages/CredentialsPage.tsx Converts imports to @/ and applies formatting + explicit button types.
ui/src/main.tsx Switches to @/ imports for App/CSS/generated client.
ui/src/index.css Normalizes quoting and formatting; updates selectors to match style rules.
ui/src/hooks/useUpdateCheck.ts Formatting + semver helper formatting; no functional change intended.
ui/src/hooks/usePendingRequests.ts @/ import conversion + formatting for request aggregation hook.
ui/src/hooks/useAuth.ts @/ import conversion + formatting for auth/setup gating.
ui/src/components/ui/PermissionRuleEditor.tsx @/ type import conversion + formatting; adds missing button type.
ui/src/components/ui/PermissionRuleDisplay.tsx @/ type import conversion + formatting.
ui/src/components/ui/OneTimeKeyDisplay.tsx Formatting cleanup and minor JSX restructuring.
ui/src/components/ui/Logo.tsx Formats SVG markup; no functional change intended.
ui/src/components/ui/ConfirmInline.tsx Formatting cleanup; clearer inline handler blocks.
ui/src/components/ui/Card.tsx Formatting cleanup; JSX kept equivalent.
ui/src/components/ui/Button.tsx Adds default type="button"; formatting and spinner markup expansion.
ui/src/components/ui/Badge.tsx Formatting cleanup and minor JSX reflow.
ui/src/components/layout/TopBar.tsx @/ import conversion + explicit button types + formatting.
ui/src/components/AuthGuard.tsx @/ import conversion + formatting.
ui/src/tests/test-utils.tsx Reorders imports; moves MSW imports to top and formats helpers.
ui/src/tests/setup.ts Formatting cleanup for MSW lifecycle hooks.
ui/src/tests/pages/WorkflowsPage.test.tsx Converts page import to @/ and applies formatting.
ui/src/tests/pages/TracesPage.test.tsx Converts page import to @/ and applies formatting.
ui/src/tests/pages/ToolkitsPage.test.tsx Converts page import to @/ and applies formatting.
ui/src/tests/pages/SetupPage.test.tsx Converts page import to @/ and applies formatting.
ui/src/tests/pages/SearchPage.test.tsx Converts page import to @/ and applies formatting.
ui/src/tests/pages/OAuthBrokersPage.test.tsx Converts page import to @/ and applies formatting.
ui/src/tests/pages/LoginPage.test.tsx Converts page import to @/ and applies formatting.
ui/src/tests/pages/JobsPage.test.tsx Converts page import to @/ and applies formatting.
ui/src/tests/pages/DashboardPage.test.tsx Converts page import to @/ and applies formatting.
ui/src/tests/pages/CredentialFormPage.test.tsx Converts page import to @/ and applies formatting.
ui/src/tests/pages/CatalogPage.test.tsx Converts page import to @/ and applies formatting.
ui/src/tests/pages/ApprovalPage.test.tsx Converts page import to @/ and applies formatting.
ui/src/tests/mocks/handlers.ts Formatting cleanup for default MSW handlers.
ui/src/tests/mocks/browser.ts Formatting cleanup for MSW worker bootstrap.
ui/src/tests/hooks/useUpdateCheck.test.ts Keeps test logic but reformats helper functions.
ui/src/tests/components/ConfirmInline.test.tsx Converts component import to @/ and applies formatting.
ui/src/tests/components/Card.test.tsx Converts component import to @/ and applies formatting.
ui/src/tests/components/Button.test.tsx Converts component import to @/ and applies formatting.
ui/src/tests/components/Badge.test.tsx Converts component import to @/ and applies formatting.
ui/src/tests/components/AuthGuard.test.tsx Converts component import to @/ and applies formatting.
ui/src/App.tsx Converts route imports to @/ for pages/components.
ui/prettier.config.js Adds Prettier configuration (Tailwind plugin, tabs, single quotes, etc.).
ui/playwright.docker.config.ts Formatting cleanup for Docker Playwright config.
ui/playwright.config.ts Formatting cleanup for Playwright config.
ui/package.json Adds eslint/prettier/husky/lint-staged scripts + devDependencies and lint-staged config.
ui/index.html Prettier formatting for HTML + attribute normalization.
ui/eslint.config.js Adds ESLint 9 flat config with TS/React/import/a11y rules and ignores.
ui/e2e/workflows.spec.ts Formatting cleanup for Playwright specs.
ui/e2e/traces.spec.ts Formatting cleanup for Playwright specs.
ui/e2e/toolkits.spec.ts Formatting cleanup for Playwright specs.
ui/e2e/search.spec.ts Formatting cleanup for Playwright specs.
ui/e2e/oauth-brokers.spec.ts Formatting cleanup for Playwright specs.
ui/e2e/jobs.spec.ts Formatting cleanup for Playwright specs.
ui/e2e/docker/setup.spec.ts Formatting cleanup for Docker E2E setup spec.
ui/e2e/docker/auth-and-search.spec.ts Formatting cleanup for Docker E2E auth/search spec.
ui/e2e/dashboard.spec.ts Formatting cleanup for Playwright dashboard spec.
ui/e2e/credentials.spec.ts Formatting cleanup for Playwright credentials specs.
ui/e2e/catalog.spec.ts Formatting cleanup for Playwright catalog specs.
ui/e2e/auth.spec.ts Formatting cleanup for Playwright auth specs.
ui/e2e/approval.spec.ts Formatting cleanup for Playwright approval specs.
ui/TESTING.md Prettier formatting and updated test guidance formatting.
ui/BUILD_REPORT.md Markdown formatting tweaks and updated CI/tooling notes.
ui/.prettierignore Adds Prettier ignore list for build artifacts and generated code.
ui/.editorconfig Adds repo-wide editor defaults aligned to Prettier (tabs, 4 spaces, etc.).
.husky/pre-commit Adds pre-commit hook to run lint-staged in ui/.
.github/workflows/ci-ui.yml Adds format:check and lint steps before TypeScript check.
.claude/CLAUDE.md Documents new formatting/linting scripts and rules.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +2 to +11
export default {
useTabs: true,
tabWidth: 4,
singleQuote: true,
semi: true,
trailingComma: 'all',
printWidth: 100,
plugins: ['prettier-plugin-tailwindcss'],
tailwindFunctions: ['clsx'],
};
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

This file uses ESM syntax (export default). If ui/package.json is not configured as ESM (\"type\": \"module\") and the config file is not .mjs, Node will parse this as CommonJS and Prettier will fail to load it (syntax error). Fix by either converting the config to CommonJS (module.exports = { ... }) or renaming to prettier.config.mjs (or setting \"type\": \"module\" in ui/package.json).

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +12
import { defineConfig, globalIgnores } from 'eslint/config';
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import pluginReact from 'eslint-plugin-react';
import pluginReactHooks from 'eslint-plugin-react-hooks';
import pluginImportX from 'eslint-plugin-import-x';
import pluginUnusedImports from 'eslint-plugin-unused-imports';
import pluginJsxA11y from 'eslint-plugin-jsx-a11y';
import eslintConfigPrettier from 'eslint-config-prettier';
import globals from 'globals';

export default defineConfig(
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

This ESLint config uses ESM import syntax. Unless the UI package is ESM (\"type\": \"module\" in ui/package.json) or the config is renamed to .mjs, Node will treat eslint.config.js as CommonJS and ESLint will fail to load it. Fix by either switching to CommonJS (const ... = require(...) + module.exports) or renaming the file to eslint.config.mjs / setting \"type\": \"module\".

Suggested change
import { defineConfig, globalIgnores } from 'eslint/config';
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import pluginReact from 'eslint-plugin-react';
import pluginReactHooks from 'eslint-plugin-react-hooks';
import pluginImportX from 'eslint-plugin-import-x';
import pluginUnusedImports from 'eslint-plugin-unused-imports';
import pluginJsxA11y from 'eslint-plugin-jsx-a11y';
import eslintConfigPrettier from 'eslint-config-prettier';
import globals from 'globals';
export default defineConfig(
const { defineConfig, globalIgnores } = require('eslint/config');
const js = require('@eslint/js');
const tseslint = require('typescript-eslint');
const pluginReact = require('eslint-plugin-react');
const pluginReactHooks = require('eslint-plugin-react-hooks');
const pluginImportX = require('eslint-plugin-import-x');
const pluginUnusedImports = require('eslint-plugin-unused-imports');
const pluginJsxA11y = require('eslint-plugin-jsx-a11y');
const eslintConfigPrettier = require('eslint-config-prettier');
const globals = require('globals');
module.exports = defineConfig(

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +68
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['../*'],
message: 'Use @/ absolute imports instead of relative parent paths.',
},
],
},
],
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The restricted import pattern ../* only matches a single path segment (e.g., ../foo) and will not match common parent imports like ../foo/bar or deeper paths like ../../x. If the intent is to enforce @/ for all parent-path imports, expand the patterns (e.g., ../**, ../../**, etc.), or use a broader pattern that covers any ..-prefixed import source.

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +41
type="button"
disabled={disabled || loading}
className={`inline-flex cursor-pointer items-center justify-center rounded-lg font-medium transition-all ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}
{...props}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The component always adds cursor-pointer, which can mislead users when the button is disabled (cursor still indicates clickability). Consider removing cursor-pointer entirely (letting native/button-disabled styling apply), or applying it conditionally only when the button is not disabled/loading.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +27
const toolkits = await api.listToolkits();
const results: Array<Record<string, unknown> & { toolkit_name: string }> = [];
for (const t of toolkits) {
try {
const reqs = await api.listAccessRequests(t.id, 'pending');
if (Array.isArray(reqs)) {
for (const r of reqs) {
if (r.status === 'pending') {
results.push({ ...r, toolkit_name: t.name });
}
}
}
} catch {
// ignore per-toolkit errors
}
}
return results;
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

This performs toolkit access-request fetches sequentially (one network request per toolkit, awaited in a loop), which can be noticeably slow with many toolkits. Consider parallelizing with Promise.all (and filtering successful responses) to reduce total wait time while preserving the per-toolkit error isolation.

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +40
<a href="/" className="shrink-0">
<JenticLogo />
</a>
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

Using a raw <a href=\"/\"> for internal navigation triggers a full page reload in an SPA and will also match the new no-restricted-syntax rule that warns against internal <a href=\"/...\"> usage. Prefer a router <Link to=\"/\"> here to preserve client-side navigation.

Copilot uses AI. Check for mistakes.
Replace eslint-config-prettier with eslint-plugin-prettier/recommended
so formatting violations surface as ESLint errors. This simplifies the
toolchain to a single lint command for both code quality and formatting.

Made-with: Cursor
@Manuel-Jentic
Copy link
Copy Markdown
Contributor Author

Copilot review — responses

1. prettier.config.js ESM syntax & 2. eslint.config.js ESM syntax

Not an issueui/package.json has "type": "module" (line 5), so ESM import/export syntax is correct and works natively. No rename or CJS conversion needed.

3. no-restricted-imports pattern ../* only matches single segment

Not an issue — tested locally. ESLint's no-restricted-imports treats ../* as a prefix match, not a single-segment glob:

import a from '../foo';              → caught ✅
import b from '../../foo/bar';       → caught ✅
import c from '../../../deep/path';  → caught ✅
import d from './sibling';           → allowed ✅ (same-dir relative)
import e from '@/components/Button'; → allowed ✅ (@/ alias)

All parent-path depths are correctly blocked.

4. cursor-pointer on disabled Button

Valid but pre-existing — not introduced by this PR (we only added the type="button" attribute and reformatted). Will address conditional cursor-pointer in the upcoming UI component library PR.

5. Sequential fetches in usePendingRequests

Valid but pre-existing — only formatting was applied to this file, no logic changes. Tracked as a performance improvement for a future PR (Promise.all parallelization).

6. Raw <a href="/"> in TopBar

Valid but pre-existing — should use <Link to="/"> from react-router-dom. Our new no-restricted-syntax ESLint rule already warns about this pattern. Will fix in the UI component library PR.

@Manuel-Jentic
Copy link
Copy Markdown
Contributor Author

Update: Prettier integrated into ESLint

Thanks @char0n for the suggestion to integrate Prettier into ESLint — pushed in 2b3990b.

What changed:

  • Replaced eslint-config-prettier (only disables conflicting rules) with eslint-plugin-prettier/recommended (runs Prettier as an ESLint rule)
  • Formatting violations now surface as prettier/prettier ESLint errors, auto-fixable with eslint --fix

Advantages:

  • Simpler toolchainnpm run lint checks both code quality and formatting in one pass; npm run lint:fix fixes both
  • Fewer scripts — removed separate format and format:check npm scripts
  • Simpler CI — one step instead of two (format check + lint → single lint step)
  • Simpler pre-commit — lint-staged runs eslint --fix only instead of prettier --write + eslint --fix
  • Consistent with internal Jentic tooling — aligns with the pattern used in jentic-arazzo-tools

prettier.config.js is kept since eslint-plugin-prettier reads it for formatting rules, and prettier --write still runs directly for non-TS files (JSON, CSS, MD) via lint-staged.

@Manuel-Jentic Manuel-Jentic changed the title chore: add Prettier, ESLint 9, Husky, and @/ import aliases chore(lint): add Prettier, ESLint 9, Husky, and @/ import aliases Apr 1, 2026
@Manuel-Jentic Manuel-Jentic merged commit 181d9da into main Apr 1, 2026
5 checks passed
@Manuel-Jentic Manuel-Jentic deleted the chore/formatting-linting branch April 1, 2026 13:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants