diff --git a/CLAUDE.md b/CLAUDE.md index 8b926b7..6e8f1fe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -197,6 +197,14 @@ S3_REGION="eu-central" - **`src/proxy.ts` не существует** — вся защита маршрутов в `src/middleware.ts`, экспорт `middleware`. Не заводить второй файл. - Новый API-роут, который авторизуется сам (Bearer-ключ, внутренний секрет), обязан попасть в `PUBLIC_ROUTES` — иначе middleware отдаст редирект на `/login` раньше роута, и в браузере с кукой это не воспроизведётся. Обратная сторона: сверка идёт по `startsWith`, поэтому запись `/api/pdf` уже открывает любой будущий `/api/pdf-*` — такой роут обязан проверять доступ внутри себя. +### Уведомления и письма + +- **Отправка глотает ошибки.** Каждый `send*Email` заканчивается `.catch((e) => console.error(...))`, поэтому «серверное действие выполнилось» ничего не говорит о письме. Проверять боем только по Resend: `GET https://api.resend.com/emails?limit=…` и поле `last_event` (`delivered` / `bounced`). Ключ — `~/.config/secrets/resend.env`. +- **Уведомления висят на серверном действии, а не на таблице.** Строка, вставленная в базу напрямую (`INSERT INTO "LessonComment" …`), писем не шлёт — это удобно, чтобы подготовить сцену, но проверять нужно через интерфейс. При автоматизации браузером легко промахнуться мимо формы ответа и попасть в корневое поле: тогда `parentId` пустой, код-путь не вызывается и письма правомерно нет. Помечать поля до клика «Ответить» и брать `textarea:not([data-pre])`. +- **Перед правкой серверного действия проверить, кто его импортирует.** В проекте жили две расходящиеся копии `addComment`/`deleteComment`/`editComment`: живая в `src/lib/actions/student-actions.ts` (лимит 10 000) и мёртвая в `src/app/(student)/courses/[slug]/lessons/[lessonId]/comment-actions.ts` (лимит 2000, никем не импортировалась, удалена 20260915). Правка в неподключённой копии выглядит применённой и не даёт эффекта. +- **Дефолты настроек живут в коде.** В прод-таблице `Settings` строк `notify*` нет вообще — значения берутся из `SETTINGS_DEFAULTS` (`src/lib/settings.ts`), строка появляется только при первом сохранении формы в админке. Новый ключ с дефолтом `"true"` включается на проде сам, без миграции и без действий в интерфейсе. +- Лимиты превью в письмах разные по смыслу: `REPLY_PREVIEW_LIMIT = 2000` у тредов вопросов, `COMMENT_REPLY_PREVIEW_LIMIT = 4000` у ответов под уроками (ввод разрешает 10 000, разборы под уроком длиннее реплик в треде). Не сводить в одну константу. + ### Чистый PDF (`/tools/clean-pdf`, `/api/pdf`) - **Два разных значения `ToolUsage.tool`**: `clean-pdf-generate` (`PDF_TOOL_ID`) — только успешные генерации, по ним считаются месячный лимит и burst; `clean-pdf` — аналитика копирований из `CopyButton`. Не объединять: иначе каждое нажатие «Скопировать» списывает генерацию. `clean-pdf-generate` намеренно отсутствует в `TOOL_IDS`, чтобы публичный Server Action не мог списать квоту.