description: "Используй при создании, изменении, форматировании или ревью frontend-кода и физическом оформлении уже спроектированных SLM-модулей по code style и style guide. Триггеры: JS, TS, JSX, TSX, HTML, CSS, .js, .ts, .jsx, .tsx, .module.css, React-компонент, Provider, Guard, Error Boundary, props, components/, providers/, modules/, ErrorCode, код ошибки, SCREAMING_SNAKE_CASE, стили, CSS Modules, JSDoc, документация, именование, импорты/экспорты, типизация, отформатировать по гайду, поправить кодстайл. НЕ используй для выбора владельца, слоя, модульной границы или public API по SLM, генерации .templates, Next.js routing/data fetching/rendering, настройки PostCSS/tooling, backend-кода и вопросов без правки frontend-кода; в смешанной задаче сначала примени slm-design."
Сначала определи архитектурную роль единицы по правилам `slm-design`. Этот раздел не выбирает владельца и не решает, нужен компонент или вложенный модуль: он задаёт обязательный путь после принятого архитектурного решения.
### Сопоставление единиц с каталогами
- Вложенные framework-компоненты, кроме `Provider`, размещай в `components/`.
-`Guard` и `Error Boundary` считай framework-компонентами и размещай в `components/`.
- Вложенные `Provider` размещай в `providers/`, а не в `components/`.
- Вложенные SLM-модули размещай в `modules/`.
- Внутри модуля не используй `ui/`, `parts/` или другой каталог как альтернативное имя для `components/`, `providers/` или `modules/`.
-Не создавай эти каталоги заранее: добавляй каталог, когда в модуле появляется соответствующая единица.
```text
checkout/
├── index.ts
├── checkout.tsx
├── styles/
├── types/
├── components/
│ ├── order-summary/
│ ├── checkout-guard/
│ └── checkout-error-boundary/
├── providers/
│ └── checkout-events-provider/
└── modules/
└── form-session/
```
### Корневой framework-файл
Один главный framework-файл модуля может находиться непосредственно в корне, если он реализует или собирает ответственность модуля. Исключение распространяется и на главный `Provider`.
Корневой файл не дублируй в `components/` или `providers/`. Остальные компоненты и providers этого модуля размещай в соответствующих каталогах.
### Ограничение вложенности
- Единицы внутри `components/` и `providers/` являются соседями относительно модуля.
- Каталог компонента или Provider не содержит собственные `components/`, `providers/` или `modules/`.
- Следующий структурный уровень создавай только через вложенный модуль в `modules/`.
- Runtime-вложенность JSX не повторяй файловой вложенностью: компонент может рендерить другой компонент, оставаясь его файловым соседом.
### Чеклист структуры SLM
- Архитектурная роль определена до выбора каталога.
- Компоненты, Guards и Error Boundaries находятся в `components/`.
- Вложенные Providers находятся в `providers/`.
- Вложенные модули находятся в `modules/`.
- Главный framework-файл является единственной корневой implementation-единицей модуля.
- Компонентные каталоги не создают рекурсивную файловую иерархию.
Применяй этот раздел к `.js`, `.ts` и JS/TS-частям `.jsx` и `.tsx`. Для React-компонентов дополнительно применяй раздел `React и TSX`, для разметки - раздел `HTML, JSX и TSX`.
### Строки
- Для обычных строк используй одинарные кавычки.
- Строки в обратных кавычках используй только для интерполяции `${...}` или многострочного текста.
```ts
const label = 'Сохранить';
const title = `Привет, ${name}`;
```
### Импорты и экспорты
-В именованных импортах ставь пробелы внутри фигурных скобок: `import { User } from './user'`.
- Используй `import type`, если импорт нужен только на уровне типов.
- Для собственного кода предпочитай именованные экспорты.
-`default export` используй только когда это требует framework, tooling или устоявшийся локальный стандарт.
-`default import` допустим для сторонних библиотек, CSS Modules и framework API.
- Избегай namespace imports вида `import * as api`, если библиотека или локальный стандарт этого не требует.
-Не импортируй глубже публичного API модуля, если проектная архитектура это запрещает.
-Не меняй направление импортов ради удобства. Для архитектурных ограничений используй `slm-design`.
-Не меняй порядок, путь или тип импортов только ради форматирования, кроме автоформатирования существующим formatter/linter.
-Не создавай новый barrel или public API без понимания архитектурной границы.
- Экспорт типов оформляй через `export type`, если экспортируется только тип.
```ts
import { createUser } from './create-user';
import type { User } from './user.type';
export { UserCard } from './user-card';
export type { UserCardProps } from './types/user-card-props.type';
```
### Объекты, массивы, коллекции и вызовы
- Короткие объекты, массивы и вызовы оставляй в одну строку, если они читаются.
-В многострочном объекте размещай каждое свойство на новой строке.
-В многострочном массиве размещай каждый элемент на новой строке.
-В длинном вызове функции размещай каждый аргумент или смысловую группу на новой строке.
-В однострочных объектах и массивах ставь пробелы после запятых.
- Массивы называй во множественном числе.
- Списки идентификаторов называй с суффиксом `Ids`.
- Словари и мапы называй с суффиксом `ById`, `Map` или `Dict`.
```ts
const roles = ['admin', 'editor', 'viewer']
const options = { id: 1, name: 'User' }
const userIds = ['u1', 'u2']
const usersById = {} as Record<string,User>
const config = createRequestConfig(
endpoint,
{
headers: {
'X-Request-Id': requestId,
'X-User-Id': userId
},
params: {
page,
pageSize,
sort: 'createdAt'
}
},
timeoutMs
)
```
### Точки с запятой и trailing comma
- По умолчанию не ставь точку с запятой в конце инструкций.
- По умолчанию не ставь trailing comma после последнего свойства объекта, элемента массива, аргумента или параметра.
- Если formatter, linter или локальный стиль файла задаёт другое поведение, следуй ему.
-Не делай отдельную правку только ради массового добавления или удаления точек с запятой и trailing comma вне затронутого кода.
### Early return
- Используй ранние возвраты для упрощения чтения.
- Избегай `else` после `return`.
```ts
const getName = (user?: { name: string }) => {
if (!user) {
return 'Гость';
}
return user.name;
};
```
### Булевые значения
- Булевые значения начинай с`is`, `has`, `can` или `should`.
-Не используй нейтральные имена вроде `ready`, `access`, `submit`, если по имени неясно, что это boolean.
```ts
const isReady = true;
const hasAccess = false;
const canSubmit = true;
const shouldRedirect = false;
```
### События и callback
- Обработчики внутри кода называй через `handle*`.
- Callback-параметры и callback-свойства называй через `on*`.
```ts
const handleSubmit = () => {
// ...
};
type FormOptions = {
onSubmit: () => void;
};
```
### TypeScript
- Указывай типы для параметров функций и компонентов.
- Для публичных функций указывай возвращаемый тип.
-Не полагайся на неявный вывод типов для публичных API и важных контрактов.
- Предпочитай `type` для описания сущностей и `interface` для расширяемых контрактов.
- Избегай `any` и `unknown` без необходимости.
-Не используй `// @ts-ignore`, кроме крайних случаев с явным комментарием причины.
- Если нужно временно подавить ошибку TypeScript, предпочитай `// @ts-expect-error`с объяснением причины.
- Используй `type` для props, DTO, view-model, unions, mapped types и композиции типов.
- Используй `interface`, когда контракт должен расширяться через `extends` или declaration merging.
-Не смешивай `type` и `interface` в одной области без причины.
```ts
export type UserCardProps = {
user: User;
onSelect: (userId: string) => void;
};
export interface StorageAdapter {
get(key: string): Promise<string|null>;
set(key: string, value: string): Promise<void>;
}
```
### Any и unknown
-`any` используй только как временную заглушку или при работе с внешним API, который невозможно типизировать сразу.
- Каждый `any` должен иметь понятную причину и план замены, если это не неизбежная граница интеграции.
-`unknown` используй на границах с внешними данными и обязательно сужай перед использованием.
```ts
const parseName = (value: unknown): string => {
if (typeof value === 'string') {
return value;
}
return '';
};
```
Плохо:
```ts
const parseName = (value: any) => value.trim();
```
### Predicates и narrowing
- Для runtime-проверок базовых значений используй `shared/lib/value-predicates`.
- Если в проекте нет `shared/lib/value-predicates`, создай библиотеку перед использованием predicates. Состав утилит, сигнатуры и поведение бери из `reference/value-predicates` внутри skill.
- Импортируй predicates из публичного API библиотеки, а не из внутренних файлов.
-`shared/lib/value-predicates` содержит только базовые проверки nullish, primitives, strings, arrays, objects и literal unions.
- Доменные guards вроде `isOrder`, `isCity`, `isUserError` размещай рядом с владельцем данных: business-модулем, mapper/source или infra-адаптером.
-Не размещай доменные guards в `shared/lib/value-predicates`.
Минимальная структура библиотеки:
```text
shared/lib/value-predicates/
├── index.ts
└── value-predicates.ts
```
```ts
import { hasOwn, isArrayOf, isNonEmptyArray, isRecord, isString } from 'shared/lib/value-predicates'
```
Predicates применяй, когда нужно проверить:
- значение определено или отсутствует;
- значение является `string`, `number` или `boolean`;
- строка является непустой;
- массив пустой или непустой;
-`unknown`-значение является массивом элементов нужной формы;
-`unknown`-значение является объектом-записью;
- значение входит в список допустимых литералов.
Для определения пустого или непустого списка не используй прямые проверки длины массива:
```ts
items?.length
items.length > 0
items.length !== 0
items.length === 0
!items.length
```
Используй predicates:
```ts
isEmptyArray(items)
isNonEmptyArray(items)
```
Для `unknown`-данных и внешних API-ответов не используй `Array.isArray(value)` как проверку формы элементов. Используй `isArrayOf`с item guard:
```ts
if (isArrayOf(value, isOrder)) {
// value: Order[]
}
```
Для object-like `unknown` используй `isRecord` и `hasOwn` перед чтением полей:
```ts
const isOrder = (value: unknown): value is Order => {
- Каждая именованная функция должна иметь многострочный JSDoc-комментарий.
- Под правило попадают `function name()`, `const name = () => {}`, `const name = function () {}` и именованные обработчики вроде `const handleSubmit = () => {}`.
- Комментарий функции описывает действие, результат, побочный эффект или важное ограничение.
```ts
/**
* Рекурсивно собирает дерево категорий из плоского списка.
*
* Группирует элементы по parentId, начиная с корневых категорий.
* Категории без родителя попадают в корень дерева.
- Документируй константу только если она является публичным контрактом, доменным ограничением, magic value или переиспользуемой конфигурацией.
- Обычные локальные константы не документируй.
```ts
/**
* Максимальное количество заказов на одной странице выдачи.
*
* Значение синхронизировано с ограничением backend API.
*/
export const MAX_ORDERS_PAGE_SIZE = 100
```
#### Enum
- Каждый `enum` должен иметь многострочный JSDoc-комментарий.
- Комментарий enum описывает назначение набора значений.
- Каждое значение enum должно иметь JSDoc-комментарий `/** ... */` непосредственно над значением.
- Комментарий значения enum описывает смысл значения или состояние, которое оно обозначает.
-Не отделяй значения enum пустой строкой только из-за JSDoc-комментария.
-Не оставляй значение enum без комментария, даже если оно кажется очевидным.
```ts
/**
* Состояние оплаты заказа.
*/
export enum PaymentStatus {
/** Оплата создана, но ещё не подтверждена провайдером. */
PENDING = 'pending',
/** Оплата успешно подтверждена провайдером. */
PAID = 'paid',
/** Оплата отклонена или завершилась ошибкой. */
FAILED = 'failed'
}
```
#### Что не документировать
-Не документируй обычные переменные и inline callback внутри вызовов вроде `useEffect`, `map`, `filter`, `reduce`, `forEach`, `then` или обработчиков API.
- Если inline callback требует пояснения, вынеси его в именованную функцию и задокументируй объявление.
Этот раздел описывает прикладную форму React framework-компонента после того, как архитектура уже определила его владельца и место. Правила распространяются на визуальные компоненты, Providers, Guards и Error Boundaries. Архитектурные границы компонентов, модулей, сегментов и public API определяет `slm-design`. Форму JSX-разметки дополнительно проверяй по разделу `HTML, JSX и TSX`.
- Функциональный компонент объявляй через `const` и именованный экспорт, если framework или локальный стандарт не требует другой формы.
- Нативный React Error Boundary разрешено объявлять классом, потому что React не предоставляет функциональный lifecycle для перехвата ошибок дочернего дерева.
- Callback props названы через `on*`, внутренние обработчики - через `handle*`.
- Named handlers и custom hooks документированы по правилам `JS/TS`; вызовы hooks и inline callbacks не документируются.
- Hooks вызваны только на верхнем уровне компонента или custom hook.
## HTML, JSX и TSX
Применяй этот раздел к HTML-разметке и JSX/TSX-деревьям. Для React-компонентов дополнительно применяй раздел `React и TSX`, для JS/TS-выражений внутри JSX/TSX - раздел `JS/TS`.
### Атрибуты и props
- HTML-теги и HTML-атрибуты пиши в нижнем регистре.
- Строковые значения HTML/JSX/TSX-атрибутов пиши в двойных кавычках.
- Динамические значения передавай через `{...}`.
- Статические boolean-атрибуты записывай без `={true}`.
- Динамические boolean-атрибуты записывай через выражение: `disabled={isSaving}`.
-Не добавляй пустые, дублирующиеся или неиспользуемые атрибуты.
- Props компонентов именуй по правилам `React и TSX`.
```tsx
<button
type="button"
disabled={isSaving}
onClick={handleSave}
>
Сохранить
</button>
```
### Формат элемента
- Короткий элемент с небольшим числом атрибутов или props можно оставлять в одну строку.
- Если атрибутов или props много, размещай каждый на отдельной строке.
- Закрывающую скобку многострочного элемента размещай на отдельной строке.
- Если элемент содержит вложенные элементы, размещай открывающий и закрывающий теги на отдельных строках.
-Не смешивай в одном участке разметки разные стили записи props без причины.
```tsx
<inputtype="text"name="email"/>
<UserCard
user={user}
isSelected={isSelected}
onSelect={handleSelect}
/>
```
### Условия и значения в разметке
-Не усложняй JSX/TSX условиями, которые можно подготовить в переменной до `return`.
-Не размещай составные runtime-условия непосредственно в JSX/TSX.
-Не используй тернарные выражения внутри JSX/TSX-разметки.
- Подготовь label, локальные данные, boolean-флаги, списки и ветки отображения до JSX.
-В JSX используй простые predicate gates или заранее подготовленные флаги.
- Если для JSX-условий нужны predicates, но в проекте нет `shared/lib/value-predicates`, сначала создай библиотеку по `reference/value-predicates`.
- Если ветвление большое, вынеси его в переменную, named function или отдельный компонент по правилам `React и TSX`.
- Render-переменные вида `profileContent`, `ordersContent`, `emptyStateContent` используй только когда ветка большая, повторяется или содержит несколько состояний.
- Если условие простое, не создавай render-переменную без необходимости.
-Не дублируй длинные пути к данным внутри JSX: вынеси значение в локальную переменную до `return`.
- Inline callback в JSX props не документируй; если ему нужно пояснение, вынеси в named handler.
Для локальных данных используй имена с суффиксами `Data`, `Items`, `List`:
```tsx
const ordersData = orders.data
const cityItems = citySuggestions.data
```
### Wrapper-элементы и семантика
-Не добавляй wrapper-элемент только ради форматирования.
-Не добавляй wrapper, если он меняет DOM-структуру, CSS-поведение или доступность.
-Не меняй HTML-семантику или ARIA в задаче на чистое форматирование.
- Если задача требует выбрать семантический тег, ARIA или поведение формы, решай это как отдельную semantic/accessibility-задачу, а не как style-only правку.
```tsx
return (
<sectionaria-labelledby={titleId}>
<h2id={titleId}>{title}</h2>
{children}
</section>
)
```
### Комментарии
-В HTML/JSX/TSX-разметке по умолчанию обходись без комментариев.
- Комментируй только хаки, внешние ограничения и неочевидные причины.
- JSX/TSX-комментарий оформляй только однострочно: `{/* Комментарий. */}`.
- HTML-комментарий оформляй только однострочно: `<!-- Комментарий. -->`.
-Не используй многострочные комментарии в HTML/JSX/TSX-разметке.
-Не оставляй комментарий в разметке, если можно дать блоку, компоненту или переменной понятное имя.
-Не комментируй очевидную структуру, семантику или назначение элемента.
### Чеклист HTML/JSX/TSX
- Атрибуты и props оформлены единообразно с локальным стилем файла.
- Строковые атрибуты используют двойные кавычки.
- Boolean-атрибуты записаны без лишнего `={true}`.
- Многострочный элемент читается без горизонтального скролла.
-В JSX/TSX-разметке нет тернарных выражений.
- JSX использует подготовленные локальные данные, `is*`, `has*`, `can*`, `should*` или простые predicate gates вместо составных runtime-условий в разметке.
- Conditional rendering списков использует `isEmptyArray`, `isNonEmptyArray` или `isArrayOf`, а не прямые `.length`-условия в JSX.
- Длинные пути к данным не дублируются внутри JSX: список выносится в локальную переменную до `return`.
- Render-переменные не создаются для простых условий без необходимости.
- Нет пустых, дублирующихся или случайных wrapper-элементов.
- Комментарии в HTML/JSX/TSX используются только для хаков, внешних ограничений и неочевидных причин.
## CSS
Этот канон описывает, как писать CSS без привязки к frontend-фреймворку, препроцессору или PostCSS-настройке. Он не описывает установку tooling, подключение плагинов и диагностику сборки.
### Базовый подход
- По умолчанию стили пишутся модульно: локальный UI-стиль живёт в CSS Module владельца.
- CSS Module принадлежит конкретному компоненту или модулю.
-Не импортируй CSS Module одного компонента или модуля в другой.
-Не выноси локальные классы компонента в global styles.
- Глобальные стили используй только для проектных основ: tokens, media, reset, typography, themes.
- Если стиль должен переиспользоваться, выноси переиспользуемую UI-сущность или token, а не общий CSS Module.
### CSS Modules
- Корневой класс CSS Module всегда называй `.root`.
- Обычные классы внутри CSS Module называй в `camelCase`.
-Не смешивай `camelCase`, BEM и `kebab-case` в одном CSS Module.
- Внешний `className` должен дополнять `.root`, а не заменять его.
- Локальный `@media (min-width: ...)` допустим только как исключение для уникального порога, который нужен одному конкретному компоненту и не подходит для общей media-шкалы.
- Локальный `@media (min-width: ...)` не отменяет Mobile First: это тоже расширение вверх от базового состояния.
-У локального breakpoint обязательно оставляй короткий комментарий с причиной исключения.
- Если breakpoint совпадает с общей шкалой или может понадобиться повторно, используй существующий custom media или добавь новый в `media.css`.
-`@media` пиши внутри селектора, который изменяется.
-Не пиши `@media` верхнего уровня с набором селекторов внутри.
- Если шкалы media не хватает, расширяй `media.css` как проектный стандарт, а не добавляй локальный breakpoint.
```css
.root {
display: grid;
grid-template-columns: 1fr;
@media (--md) {
grid-template-columns: repeat(2, 1fr);
}
/* Уникальный порог, где карточки этого блока помещаются в 3 колонки. */
@media (min-width: 68.75rem) {
grid-template-columns: repeat(3, 1fr);
}
}
```
Плохо:
```css
@media (--md) {
.root {
display: flex;
}
.title {
font-size: 20px;
}
}
.root {
@media (min-width: 48rem) {
display: flex;
}
}
.card {
display: flex;
/* Плохо: desktop-first откат вниз вместо Mobile First расширения вверх. */
@media (max-width: 47.9375rem) {
display: block;
}
}
```
### media.css
-Все custom media объявляй в отдельном файле `media.css`.
-`media.css` содержит только `@custom-media`.
-Не объявляй custom media внутри CSS Modules.
-Не дублируй breakpoints локально в компонентах.
- Имена ширины пиши короткой шкалой `--xs` ... `--3xl`.
- Имена высоты пиши с префиксом `--h-`.
- Значения ширины пиши через `min-width`, кроме `--xs` как диапазона меньше `--sm`.
- Значения высоты пиши через `min-height`.
- Используй `rem` для значений media; пиксельный эквивалент указывай комментарием.
Базовый список media:
```css
/* Ширина: Mobile First, кроме --xs. */
@custom-media --xs (max-width: 35.9375rem); /* до 575px */
- Цвета, радиусы, spacing и повторяемые смысловые значения используй через CSS variables.
- Локальное одноразовое значение можно оставить в CSS Module.
-Не выноси значение в token только ради форматирования.
-Не меняй token-систему в задаче на локальную CSS-правку.
-Не дублируй смысловое значение в нескольких CSS Modules, если оно уже является token.
```css
.root {
padding: var(--space-4);
border-radius: var(--radius-2);
background-color: var(--color-bg);
}
```
### Комментарии
- CSS-комментарий оформляй через `/* Комментарий. */`.
- Комментируй только неочевидные ограничения, hacks и причины.
-Не комментируй очевидные CSS-свойства.
- Удаляй устаревший комментарий при изменении поведения стиля.
### Чеклист CSS
- Локальный UI-стиль написан через CSS Module владельца.
- Корневой класс CSS Module называется `.root`.
- Классы CSS Module названы в `camelCase`, модификаторы - через `._modifier`.
- Одно CSS-свойство размещено на строку.
- Между правилами верхнего уровня и перед вложенными блоками есть пустая строка.
- Вложенность используется только для `@media`, псевдоклассов, псевдоэлементов и модификаторов.
- Mobile First соблюдён: базовое состояние написано без media query, расширения идут вверх через `@media (--*)`.
- Desktop-first и откаты вниз через `max-width` не используются.
- Breakpoints из общей шкалы используются через `@media (--*)`; локальные `min-width` есть только как обоснованные одноразовые исключения с комментарием причины.
-Все custom media объявлены в `media.css`.
-`media.css` содержит только `@custom-media`.
- Повторяемые смысловые значения оформлены через CSS variables.
- CSS-комментарии объясняют неочевидную причину, а не пересказывают свойства.