Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

## Verification

<!-- List the commands or checks you ran. For example: cd backend && go test ./..., cd frontend && pnpm lint, cd frontend && pnpm build. -->
<!-- List the commands or checks you ran. For example: cd backend && go test ./..., cd frontend && pnpm check, cd frontend && pnpm build. -->

- [ ] Not run; reason:

Expand Down
56 changes: 56 additions & 0 deletions .github/workflows/frontend-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Frontend Quality

on:
push:
branches:
- main
- dev
paths:
- "frontend/**"
- "scripts/sync-version.mjs"
- "VERSION"
- ".github/workflows/frontend-quality.yml"
pull_request:
branches:
- main
- dev
paths:
- "frontend/**"
- "scripts/sync-version.mjs"
- "VERSION"
- ".github/workflows/frontend-quality.yml"
workflow_dispatch:

permissions:
contents: read

jobs:
quality:
name: Biome and TypeScript
runs-on: ubuntu-24.04
defaults:
run:
working-directory: frontend

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: 10.17.0
run_install: false

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
cache-dependency-path: frontend/pnpm-lock.yaml

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run frontend checks
run: pnpm check
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ web_modules/
# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

Expand Down Expand Up @@ -165,4 +162,4 @@ frontend/public/pwa/generated/
docker-compose.local.yml

.DS_Store
.idea
.idea
34 changes: 34 additions & 0 deletions frontend/BIOME.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Frontend Biome policy

DEEIX Chat uses Biome as the frontend linter. TypeScript 7 remains responsible for type checking; ESLint, `typescript-eslint`, and `eslint-config-next` are not part of the frontend toolchain.

## Commands

```bash
pnpm check
pnpm typecheck
pnpm lint
pnpm lint:fix
```

`pnpm check` is the required non-mutating local and CI gate. It runs `pnpm lint` followed by `pnpm typecheck`. `lint:fix` applies safe Biome lint fixes only.

Formatting is intentionally outside this migration. Enabling a repository-wide formatter requires a separate mechanical baseline so lint-tooling changes remain reviewable and do not rewrite unrelated application files.

## Rule policy

Biome's recommended preset and the recommended React and Next.js domains are enabled in `biome.jsonc`. Project-specific deviations are listed explicitly so an upgrade cannot silently change the pull request gate.

- `error`: correctness, React hook ordering, duplicate JSX props, invalid React attributes, unsafe script usage, and concrete security defects. Errors fail `pnpm lint`.
- `warn`: accessibility guidance, exhaustive hook dependencies, framework performance guidance, and Next.js conventions that can require project-specific exceptions. Warnings stay visible without blocking an otherwise valid build.
- `off`: only for rules that are not reliable with the project's component abstractions. `useAriaPropsSupportedByRole` is disabled because Radix and polymorphic controls expose roles and ARIA attributes through runtime composition that Biome cannot resolve statically. `performance/noImgElement` is disabled because the application deliberately renders administrator-configured provider icons, arbitrary Markdown/tool images, and local previews whose URLs cannot be declared in a fixed Next image domain list.

Rule exceptions use file-scoped Biome overrides only for stable framework/library contracts. Inline suppressions are reserved for a single unavoidable statement and must include a reason. File-wide blanket disables and category-wide exceptions are not accepted.

The Animate UI registry owns `components/animate-ui/icons/icon.tsx` and exports its hook-backed helper as `getVariants`. That upstream API name is preserved so newly downloaded or overwritten registry components remain compatible; the Hook naming rule is disabled only for that exact registry-owned file.

## Migration coverage

Biome covers the correctness, hooks, accessibility, security, and performance rules that can be mapped from the former Next.js ESLint configuration. Biome 2.5 does not yet implement every React Compiler rule or every Next.js-specific rule. TypeScript and Next.js builds remain required checks, and unsupported rules should be reconsidered when Biome adds native equivalents.

Notable unsupported groups include React Compiler diagnostics such as immutability, refs, purity, set-state-in-render, and static component analysis, plus Next.js rules for page-specific HTML, Head, Script, and relative `location` assignments. These gaps are documented rather than hidden behind a second TypeScript runtime.
6 changes: 4 additions & 2 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,14 @@ NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8080

```bash
pnpm dev
pnpm check
pnpm lint
pnpm lint:fix
pnpm build
pnpm start
```

当前 `package.json` 未配置单独的 `typecheck` 脚本,需要类型检查时使用框架构建或临时执行 TypeScript 检查命令
`check` 依次运行 Biome 静态检查和 TypeScript 7 类型检查。规则范围、严重级别和框架例外见 [BIOME.md](./BIOME.md)

## 开发约束

Expand All @@ -170,7 +172,7 @@ pnpm start
## 提交前验证

```bash
pnpm lint
pnpm check
```

