mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 13:36:36 +00:00
178 lines
13 KiB
Markdown
178 lines
13 KiB
Markdown
# AGENTS.md
|
||
|
||
Инструкции для агентов (и IDE-ассистентов) по работе с этим репозиторием.
|
||
|
||
## Стек
|
||
|
||
- **SvelteKit** (`web/`) — основное приложение, Svelte 5 (runes).
|
||
- Менеджер пакетов: **pnpm**. Запускать команды из `web/`, либо через
|
||
обёртки в корне: `pnpm build`, `pnpm dev` (это `pnpm --dir web ...`).
|
||
Не использовать npm.
|
||
- Сборка: `pnpm --dir web build`, проверка типов: `pnpm --dir web exec svelte-check --tsconfig ./tsconfig.json`,
|
||
тесты: `pnpm --dir web test` (Vitest), e2e: `pnpm --dir web test:e2e` (Playwright,
|
||
`web/e2e/`, свой webServer на `pnpm build` + `scripts/serve-static.mjs`, порт 4173).
|
||
- Форматирование: два независимых прогона. Корневой prettier форматирует
|
||
только markdown `docs/` (`.prettierrc` в корне, `pnpm format:docs`, проверка —
|
||
`pnpm check:docs`). Корневой `.prettierignore` — только точечные исключения
|
||
(node_modules, refs-html…), без `/*`-allowlist: он глушит формат-он-сейв
|
||
для всего `web/` (файлы резолвятся как ignored и дочерние конфиги не
|
||
применяются). Код `web/` форматируется
|
||
отдельно: `pnpm --dir web format` (Prettier + `prettier-plugin-svelte`,
|
||
конфиг `web/.prettierrc`, игнор `web/.prettierignore`). Проверка без записи:
|
||
`pnpm --dir web exec prettier --check .`. Оба прогона сразу: `pnpm format`
|
||
в корне.
|
||
- Линтинг: `pnpm --dir web lint` (ESLint, flat-конфиг `web/eslint.config.js`).
|
||
Устроен инкрементально:
|
||
- На **весь код** — парсинг TS/Svelte + правило
|
||
`@typescript-eslint/consistent-type-imports` (запрет инлайн-тип-импортов).
|
||
- Полные `recommended`-наборы (`eslint` + `typescript-eslint` + `eslint-plugin-svelte`)
|
||
навешены **только на новый код** (`src/lib/components/kit/**`,
|
||
`src/routes/preview/**`), чтобы старый код не засыпался предсуществующими
|
||
ошибками. Когда старый дизайн удалён (C19), scoped-блок убирается и
|
||
`recommended` включается на весь код.
|
||
- **Scoped-пути двигаются вместе с папками** (см. `plan-redesign.md` §10):
|
||
на C17 `src/routes/preview/**` → `src/routes/**` (preview переезжает на
|
||
реальные маршруты), на C19 при переименовании `kit/`→`ui/` — и компонентный
|
||
glob. Не оставлять устаревшие пути в `eslint.config.js`.
|
||
- Для `*.svelte` выключен `prefer-const` (пропсы в Svelte 5 пишутся через `let`).
|
||
- **Единый запуск всей дизайн-проверки: `pnpm --dir web lint:all`**
|
||
(`web/scripts/lint-all.mjs`) — гоняет по очереди: ESLint (все правила,
|
||
включая design-tokens), stylelint (`lint:css`) и токен-аудит
|
||
(`lint:tokens`). Каждый шаг выполняется, даже если предыдущий упал; выход
|
||
ненулевой, если хоть один не прошёл. Текущие «ожидаемые долги» (hardcoded
|
||
размеры в kit, не-Prefix токены в preview.css и т.п.) — техдолг: чинить
|
||
только по заведённым tasks, не игнорировать правилом.
|
||
|
||
## Правила кода
|
||
|
||
### Svelte 5: типизация props через `interface Props`
|
||
|
||
Все типизированные пропсы компонентов описываются через локальный
|
||
`interface Props`, а деструктуризация идёт через аннотацию типа при `$props()`:
|
||
|
||
```svelte
|
||
<script lang="ts">
|
||
import type { Snippet } from 'svelte';
|
||
|
||
interface Props {
|
||
label: string;
|
||
accent?: boolean;
|
||
children?: Snippet;
|
||
}
|
||
|
||
let { label, accent = false, children }: Props = $props();
|
||
</script>
|
||
```
|
||
|
||
Не использовать инлайн-дженерик `$props<{ ... }>()` — он тяжело читается и
|
||
разносит тип и деструктуризацию по разным местам. Также **не использовать
|
||
инлайн-импорты в типах** (`children?: import('svelte').Snippet;`) — все
|
||
`import type` поднимаются наверх файла. Это правило **включено в ESLint**
|
||
(`@typescript-eslint/consistent-type-imports`, `prefer: 'type-imports'`).
|
||
|
||
> Правило «всегда `interface Props` + `let {...}: Props = $props()`» стандартным
|
||
> ESLint-правилом не покрывается — остаётся конвенцией. Кастомное правило
|
||
> (eslint-plugin-svelte / свой visitor) — TODO, позже.
|
||
|
||
### Дизайн: новый визуальный язык
|
||
|
||
Переезд на новый дизайн описан в `docs/plan-redesign.md`. Ключевые
|
||
ограничения при работе с веткой `feat/redesign`:
|
||
|
||
- Глобальные стили старого и нового дизайна разнесены по layout без
|
||
наследования: старое в `web/src/app.css` (группа `(old)/`), новое в
|
||
`web/src/lib/styles/design2.css` (ветка `preview/*`). Префикс `.ds2` не
|
||
используется.
|
||
- Все повторяющиеся визуальные элементы — отдельные компоненты в
|
||
`web/src/lib/components/kit/`, даже «просто div с двумя стилями».
|
||
|
||
### Линтинг дизайн-токенов («единая точка правды» дизайна)
|
||
|
||
Запрещено «захардкоживать» дизайн: цвета, размеры, длительности и z-index
|
||
обязаны приходить из CSS-переменных. Стек: **stylelint** прогоняется по всем
|
||
CSS-файлам (`web/stylelint.config.js`), **ESLint** лезет в `<style>`-блоки
|
||
svelte-компонентов через локальный плагин `web/eslint-plugins/design-tokens/`
|
||
(постпрефес postcss AST от svelte-eslint-parser). Правила применяются к тем же
|
||
scoped-путям (`kit/**`, `preview/**`).
|
||
|
||
Команды:
|
||
|
||
- `pnpm --dir web lint:css` — stylelint по всем `src/**/*.css` (`old.css` и
|
||
`node_modules/build/.svelte-kit/static` игнорируются).
|
||
- `pnpm --dir web exec node scripts/check-tokens.mjs` — токен-аудит по
|
||
preview.css (лёгкий оркестратор поверх `web/scripts/token-audit/*`);
|
||
проверяет три вещи. Каждый цветовой
|
||
токен из `:root` обязан иметь пару в `[data-theme="dark"]` и наоборот.
|
||
Производные токены (значение содержит `var()`, напр. `hct(from var(--...))`)
|
||
из пары исключены — они наследуют тему автоматически. Выводит **варнинг** о
|
||
неиспользуемых токенах preview.css (определены, но нигде не используются) —
|
||
выход он не меняет. Провалом (exit 1) считаются только parity и hct-авторство.
|
||
- Тот же скрипт проверяет **hct-only для цветов**: ЛЮБОЕ цветовое значение в
|
||
preview.css обязано быть `hct(...)` — и литерал, и производное
|
||
`hct(from var(...) h c t)` (вся палитра считается через hct-каналы; эммит в
|
||
sRGB-hex делает postcss-плагин `web/scripts/postcss-hct.mjs`). Исключения:
|
||
только `--brand-main` / `--brand-alt` (seed-токены, любая форма) и
|
||
`color-mix(...)` (единственный легальный способ смешать два токена).
|
||
`oklch()/rgb()/#hex` в `--color-*` запрещены.
|
||
|
||
Токены-префиксы (целевой словарь дизайна):
|
||
|
||
- Цвета: `--color-*`, бренд `--brand-main` / `--brand-alt` — единственные
|
||
две переменные, которым разрешено быть hex/rgb, остальные цвета — только
|
||
`hct()` (эммит в sRGB-hex делает postcss-плагин `web/scripts/postcss-hct.mjs`)
|
||
и только в preview.css.
|
||
- Размеры: `--space-*`, `--text-*` (font-size), `--radius-*`, `--size-*`.
|
||
- Брейкпоинты: `@custom-media --bp-mobile (max-width: 640px)` /
|
||
`--bp-tablet (800px)` / `--bp-desktop (1100px)` — объявляются в preview.css,
|
||
используются как `@media (--bp-*)`. CSS-переменные в `@media` не работают,
|
||
поэтому отдельных `--bp-*` токенов нет; раскрытие делает postcss-плагин
|
||
`postcss-custom-media` (конфиг `web/postcss.config.js`, определения
|
||
подтягиваются через `@csstools/postcss-global-data`).
|
||
- z-index: `--z-*`; длительности/анимации: `--duration-*`, `--ease-*`.
|
||
- Правило `custom-property-pattern` в stylelint тестирует паттерн **без** `--`
|
||
(`--x` → `x`), а `declaration-property-value-disallowed-list` — целиком с `--`.
|
||
- `custom-property-empty-line-before` перенастроен (`after-custom-property` в
|
||
`ignore`, не в `except`): пустые строки между подряд идущими токенами
|
||
**свободны** и `--fix` их не удаляет — можно группировать цветовые и размерные
|
||
токены в preview.css отдельными блоками.
|
||
|
||
Правила плагина (компоненты, `eslint.config.js`):
|
||
|
||
- `design-tokens/no-hardcoded-in-svelte` — запрет прямых цветов/размеров/
|
||
длительностей/z-index и `color-mix()` (результат токенизировать в CSS).
|
||
Исключения размеров: `0`, `0px`, `1px`, проценты и unitless (`line-height`).
|
||
- `design-tokens/no-category-mismatch` — size-свойство не может использовать
|
||
color-токен (`--color-*`/`--brand-*`) и наоборот.
|
||
- `design-tokens/no-token-definition-in-svelte` — определение `--x:` в
|
||
компоненте не может содержать примитив (hex/rgb/oklch/px/rem); допустимы
|
||
производные от токенов (`var()`, `calc()`, unitless-числа).
|
||
- `design-tokens/no-undefined-in-svelte` — `var(--x)` в `<style>` должен быть
|
||
определён в `src/preview.css` (словарь токенов) или локально в компоненте.
|
||
Файл читается один раз и кэшируется (не `glob`-зависим).
|
||
|
||
### Изоляция веток old ↔ preview
|
||
|
||
`isolation/no-mixed-imports` (`web/eslint-plugins/isolation/no-mixed-imports.js`,
|
||
включён на весь `**/*.{ts,svelte}`) — **полная взаимная изоляция** старого UI и
|
||
новой preview-ветки. В отличие от `no-restricted-imports`, правило **резолвит**
|
||
каждый импорт (и `$lib/...`, и относительные `./`/`../`) до реального файла и
|
||
классифицирует стороны по фактическому пути, поэтому относительным импортом
|
||
правило не обойти.
|
||
|
||
- Старое: `routes/(old)/**`, `lib/old/**`.
|
||
- Новое: `routes/preview/**`, `lib/registry-new/**`, `lib/preview/**`,
|
||
`lib/registry-schema.ts`, `lib/registry-schema.test.ts`, `lib/components/kit/**`.
|
||
- Общее (разрешено обоим): `core/`, `i18n/`, `theme`, `assets/`, корневой `lib`.
|
||
- Плагин **конфигурируем** (опции `old`/`new` + `root`/`alias` в
|
||
`eslint.config.js`): перенос старых файлов в папки `old/` — это правка
|
||
glob-паттернов в настройке, а не код правила.
|
||
- Изоляция уже достигнута: старый `registry/` не тянет `registry-schema`
|
||
(пилоты add-border/add-stroke работают через `params`), `registry.ts` не
|
||
импортирует `ToolSchema`; preview-`list-tools` использует копию
|
||
`preview/tool-icons.ts`, а не старый `tools/tool-icons.ts`.
|
||
|
||
Линтер только показывает ошибки, старый код (непрефиксованные токены, hex в
|
||
preview.css) — известный техдолг, его НЕ чинить и не игнорировать правилами.
|
||
|
||
- Коммиты мелкие (< ~500 строк), атомарные
|