Files
voidea/webui/STYLE_GUIDE.md
T

185 lines
6.8 KiB
Markdown

# VoIdea WebUI Style Guide
## Tech Stack
| Category | Choice | Version |
|----------|--------|---------|
| Framework | React | 18.3.x (LTS) |
| Language | TypeScript | 5.5+ (strict mode) |
| Routing | react-router-dom | 6.26.x |
| Styling | Tailwind CSS | 3.4.x |
| State | Zustand (new stores) + Context (existing) | 5.x |
| Forms | react-hook-form + zod | latest |
| i18n | react-i18next (future) / constants/strings.ts (now) | — |
| Testing | Vitest + @testing-library/react | latest |
| PWA | vite-plugin-pwa | 0.20.x |
| Build | Vite | 5.4.x |
## Project Structure
```
webui/src/
├── api/ # HTTP client + endpoint modules
├── auth/ # Zustand store (future), Context (migration target)
├── components/ # Shared UI (ErrorBoundary, Layout, VoiceChat, etc.)
├── constants/ # strings.ts (i18n-ready), enums
├── hooks/ # Custom hooks
├── pages/ # Route pages (one file per route)
├── stores/ # Zustand stores (auth, ideas, settings...)
├── types/ # Shared TypeScript types
├── utils/ # Pure utility functions
├── App.tsx # Router setup
└── main.tsx # Entry point
```
## Coding Rules
### General
- `any` запрещён. Всегда явный тип.
- Импорты: абсолютные через `@/` alias. Относительные только для соседних файлов.
- Файл — не более 300 строк. Сервисы/компоненты больше — разбить.
- Prettier: 100 символов, двойные кавычки, точка с запятой.
### State Management (Zustand)
```ts
// stores/auth.ts
import { create } from "zustand";
interface AuthState {
user: User | null;
token: string | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
export const useAuthStore = create<AuthState>((set) => ({
user: null,
token: null,
login: async (email, password) => { /* ... */ },
logout: () => { clearTokens(); set({ user: null, token: null }); },
}));
```
- Один store — одна доменная область (auth, ideas, settings, voice).
- Store НЕ содержит UI-логику (только состояние + действия).
- Использовать `useAuthStore()` в компонентах напрямую (без Provider).
- Context API — только для редких случаев (< 3 подписчиков, Legacy).
### Forms (react-hook-form + zod)
```ts
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
type FormData = z.infer<typeof schema>;
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(schema),
});
```
- Каждая сложная форма (3+ поля) — свой schema-файл в `pages/<name>.schema.ts`.
- Простые формы (1–2 поля, логин) — schema внутри компонента.
- Ошибки выводить `<span className="text-red-500 text-sm">`.
- Кнопку сабмита дизейблить пока `isSubmitting`.
### Accessibility (WCAG AA)
**Обязательно:**
- `aria-label` на всех кнопках без текста (иконки: Menu, ThemeToggle, Logout, Close, VoiceChat mic)
- `htmlFor` + `id` на всех `<label>`/`<input>` парах
- `role="log"` + `aria-live="polite"` на VoiceChat
- `<SkipToContent />` — первая ссылка в `<body>`
- Focus trap в модалках (первый элемент при открытии, возврат при закрытии)
- Цветовой контраст ≥ 4.5:1 (проверять `text-gray-500` на `bg-gray-800`)
**Проверка:** eslint-plugin-jsx-a11y (strict ruleset, errors = fail CI).
### Error Boundaries
- Глобальный `<ErrorBoundary>` вокруг `<Layout>` в App.tsx.
- Опционально: per-page ErrorBoundary для VoiceChat, AdminPage (высокая сложность).
- Fallback: `<ServerErrorPage />` с кнопкой «На главную».
```tsx
<ErrorBoundary fallback={<ServerErrorPage />}>
<Layout><Outlet /></Layout>
</ErrorBoundary>
```
### i18n-ready
Сейчас — только русский. Но строки НЕ пишутся хардкодом в JSX.
```ts
// constants/strings.ts
export const strings = {
nav: { ideas: "Идеи", new: "Новая", voice: "Голос" },
auth: { login: "Войти", register: "Регистрация" },
} as const;
```
```tsx
// Использование
<T path="auth.login" />
<T path="nav.ideas" />
```
При переходе на react-i18next — только замена реализации `<T>`, строки не трогать.
### ESLint Ruleset (для CI и будущих проектов)
**Плагины:**
- `@eslint/js` — базовые JS правила
- `typescript-eslint` — TS strict ruleset
- `eslint-plugin-jsx-a11y` — WCAG AA enforcement
**Ключевые правила:**
```js
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/strict-boolean-expressions": "error",
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
"jsx-a11y/alt-text": "error",
"jsx-a11y/aria-props": "error",
"jsx-a11y/aria-role": "error",
"jsx-a11y/label-has-associated-control": "error",
"jsx-a11y/click-events-have-key-events": "error",
"jsx-a11y/no-static-element-interactions": "warn",
"no-console": ["error", { allow: ["warn", "error"] }],
"prefer-const": "error",
"no-var": "error",
```
**Установка в новом проекте:**
```bash
npm install -D eslint @eslint/js typescript-eslint eslint-plugin-jsx-a11y
```
### Testing (Vitest)
- Имена файлов: `*.test.ts` / `*.test.tsx` рядом с тестируемым модулем.
- Smoke: каждая страница рендерится без падения.
- Store: каждый action тестируется (login, logout, create, delete).
- Формы: заполнить → submit → проверить вызов API.
- JSON-репортёр для QATesterAgent: `vitest run --reporter=json`.
- Минимальный coverage: 30% (только критические пути: auth, forms, ErrorBoundary).
## React Version Policy
- Фиксирована: React 18.3.x LTS
- Переход на React 19 — только когда:
1. `vite-plugin-pwa` обновит поддержку
2. Все зависимости совместимы
3. Отдельный этап миграции
## VPS Compatibility
Весь инструментарий (Zustand, react-hook-form, zod, Vitest, ESLint) — npm-пакеты, работают на любой OS. Сборка под VPS:
```bash
npm ci # чистая установка под Ubuntu
npm run build # Vite -> dist/
```