涉及构建、路由、依赖或 Next.js 配置变更时再执行:
Expand Down
178 changes: 178 additions & 0 deletions frontend/biome.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
{
"$schema": "https://biomejs.dev/schemas/2.5.4/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": true,
"includes": [
"**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}",
"!node_modules",
"!.next",
"!out",
"!build",
"!next-env.d.ts",
"!public/sw.js",
"!public/vendor",
"!shared/generated"
]
},
"formatter": {
"enabled": false
},
"linter": {
"enabled": true,
"domains": {
"next": "recommended",
"react": "recommended"
},
"rules": {
"preset": "recommended",
"a11y": {
"noLabelWithoutControl": "off",
"noStaticElementInteractions": "off",
"noSvgWithoutTitle": "off",
"useAriaPropsSupportedByRole": "off",
"useSemanticElements": "off"
},
"correctness": {
"noUnusedFunctionParameters": "off",
"useExhaustiveDependencies": {
"level": "warn",
"options": {
"reportUnnecessaryDependencies": false
}
},
"useImageSize": "off"
},
"nursery": {
"noComponentHookFactories": "error",
"noReactStringRefs": "error"
},
"performance": {
"noBarrelFile": "off",
"noImgElement": "off",
"noNamespaceImport": "off",
"useTopLevelRegex": "off"
},
"suspicious": {
"noAlert": "off",
"noArrayIndexKey": "off",
"noDocumentCookie": "off",
"noEmptyBlockStatements": "off",
"noShadowRestrictedNames": "off",
"useAwait": "off"
},
"complexity": {
"noExcessiveCognitiveComplexity": "off",
"noUselessFragments": "off",
"noUselessSwitchCase": "off",
"useOptionalChain": "off"
},
"style": {
"noNestedTernary": "off",
"noNonNullAssertion": "off",
"useBlockStatements": "off",
"useConsistentTypeDefinitions": "off",
"useConst": "off",
"useImportType": "off"
}
}
},
"overrides": [
{
"includes": ["components/animate-ui/icons/icon.tsx"],
"linter": {
"rules": {
"correctness": {
"useExhaustiveDependencies": "off",
"useHookAtTopLevel": "off"
}
}
}
},
{
"includes": ["components/animate-ui/utils/get-strict-context.tsx"],
"linter": {
"rules": {
"nursery": {
"noComponentHookFactories": "off"
}
}
}
},
{
"includes": ["shared/components/file-preview/preview-docx.tsx"],
"linter": {
"rules": {
"suspicious": {
"noUnknownAttribute": "off"
}
}
}
},
{
"includes": [
"components/ui/live-waveform.tsx",
"components/ui/virtual-table.tsx",
"shared/components/file-preview/preview-document.tsx"
],
"linter": {
"rules": {
"a11y": {
"noAriaHiddenOnFocusable": "off"
}
}
}
},
{
"includes": [
"features/admin/components/sections/billing/billing-redemption.tsx",
"features/chat/components/message/message-meta.tsx"
],
"linter": {
"rules": {
"a11y": {
"noNoninteractiveTabindex": "off"
}
}
}
},
{
"includes": [
"features/chat/components/sections/chat-input.tsx",
"features/layouts/components/navigation/sidebar-conversation-item.tsx",
"features/settings/components/sections/chat/settings-chat.tsx"
],
"linter": {
"rules": {
"a11y": {
"noAutofocus": "off"
}
}
}
},
{
"includes": ["shared/components/file-preview/preview-media.tsx"],
"linter": {
"rules": {
"a11y": {
"useMediaCaption": "off"
}
}
}
},
{
"includes": ["components/ui/input-group.tsx"],
"linter": {
"rules": {
"a11y": {
"useKeyWithClickEvents": "off"
}
}
}
}
]
}
1 change: 0 additions & 1 deletion frontend/components/animate-ui/icons/arrow-right.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import * as React from 'react';
import { motion, type Variants } from 'motion/react';

import {
Expand Down
1 change: 0 additions & 1 deletion frontend/components/animate-ui/icons/audio-lines.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import * as React from 'react';
import { motion, type Variants } from 'motion/react';

import {
Expand Down
1 change: 0 additions & 1 deletion frontend/components/animate-ui/icons/binary.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import * as React from 'react';
import { motion, type Variants } from 'motion/react';

import {
Expand Down
1 change: 0 additions & 1 deletion frontend/components/animate-ui/icons/blend.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import * as React from 'react';
import { motion, type Variants } from 'motion/react';

import {
Expand Down
1 change: 0 additions & 1 deletion frontend/components/animate-ui/icons/blocks.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import * as React from 'react';
import { motion, type Variants } from 'motion/react';

import {
Expand Down
1 change: 0 additions & 1 deletion frontend/components/animate-ui/icons/brush.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import * as React from 'react';
import { motion, type Variants } from 'motion/react';

import {
Expand Down
1 change: 0 additions & 1 deletion frontend/components/animate-ui/icons/check.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import * as React from 'react';
import { motion, type Variants } from 'motion/react';

import {
Expand Down
1 change: 0 additions & 1 deletion frontend/components/animate-ui/icons/chevron-down.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import * as React from 'react';
import { motion, type Variants } from 'motion/react';

import {
Expand Down
1 change: 0 additions & 1 deletion frontend/components/animate-ui/icons/chevron-left.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import * as React from 'react';
import { motion, type Variants } from 'motion/react';

import {
Expand Down
1 change: 0 additions & 1 deletion frontend/components/animate-ui/icons/chevron-right.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import * as React from 'react';
import { motion, type Variants } from 'motion/react';

import {
Expand Down
1 change: 0 additions & 1 deletion frontend/components/animate-ui/icons/chevron-up.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import * as React from 'react';
import { motion, type Variants } from 'motion/react';

import {
Expand Down
Loading
Loading