chore(lint): add Prettier, ESLint 9, Husky, and @/ import aliases#137
chore(lint): add Prettier, ESLint 9, Husky, and @/ import aliases#137Manuel-Jentic merged 10 commits intomainfrom
Conversation
- 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
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
There was a problem hiding this comment.
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
typeon<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.
| export default { | ||
| useTabs: true, | ||
| tabWidth: 4, | ||
| singleQuote: true, | ||
| semi: true, | ||
| trailingComma: 'all', | ||
| printWidth: 100, | ||
| plugins: ['prettier-plugin-tailwindcss'], | ||
| tailwindFunctions: ['clsx'], | ||
| }; |
There was a problem hiding this comment.
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).
| 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( |
There was a problem hiding this comment.
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\".
| 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( |
| 'no-restricted-imports': [ | ||
| 'error', | ||
| { | ||
| patterns: [ | ||
| { | ||
| group: ['../*'], | ||
| message: 'Use @/ absolute imports instead of relative parent paths.', | ||
| }, | ||
| ], | ||
| }, | ||
| ], |
There was a problem hiding this comment.
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.
| 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} |
There was a problem hiding this comment.
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.
| 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; |
There was a problem hiding this comment.
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.
| <a href="/" className="shrink-0"> | ||
| <JenticLogo /> | ||
| </a> |
There was a problem hiding this comment.
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.
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
Copilot review — responses1.
|
Update: Prettier integrated into ESLintThanks @char0n for the suggestion to integrate Prettier into ESLint — pushed in What changed:
Advantages:
|
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:
button-has-type,consistent-type-imports,no-restricted-imports@/path alias — all cross-directory imports use@/(maps tosrc/), enforced by ESLintno-restricted-importsruleformat:checkandlintsteps added toci-ui.ymlbefore TypeScript check (blocking)Bulk changes (can be skimmed):
../) converted to@/absolute importsreact/button-has-typeerrors fixed (explicittypeattribute on all<button>elements)jsx-a11y/label-has-associated-controlerrors fixed (labels → fieldset/legend)@typescript-eslint/no-unused-expressionserror fixed (ternary → if/else)Files to review (the important ones)
ui/eslint.config.jsglobalIgnoresui/prettier.config.jsui/.editorconfigui/.prettierignoreui/package.jsonlint-stagedconfig.husky/pre-commitui/vite.config.tsresolve.aliasfor@/ui/tsconfig.jsonbaseUrl+pathsfor@/.github/workflows/ci-ui.ymlFormat check+Lintsteps.claude/CLAUDE.mdThe 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 towarn(noterror) for gradual cleanup. CI passes with warnings. No--max-warnings 0— that would block all PRs until 143+ instances are cleaned up.Next steps
Button,Badge,Card,Input,Select, etc.). Once the library has replacements for raw HTML elements, the ESLint config already has theno-restricted-syntaxrule ready to enforce usage of<Button>over<button>,<Link>over<a>, etc. — just needs escalating fromwarntoerrorand adding new selectors for<input>,<select>, etc.anycleanup — address the 143 warnings over time, then consider--max-warnings 0in CITest plan
npx tsc --noEmit— zero TypeScript errorsnpx prettier --check .— all files formattednpx eslint .— 0 errors, 143 warnings (pre-existing)npx vite build— production build succeedsMade with Cursor