From 7c21fa2aefc5ab558879cdf159d3651056c30c34 Mon Sep 17 00:00:00 2001 From: Aleksey Sabilin Date: Mon, 27 Jul 2026 15:21:12 +0300 Subject: [PATCH] Docs: sync booking-requests + Bookings IA + image_url after Back#59/Front#30. Fixes EventHub/EventHubSpec#12 --- .cursor/rules/dev-env-wsl.mdc | 33 ++++++++++++++++++++++ .cursor/rules/one-repo-per-agent.mdc | 29 +++++++++++++++++++ .cursor/rules/shell-wsl-not-powershell.mdc | 13 +++++++++ EventHubBackSpec.md | 15 ++++++++-- EventHubFrontSpec.md | 24 ++++++++++++---- STAGE-POPULAR-CLEANUP.md | 23 +++++++++++++++ WORKFLOW.md | 2 ++ design/front/UI-PARITY.md | 27 ++++++++++++++++-- 8 files changed, 157 insertions(+), 9 deletions(-) create mode 100644 .cursor/rules/dev-env-wsl.mdc create mode 100644 .cursor/rules/one-repo-per-agent.mdc create mode 100644 .cursor/rules/shell-wsl-not-powershell.mdc create mode 100644 STAGE-POPULAR-CLEANUP.md diff --git a/.cursor/rules/dev-env-wsl.mdc b/.cursor/rules/dev-env-wsl.mdc new file mode 100644 index 0000000..66688c7 --- /dev/null +++ b/.cursor/rules/dev-env-wsl.mdc @@ -0,0 +1,33 @@ +--- +description: EventHub — локальные сборки/тесты только через WSL (Windows toolchain сломан) +alwaysApply: true +--- + +# EventHub: рабочее окружение агента + +На этой машине **не использовать** нативный Windows для: + +| Стек | Нельзя (Windows) | Нужно | +|------|------------------|--------| +| Front / FrontAdmin | `npm`, `npx`, Playwright | **WSL** `npm` | +| Back (Erlang) | Windows `erl` / `rebar3` | **WSL** + `scripts/wsl-dev-env.sh` | + +## Front (EventHubFront) + +```bash +wsl -e bash -lc 'cd /mnt/c/Users/alexc/IdeaProjects/eventHub/EventHubFront && npm run lint && npm run build && npm run test:e2e' +``` + +## FrontAdmin + +```bash +wsl -e bash -lc 'cd /mnt/c/Users/alexc/IdeaProjects/eventHub/EventHubFrontAdmin && npm run lint && npm run build' +``` + +## Back + +```bash +wsl -e bash -lc 'cd /mnt/c/Users/alexc/IdeaProjects/eventHub/EventHubBack && source scripts/wsl-dev-env.sh && rebar3 eunit' +``` + +Подробности: `EventHubFront/.cursor/rules/npm-wsl.mdc`, `EventHubBack/.cursor/rules/otp-rebar-wsl.mdc`. diff --git a/.cursor/rules/one-repo-per-agent.mdc b/.cursor/rules/one-repo-per-agent.mdc new file mode 100644 index 0000000..28ea348 --- /dev/null +++ b/.cursor/rules/one-repo-per-agent.mdc @@ -0,0 +1,29 @@ +--- +description: Один агент — один репозиторий; не смешивать Front и Back +alwaysApply: true +--- + +# EventHub: отдельные агенты по репозиториям + +## Правило +При работе с **несколькими репозиториями** EventHub запускай **отдельного агента на каждый репо**. Не смешивай Front и Back (и Spec/FrontAdmin) в одном агенте. + +| Репо | Зона | +|------|------| +| `EventHubFront` | клиентский SPA | +| `EventHubBack` | Erlang API | +| `EventHubSpec` | спеки, UI-PARITY, контракты | +| `EventHubFrontAdmin` | admin SPA | + +## Запрещено +- Один агент правит и Front, и Back в одной сессии +- Коммиты/`Refs`/`Fixes` не в тот репо (Front-код → issue Back, и наоборот) +- Закрывать/комментировать issue чужого репо «заодно» + +## Как делать +1. Спека/issue → агент **Spec** (или короткий foreground), если только доки. +2. API → отдельный агент **Back** (`Back#N`, eunit, push Back). +3. UI → отдельный агент **Front** (`Front#N`, lint/IFT, push Front). +4. Родитель-координатор только маршрутизирует; не пишет код сразу в два репо. + +Кросс-репо фича (например Bookings inbox): **сначала Back**, потом **Front** (или параллельно двумя агентами), Spec — третьим или после стабилизации API. diff --git a/.cursor/rules/shell-wsl-not-powershell.mdc b/.cursor/rules/shell-wsl-not-powershell.mdc new file mode 100644 index 0000000..804f74e --- /dev/null +++ b/.cursor/rules/shell-wsl-not-powershell.mdc @@ -0,0 +1,13 @@ +--- +description: Shell — только WSL/bash; PowerShell-экранирование запрещено для сложного +alwaysApply: true +--- + +# Shell: WSL, не PowerShell + +Агенты **не должны** гонять сложные one-liner’ы в PowerShell (ломается экранирование). + +Канон: файл `.sh` + `wsl -e bash /mnt/c/.../script.sh`, либо +`wsl -e bash -lc 'простая команда без вложенных двойных кавычек'`. + +Полный текст: `EventHubBack/.cursor/rules/shell-wsl-not-powershell.mdc`. diff --git a/EventHubBackSpec.md b/EventHubBackSpec.md index ea97a64..1b3d97a 100644 --- a/EventHubBackSpec.md +++ b/EventHubBackSpec.md @@ -171,6 +171,8 @@ Legacy: прямой `POST /v1/calendars/:id/specialists` **не использ (иначе `400`). - **Confirm/decline booking:** владелец — любые booking календаря; `active` specialist — только booking на событиях, где `event.specialist_id` = его `user_id`. +- **Inbox к подтверждению:** `GET /v1/user/booking-requests` агрегирует pending по тем же + правилам (owner — все события своих календарей; specialist — только свои слоты). #### Фаза 2 (вне текущего контракта реализации) - `calendar_share` (`read` | `write` | `admin`): не путать с follow, specialist_invite и платной subscription. @@ -453,10 +455,18 @@ src/ `nickname`, `timezone`, `phone`, `avatar_url`, `preferences`; смена пароля — пара `current_password` + `password` (неверный текущий → `403`). Нельзя менять email/role/status; неизвестные поля → `400`. Ответ — полный профиль как GET. -- `GET /v1/user/bookings` — бронирования пользователя. +- `GET /v1/user/bookings` — бронирования пользователя **как участника**. +- `GET /v1/user/booking-requests` — pending-заявки к подтверждению, где текущий + пользователь — **owner** календаря события или **assigned specialist** + (`event.specialist_id` = user и specialist active). Ответ — массив объектов + booking + `role` (`owner`|`specialist`) + вложенный `event` + (`id`, `calendar_id`, `calendar_title`, `title`, `start_time`, `duration`, + `specialist_id`). Confirm/decline — существующий `PUT /v1/bookings/:id`. - `GET /v1/user/reviews` — отзывы пользователя. - `GET /v1/user/following` — календари, которые пользователь отслеживает (follow). - `GET /v1/search` — поиск; пустой запрос (только auth + пагинация/`type`) — discovery tops. + В calendar-результатах — `image_url` (если задан на календаре). Upload/multipart + **не** входит в контракт: поле URL. - `GET /v1/calendars` — список календарей. - `POST /v1/calendars` — создать календарь (`commercial` → подписка/trial; иначе `402`). - `GET /v1/calendars/:id` — календарь (`following`, `booking_open` для текущего контекста). @@ -482,7 +492,8 @@ src/ - `POST /v1/events/:id/bookings` — запись на событие. - `GET /v1/events/:id/bookings` — список бронирований события (владелец). - `GET /v1/bookings/:id` — статус бронирования. -- `PUT /v1/bookings/:id` — подтвердить/отклонить бронирование (владелец). +- `PUT /v1/bookings/:id` — подтвердить/отклонить (`confirm`|`decline`); владелец — + любые booking календаря; active specialist — только события со своим `specialist_id`. - `DELETE /v1/bookings/:id` — отменить бронирование (участник). - `POST /v1/reviews` — создать отзыв. - `GET /v1/reviews` — список отзывов (поле `my_vote` для текущего пользователя). diff --git a/EventHubFrontSpec.md b/EventHubFrontSpec.md index 9f471f2..3e0b5e9 100644 --- a/EventHubFrontSpec.md +++ b/EventHubFrontSpec.md @@ -51,6 +51,9 @@ Deep-link на календарь, владельцем которого явл - Главная сущность — **календарь**. Центр UI — виджет с видами **месяц** (default) / **неделя** / **день**. - Главные вкладки: **Календарь** (`/`, `/c/:id`), **Найти** (`/search`), **Записи** (`/bookings`), **Ещё** (`/more`). +- **Записи** (`/bookings`) — grouped inbox: «К подтверждению» (owner/specialist pending + Confirm/Decline) + и «Мои записи» (participant); пустые группы скрывать. Confirm на карточке события остаётся. +- В **Ещё**: пункт входящих specialist invites скрыт при `pending=0` (deep-link `/invites` работает). - Контекст виджета: свой календарь (селектор) или чужой (browse после поиска). Чужой `personal` — только просмотр; чужой `commercial` с `booking_open=true` — запись; при `booking_open=false` (restricted / нет подписки владельца) — просмотр + сообщение «Запись временно недоступна». @@ -96,9 +99,12 @@ Deep-link на календарь, владельцем которого явл - `/c/:calendarId` — workspace с календарём - `/c/:calendarId/e/:eventId` — workspace + карточка события - `/search` — поиск календарей/событий -- `/bookings` — мои бронирования (deep-link в `/c/.../e/...`) +- `/bookings` — grouped inbox: (A) к подтверждению (owner/specialist pending) + + (B) мои записи участника; deep-link в `/c/.../e/...` - `/calendars` — управление своими календарями (CRUD) -- `/more` — вторичное меню + mood switcher +- `/more` — вторичное меню + mood switcher; пункт specialist invites **скрыт**, + если входящих pending = 0 (deep-link `/invites?token=` работает) +- `/invites` — inbox входящих specialist invites (не смешивать с `/bookings`) - `/following` — отслеживаемые чужие календари (follow; не платная Подписка) - `/reviews`, `/subscription`, `/tickets`, `/profile` @@ -116,6 +122,7 @@ Redirects: `/discover` → `/search`; `/calendars/:id` → `/c/:id`; `/calendars ### 5.3. Поиск (участник) `GET /v1/search` — фильтры (`type`, `q`, `tags`, даты, `lat`/`lon`/`radius`, sort, order, пагинация). Без `q`/дат/tags/geo/sort — топы календарей и событий; иначе — поиск. В списке бейдж типа — `calendar`|`event` (не `personal`/`commercial`). Даты, теги, geo и сортировка свёрнуты по умолчанию. Переход к `/c/:id` или `/c/:id/e/:eventId`. +В результатах calendar (и event, если Back отдаёт) может быть `image_url` — Discover показывает cover; иначе mood wash + initials. **Upload API нет** — поле URL (seed / ручная установка). ### 5.4. Календари - Список своих: `GET /v1/calendars` @@ -132,8 +139,10 @@ Redirects: `/discover` → `/search`; `/calendars/:id` → `/c/:id`; `/calendars список active + исходящие pending; deactivate/remove принятых. Выбор `specialist_id` в форме события (только `active`). Баннер «подписка истекла / истекает» при `booking_open=false`. -- Invitee: inbox входящих `GET /v1/user/specialist-invites` (More / уведомления) — +- Invitee: inbox входящих `GET /v1/user/specialist-invites` на `/invites` — принять / отклонить; deep-link из email → accept по `token` после логина. + В More пункт «Приглашения» показывается **только** при `pending > 0` + (не путать с booking-заявками на `/bookings`). ### 5.5. События - Список: `GET /v1/calendars/:calendar_id/events` @@ -145,8 +154,13 @@ Redirects: `/discover` → `/search`; `/calendars/:id` → `/c/:id`; `/calendars ### 5.6. Запись (bookings) - Участник: `POST /v1/events/:id/bookings` только при `booking_open`; иначе UI-сообщение / ответ `403`; `GET /v1/user/bookings`, `DELETE /v1/bookings/:id`, `GET /v1/bookings/:id` -- Владелец и specialist: `GET /v1/events/:id/bookings`, `PUT /v1/bookings/:id` - `{ action: confirm | decline }` (specialist — только свои события, см. BackSpec §2.1.2) +- Владелец и specialist: + - на карточке события: `GET /v1/events/:id/bookings` + Confirm/Decline (**обязательно**) + - inbox `/bookings` группа «К подтверждению»: `GET /v1/user/booking-requests` + (pending, где user — owner календаря или `event.specialist_id`); те же + `PUT /v1/bookings/:id` `{ action: confirm | decline }` +- UI `/bookings`: две группы (пустые скрывать): **К подтверждению** + **Мои записи** + (upcoming/past participant). `specialist_invite` сюда **не** попадает. - Статусы: `pending` | `confirmed` | `cancelled`; confirmation: `auto` | `manual` | timeout - Pending резервирует capacity (как на бэке) diff --git a/STAGE-POPULAR-CLEANUP.md b/STAGE-POPULAR-CLEANUP.md new file mode 100644 index 0000000..a147cbf --- /dev/null +++ b/STAGE-POPULAR-CLEANUP.md @@ -0,0 +1,23 @@ +# Stage Popular cleanup notes + +Popular / Discover empty-search feed = ratings tops (`GET /v1/search` without `q`/filters). +E2E и smoke иногда оставляют commercial-календари с рейтингами, из‑за чего лента засоряется. + +## Правила + +1. **Не** удалять «живые» smoke-календари пилота без явного фильтра. +2. Чистить только по безопасным маркерам (название/тег/описание содержат `e2e`, `E2E`, `playwright`, `ift-litter`, или owner = известный e2e user). +3. Soft-delete через API (`DELETE /v1/calendars/:id` владельцем / admin), не ручной wipe Mnesia. +4. Front already tracks calendars created in-session via `e2e/helpers/standCleanup.ts` — предпочитать это для новых тестов. + +## Suggested filter (admin / ops) + +- title/tags match: `(?i)e2e|playwright|ift-litter|tmp-` +- created recently + zero real bookings, if policy allows +- exclude calendars used by documented stage smoke accounts + +## Upload / cover images + +`image_url` на календаре отдаётся в search. **Upload API нет** — seed URL вручную или через admin/update поля. Discover показывает cover если URL непустой, иначе mood wash + initials. + +Refs: EventHubSpec#12, EventHubBack#59, EventHubFront#30. diff --git a/WORKFLOW.md b/WORKFLOW.md index 12725c8..666aac3 100644 --- a/WORKFLOW.md +++ b/WORKFLOW.md @@ -362,6 +362,7 @@ error rate > 1% **или** mean request > 500 ms (hold ≥2 мин в фазе) - После успешных тестов — обновить спеку, если менялся внешний контракт. - Одна задача — один логический объём работы. - Код фичи — в репозитории-владельце; `EventHubSpec` — для документации и процесса. +- **Один агент — один репо** (не смешивать Front/Back/Spec/FrontAdmin в одной сессии): `.cursor/rules/one-repo-per-agent.mdc`. - Метки во всех репозиториях держать одинаковыми (`sync-labels.ps1`). - Секреты и токены не коммитить; Gitea token — только в env / `~/.cursor/secrets/`. - **Git commit/push — через WSL** (`~/.cursor/eventhub/git-commit.sh`, `git-push.sh`); **push — только после подтверждения пользователя** (см. §2.1). @@ -377,6 +378,7 @@ error rate > 1% **или** mean request > 500 ms (hold ≥2 мин в фазе) | Дата | Изменение | |------|-----------| +| 2026-07-27 | Spec#12: booking-requests + Bookings IA + image_url; правило one-repo-per-agent | | 2026-07-27 | §6 / STAGE-BROWSER: канон — прямой HTTPS `*.calentiq.com`; proxy `:8787` только DNS-fallback | | 2026-07-22 | §6: Stage UI в Cursor Browser — `STAGE-BROWSER.md` + alias proxy | | 2026-07-22 | §2.1: Agent shell — не PowerShell для сложного; `.sh` + WSL; Front push только после lint/build/IFT | diff --git a/design/front/UI-PARITY.md b/design/front/UI-PARITY.md index 51d52df..c935cbc 100644 --- a/design/front/UI-PARITY.md +++ b/design/front/UI-PARITY.md @@ -17,7 +17,7 @@ SoT: [`boards/calentiq-ui-board-final.png`](boards/calentiq-ui-board-final.png). | Pixel-perfect calendar | denser board mock + Today/Upcoming rail | Agenda rail (§B): title-case Сегодня/Предстоящие + count, time-first rows; month chips/cell wash (§B/D) по mood; не 1:1 Figma | | Discover cards | soft cards + media thumb + CTA | mood media plane (wash/initials + `image_url` when present) + soft-card rhythm + CTA; без full photo-hero grid | | CONTROL booking §E | отдельные mock-карточки «слот/специалист» | `EventActionCard` / specialists / bookings: steel rail (усилен), avatar, available label, Confirm/Decline; не pixel-perfect board mocks | -| Bookings list §D | список заявок с Confirm/Decline на `/bookings` | `/bookings` = заявки **участника** (Отменить); owner Confirm/Decline — в карточке события (`EventActionCard` / Заявки) | +| Bookings list §D | список заявок с Confirm/Decline на `/bookings` | `/bookings` = **grouped inbox**: (A) К подтверждению — pending owner/specialist + Confirm/Decline; (B) Мои записи — participant upcoming/past + Cancel; empty groups hidden. Event card Confirm/Decline **остаётся** обязательным | | Login §D phone chrome | три phone frames + (на борде) Google CTA | brand-first hero + mood `--auth-*`; email/password only; mood switcher на login (иконки); taglines mood cards — на `/more`; **без** Google OAuth | | Favicon set | 3 отдельных favicon файла на борде | mood appicon через `applyMood` | | Desktop nav | phone bottom tabs на борде | desktop: top tabs (Календарь / Найти / Записи / Ещё); bottom nav — mobile | @@ -32,7 +32,30 @@ IA без sidebar / без Creator mood — совпадает с non-goals (п **Polish in this pass:** agenda copy/casing/counts + time-first rows; CONTROL steel rail emphasis; neutral Discover cover in mock (не flow-appicon во всех moods). -**Remaining intentional:** no pixel-perfect phone chrome; Discover без photo-hero grid; owner Confirm/Decline не на `/bookings` list. +**Remaining intentional:** no pixel-perfect phone chrome; Discover без photo-hero grid (cover via `image_url` when API returns it); §E specialist schedule card vs panel — не pixel-perfect board mocks (follow-up). + +### Bookings inbox + More invites (locked 2026-07-27) + +Продуктовые решения (не deviation — канон): + +1. **Event card Confirm/Decline** остаётся обязательным (quick approve на слоте). +2. **`/bookings`** — grouped inbox, не только participant: + - **Группа A — К подтверждению:** pending bookings, где пользователь calendar **owner** или **assigned specialist** (`event.specialist_id` = user). Confirm/Decline те же, что на `EventActionCard`. + - **Группа B — Мои записи:** participant bookings (upcoming/past + Cancel) как раньше. + - Пустые группы скрывать (`count=0`). + - Опционально подпись owner vs specialist внутри A; иначе одна секция «К подтверждению». +3. **`specialist_invite` в More:** скрывать пункт меню, если **0 pending** входящих. Deep-link `/invites?token=` работает. Исходящие invite остаются в `SpecialistsPanel`. +4. **Не** класть строки `specialist_invite` в `/bookings`. + +API: `GET /v1/user/booking-requests` (Back) + существующий `PUT /v1/bookings/:id`. + +### Follow-ups (вне этого fix-pass или частично) + +| Тема | Статус | +|------|--------| +| Discover `image_url` в search serialization | Back отдаёт поле; upload API **нет** — только URL field / seed | +| Stage Popular clutter (e2e calendars) | filter notes: см. `EventHubSpec/STAGE-POPULAR-CLEANUP.md`; не трогать реальные smoke данные без фильтра по e2e-паттернам | +| §E specialist schedule card vs panel | deviation: EventActionCard + SpecialistsPanel; отдельный pixel mock — Future | ## Mood concept = product surface