# CLAUDE.md

Указания для работы над проектом «РестоМесто». Полное ТЗ — у заказчика (docx на его диске);
ключевые ссылки на его пункты расставлены прямо в коде и документации (`ТЗ §N`).

## Что это

Портал-агрегатор заведений с онлайн-бронированием, интегрированный с Restoplace.
**Restoplace — источник истины** по каталогу, доступности столов и броням. Локально хранятся кеш
каталога, дополнения карточки, медиа, аккаунты гостей, отзывы, SEO и служебные журналы.

## С чего начать сессию

1. [docs/progress.md](docs/progress.md) — что готово, что следующее, что сознательно отложено.
2. [docs/assumptions.md](docs/assumptions.md) — принятые решения и открытые вопросы. Решения
   заказчика (раздел D) менять нельзя без его нового решения.
3. [docs/architecture.md](docs/architecture.md) — стек, домены, guard-ы, слой интеграции.

Готовы Этапы 1–7 (основа, каталог, бронирование, гостевой кабинет, отзывы, кабинет заведения,
админка и SEO). Этап 8 (стабилизация) начат: сделаны CSP, резервное копирование, CI/CD и
нагрузочный сценарий; не сделаны E2E, замер Core Web Vitals, прогон нагрузочного теста и три
отложенные функции — список в конце раздела «Этап 8» в [docs/progress.md](docs/progress.md).

## Нерушимые правила

Нарушение любого из них — не стилистическая придирка, а дефект (ТЗ §2.3):

- **Не хранить API-ключ отдельного ресторана.** Только единый партнёрский токен, только на сервере.
- **Не обращаться к API Restoplace из JavaScript.** Все запросы сервер-сервер.
- **Не считать доступность столов самостоятельно.** Расчёт — на стороне Restoplace.
- **Не создавать бронь без финальной проверки Restoplace.** Пакетная доступность предварительна.
- **Не удалять заведения жёстко.** Потеря права на публикацию = скрытие; история и отзывы остаются.
- **Не привязывать домен к поставщику** карт, телефонии, email или ИИ — только через интерфейс.
- **Не вызывать внешний API из Blade-шаблонов и контроллеров.**
- **Не коммитить секреты.**
- **Не показывать контактный email заведения публично.**

## Как устроен код

Модульный монолит. Модели живут в `app/Domain/<Домен>/Models`, не в `App\Models`. Фабрики —
`database/factories/<Домен>/`; разрешение имён и morph map с короткими именами настроены в
`DomainServiceProvider`, там же регистрируются доменные консольные команды.

Три guard-а: `guest` (портал), `restaurant` (кабинет заведения), `admin` (/developer). Общей
таблицы `users` нет.

Интеграция Restoplace — только через интерфейсы в
`app/Domain/RestoplaceIntegration/Contracts`. Реализацию выбирает `RESTOPLACE_DRIVER`
(`fake` — контрактные fixtures, `http` — боевой API). Наружу клиенты отдают типизированные DTO;
массивы из внешнего API за границу интеграции не проходят.

Время: в базе UTC, показ — в часовом поясе конкретного адреса. Даты — `CarbonImmutable`.

## Грабли, на которые уже наступали

- **В кеш нельзя класть объекты.** Laravel 13 по умолчанию запрещает десериализацию классов из
  кеша (`cache.serializable_classes => false`). Объект вернётся как `__PHP_Incomplete_Class`, и
  кеш молча перестанет работать. Кладите массивы — у DTO есть `fromArray()` / `toArray()`.
  Тест на кеш должен проверять **попадание**, а не факт записи.
- **Alpine ломает сторонние библиотеки.** Всё из `x-data` оборачивается в реактивный Proxy;
  Яндекс.Карты этого не переживают. Экземпляры таких объектов держите в замыкании компонента.
- **Alpine приезжает с Livewire.** В макете есть `@livewireScripts` — без него Alpine не
  загрузится на страницах без Livewire-компонентов.
- **Директивы вне `x-data` не работают.** Alpine обходит только поддеревья компонентов: `x-on` на
  элементе без родителя с `x-data` просто игнорируется, без единого сообщения. Шапке и нижней
  навигации пустой `x-data` поставлен именно поэтому.
- **`x-model` и `x-on:input` на одном поле не уживаются.** `x-model` вешает собственный
  обработчик, и второй такой же рядом не срабатывает. Реагируйте через `$watch` на значение.
- **Статус сущности выводите одним способом.** Если создание и последующая сверка считают его по
  разным правилам, состояние начинает переключаться само собой.
- **У Filament своя статика, отдельно от Vite.** JavaScript панели лежит в `public/js/filament` и
  появляется там только после `filament:assets` (входит в `filament:upgrade`, тот — в
  `post-autoload-dump`). Без него панель выглядит почти рабочей: разметка на месте, но меню не
  раскрывается, выпадающие списки уезжают за край экрана, а у кнопок пропадают подписи. В ответе
  сервера всё в порядке, ошибка видна только в консоли браузера — HTTP-тесты её не поймают.
- **Внешние картинки блокирует CSP.** Filament по умолчанию берёт аватар с ui-avatars.com, и в
  шапке оказывается битое изображение. Ослаблять политику ради украшений не надо — рисуйте своё и
  отдавайте `data:`-адресом (`InitialsAvatarProvider`).
- **Колонка Filament с массивом форматируется поэлементно.** `formatStateUsing(fn (?array $state))`
  получит не массив, а каждое его значение по очереди, и страница упадёт с TypeError. Собирайте
  строку сами через `->state()`.
- **Боковое меню админки держится на обходе ошибки Filament.** `public/js/panel-sidebar.js` чинит
  `collapsedGroups: $persist(null)` — без него меню не раскрывается ни в одном браузере, где панель
  открывают впервые. Скрипт обязан идти до Alpine; за подключение отвечает тест.

## Команды

```bash
composer dev                        # сервер + очередь + логи + Vite
composer check                      # pint --test, phpstan, phpunit
composer fix                        # pint
php artisan restoplace:sync --full  # наполнить каталог из фикстур
```

Windows/Laragon: перед работой добавить в PATH `php`, `composer`, `node` — см. README.

## Что делать при неоднозначности

ТЗ §4: выбрать самое простое надёжное решение, записать его в `docs/assumptions.md` и продолжить.
Не останавливаться и не спрашивать по мелочам. Открытые вопросы к заказчику копятся в конце того же
файла.

ТЗ писалось с помощью нейросети, и часть требований попала туда без реальной причины (так было с
Redis и Horizon — заказчик это подтвердил). Требование, которое усложняет систему без выгоды,
стоит оспорить с обоснованием, а не выполнять механически.

## После каждого этапа (ТЗ §31)

Выводить: список изменённых файлов, выполненные команды, результаты тестов, известные ограничения,
следующий этап. Обновлять `.env.example`, README, `docs/progress.md` и остальную документацию
вместе с кодом, а не после.

## Стиль

Комментарии и документация — по-русски. Комментарий объясняет **почему**, а не что: «что» видно из
кода. Ссылка на пункт ТЗ уместна там, где решение продиктовано требованием, а не вкусом.

Тесты пишутся одновременно с функциональностью. Заглушки вместо рабочего кода не годятся.
Проверять результат в браузере, а не только тестами: два дефекта Этапов 2–3 тесты пропустили.
