docs(spec): calendar_share co-editor/deputy and Time Arc alignment.
Back §2.1.3 API/ACL; Front §3.3 share busy/ghost. Refs EventHub/EventHubBack#73
This commit is contained in:
+38
-12
@@ -13,8 +13,9 @@ EventHub — платформа для управления событиями
|
|||||||
|
|
||||||
### 2.1. Календари
|
### 2.1. Календари
|
||||||
- CRUD календаря (название, описание, теги, владелец)
|
- CRUD календаря (название, описание, теги, владелец)
|
||||||
- Расшаривание по ссылке / приглашения с правами (`calendar_share`) — **фаза 2** (таблица есть;
|
- Расшаривание по ссылке / приглашения с правами (`calendar_share`) — invite/accept/revoke +
|
||||||
user HTTP API и UI — Future, см. §2.1.3)
|
ACL `read|write|admin` (Back#73); personal и commercial (заместитель); UI — Front;
|
||||||
|
не путать со `specialist_invite` / follow.
|
||||||
- Типы календарей: `personal` | `commercial` — семантика и коммерческий контур: **§2.1.2**
|
- Типы календарей: `personal` | `commercial` — семантика и коммерческий контур: **§2.1.2**
|
||||||
- Гибкое подтверждение заявок: `auto` | `manual` | `{timeout, N}` (секунды) — детали §2.1.2 / §2.3
|
- Гибкое подтверждение заявок: `auto` | `manual` | `{timeout, N}` (секунды) — детали §2.1.2 / §2.3
|
||||||
- Теги календаря, рейтинг (средняя оценка, количество голосов)
|
- Теги календаря, рейтинг (средняя оценка, количество голосов)
|
||||||
@@ -68,7 +69,7 @@ EventHub — платформа для управления событиями
|
|||||||
| | `personal` | `commercial` |
|
| | `personal` | `commercial` |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Подписка владельца | не требуется | нужна active (trial или paid), иначе **restricted** |
|
| Подписка владельца | не требуется | нужна active (trial или paid), иначе **restricted** |
|
||||||
| Видимость чужим | только владелец (+ share — фаза 2) | публичный просмотр; в search/discover — только если **не** restricted |
|
| Видимость чужим | только владелец или share grant | публичный просмотр; в search/discover — только если **не** restricted |
|
||||||
| Booking чужим | **запрещён** (бэкенд `403`, код `personal_calendar`) | разрешён при active подписке владельца |
|
| Booking чужим | **запрещён** (бэкенд `403`, код `personal_calendar`) | разрешён при active подписке владельца |
|
||||||
| Follow | нет (нет доступа) | да |
|
| Follow | нет (нет доступа) | да |
|
||||||
| Специалисты | не используются | CRUD владельца; `specialist_id` на событии |
|
| Специалисты | не используются | CRUD владельца; `specialist_id` на событии |
|
||||||
@@ -256,16 +257,33 @@ Legacy: прямой `POST /v1/calendars/:id/specialists` **не использ
|
|||||||
**сделано** (Back#75): `GET/PUT /v1/notifications/prefs`,
|
**сделано** (Back#75): `GET/PUT /v1/notifications/prefs`,
|
||||||
`GET /v1/push/vapid-public-key`, `POST/DELETE /v1/push/subscriptions`;
|
`GET /v1/push/vapid-public-key`, `POST/DELETE /v1/push/subscriptions`;
|
||||||
UI — Front#71.
|
UI — Front#71.
|
||||||
- `calendar_share` (`read` | `write` | `admin`): низкий приоритет; не путать с follow,
|
- `calendar_share` (`read` | `write` | `admin`): соредактор personal / заместитель commercial;
|
||||||
specialist_invite и платной subscription. User HTTP API и UI — до отдельной задачи
|
invite/accept/revoke + grants; personal **не** в search/discovery. Не путать с follow,
|
||||||
(таблица в схеме есть, см. §2.1.3).
|
specialist_invite и платной subscription.
|
||||||
- `client_type=mobile` — хвост, когда появится нативный клиент.
|
- `client_type=mobile` — хвост, когда появится нативный клиент.
|
||||||
- **Не в фазе 2:** оплата услуги клиентом (B2C) — вне горизонта продукта.
|
- **Не в фазе 2:** оплата услуги клиентом (B2C) — вне горизонта продукта.
|
||||||
|
|
||||||
### 2.1.3. Share / приглашения (фаза 2)
|
### 2.1.3. Share / приглашения (соредактор и заместитель)
|
||||||
Таблица `calendar_share` (`calendar_id`, `user_id`, `rights`: `read` | `write` | `admin`)
|
|
||||||
существует в схеме. User HTTP API и клиентский UI — не стартовать, пока нет явного
|
Таблица `calendar_share` (`id`, `calendar_id`, `user_id`, `rights`: `read` | `write` | `admin`,
|
||||||
сценария соредактора personal (совместная работа сейчас: specialist_invite + booking inbox).
|
`mirror_to_default`) и `calendar_share_invite` (зеркало specialist_invite + `rights`).
|
||||||
|
|
||||||
|
- Invite создаёт **owner** или share `admin` (personal **и** commercial).
|
||||||
|
- Accept → upsert grant; для personal `mirror_to_default` default `true` (выбор invitee);
|
||||||
|
commercial mirror игнорируется / false.
|
||||||
|
- ACL: `can_access` personal = owner | share; commercial public view + share edit;
|
||||||
|
`can_edit` = owner | share `write|admin`. DELETE calendar / смена type — только owner.
|
||||||
|
- `GET /v1/calendars` = owned ∪ shared (`role`, `share_rights`, `mirror_to_default`).
|
||||||
|
- Personal **никогда** не discoverable в `/v1/search`.
|
||||||
|
|
||||||
|
API:
|
||||||
|
- `GET/POST /v1/calendars/:id/share-invites`, `DELETE …/:invite_id`
|
||||||
|
- `GET /v1/calendars/:id/shares`, `DELETE/PUT …/shares/:user_id`
|
||||||
|
- `PUT /v1/user/shares/:calendar_id` `{mirror_to_default}`
|
||||||
|
- `GET /v1/user/share-invites`, `POST /v1/share-invites/:id/accept|decline`,
|
||||||
|
`POST /v1/share-invites/accept` `{token}`
|
||||||
|
|
||||||
|
Не путать со `specialist_invite` (booking team) и `calendar_follow` (публичный follow).
|
||||||
|
|
||||||
### 2.2. События (расширенная версия с повторяющимися событиями)
|
### 2.2. События (расширенная версия с повторяющимися событиями)
|
||||||
|
|
||||||
@@ -576,10 +594,12 @@ src/
|
|||||||
- `GET /v1/user/reviews` — отзывы пользователя.
|
- `GET /v1/user/reviews` — отзывы пользователя.
|
||||||
- `GET /v1/user/following` — календари, которые пользователь отслеживает (follow).
|
- `GET /v1/user/following` — календари, которые пользователь отслеживает (follow).
|
||||||
- `GET /v1/search` — поиск; **без токена** (гость) — только commercial по `can_access`;
|
- `GET /v1/search` — поиск; **без токена** (гость) — только commercial по `can_access`;
|
||||||
пустой запрос + пагинация/`type` — discovery tops. С Bearer — как раньше (включая свои personal).
|
пустой запрос + пагинация/`type` — discovery tops. Personal **никогда** не в выдаче
|
||||||
|
(в т.ч. свои). С Bearer — commercial + доступные; busy для Time Arc берётся из personal отдельно.
|
||||||
В calendar-результатах — `image_url` (если задан на календаре). Upload/multipart
|
В calendar-результатах — `image_url` (если задан на календаре). Upload/multipart
|
||||||
**не** входит в контракт: поле URL.
|
**не** входит в контракт: поле URL.
|
||||||
- `GET /v1/calendars` — список календарей (auth).
|
- `GET /v1/calendars` — список календарей (auth): owned ∪ shared (`role`, `share_rights`,
|
||||||
|
`mirror_to_default`).
|
||||||
- `POST /v1/calendars` — создать календарь (`commercial` → нужна уже active sub/trial;
|
- `POST /v1/calendars` — создать календарь (`commercial` → нужна уже active sub/trial;
|
||||||
иначе `402`; auto-start trial нет).
|
иначе `402`; auto-start trial нет).
|
||||||
- `GET /v1/calendars/:id` — календарь **или unique `short_name`**. **Без токена** для active commercial (`following: false`);
|
- `GET /v1/calendars/:id` — календарь **или unique `short_name`**. **Без токена** для active commercial (`following: false`);
|
||||||
@@ -598,6 +618,12 @@ src/
|
|||||||
- `GET /v1/user/specialist-invites` — входящие приглашения.
|
- `GET /v1/user/specialist-invites` — входящие приглашения.
|
||||||
- `POST /v1/specialist-invites/:id/accept` | `…/decline` — ответ invitee.
|
- `POST /v1/specialist-invites/:id/accept` | `…/decline` — ответ invitee.
|
||||||
- `POST /v1/specialist-invites/accept` `{ token }` — accept по email deep-link.
|
- `POST /v1/specialist-invites/accept` `{ token }` — accept по email deep-link.
|
||||||
|
- `GET/POST /v1/calendars/:id/share-invites` — исходящие share invite / создать (owner|admin).
|
||||||
|
- `DELETE /v1/calendars/:id/share-invites/:invite_id` — отменить pending.
|
||||||
|
- `GET /v1/calendars/:id/shares` — принятые grants; `DELETE/PUT …/shares/:user_id`.
|
||||||
|
- `PUT /v1/user/shares/:calendar_id` — свой `mirror_to_default`.
|
||||||
|
- `GET /v1/user/share-invites` — входящие share invites.
|
||||||
|
- `POST /v1/share-invites/:id/accept` | `…/decline`; `POST /v1/share-invites/accept` `{token}`.
|
||||||
- `GET /v1/calendars/:calendar_id/events` — события календаря (гость: commercial).
|
- `GET /v1/calendars/:calendar_id/events` — события календаря (гость: commercial).
|
||||||
В JSON каждого события commercial-календаря — опциональное поле
|
В JSON каждого события commercial-календаря — опциональное поле
|
||||||
`booking_occupancy`: `"free"` | `"pending"` | `"confirmed"`.
|
`booking_occupancy`: `"free"` | `"pending"` | `"confirmed"`.
|
||||||
|
|||||||
@@ -151,9 +151,9 @@ CONTROL→bookings. Контент: free gaps / события сегодня /
|
|||||||
|
|
||||||
### 3.3. Time Arc (живая сетка, Front#50)
|
### 3.3. Time Arc (живая сетка, Front#50)
|
||||||
|
|
||||||
На commercial week/day в режиме Студия клиент считает предложенный час без LLM и без Back: свободные слоты студии ∩ занятость пользователя (personal + confirmed bookings) × mood (FLOW — воздух и середина дня, MOMENTUM — ближайший, CONTROL — доля свободных мест). Одна ячейка — `data-time-arc`; коллизии — `data-collided`. Тап — обычный Book.
|
На commercial week/day в режиме Студия клиент считает предложенный час без LLM и без Back: свободные слоты студии ∩ занятость пользователя (personal + confirmed bookings + **mirror** shared-personal) × mood (FLOW — воздух и середина дня, MOMENTUM — ближайший, CONTROL — доля свободных мест). Одна ячейка — `data-time-arc`; коллизии — `data-collided`. Тап — обычный Book.
|
||||||
|
|
||||||
Владелец: черновики слотов из прошлой ISO-недели в дырах текущей; «Создать неделю» —
|
Владелец **или** share `write|admin` (заместитель): черновики слотов из прошлой ISO-недели в дырах текущей; «Создать неделю» —
|
||||||
preview, затем существующий POST events. После создания черновик снимается сразу
|
preview, затем существующий POST events. После создания черновик снимается сразу
|
||||||
(без F5), в том числе если время в форме сдвинули (прошлый час → +1 день / правка start).
|
(без F5), в том числе если время в форме сдвинули (прошлый час → +1 день / правка start).
|
||||||
Фраза с часом уже в прошлом («11» в 14:00) ставит черновик на следующий день, тот же час.
|
Фраза с часом уже в прошлом («11» в 14:00) ставит черновик на следующий день, тот же час.
|
||||||
@@ -169,6 +169,11 @@ preview, затем существующий POST events. После созда
|
|||||||
|
|
||||||
**v4 (Front#53):** pending occupancy тянет demand-черновики на тот же час в свободные дни текущей ISO-недели (`data-time-arc-demand`). Long-press пустого часа week/day у владельца commercial — локальный разбор фразы → phrase-черновик (`data-time-arc-phrase`); клик и long-press месяца по-прежнему открывают create. Специалист на чужой студии: дуга на своём будущем pending-слоте. Без LLM / AiRouter.
|
**v4 (Front#53):** pending occupancy тянет demand-черновики на тот же час в свободные дни текущей ISO-недели (`data-time-arc-demand`). Long-press пустого часа week/day у владельца commercial — локальный разбор фразы → phrase-черновик (`data-time-arc-phrase`); клик и long-press месяца по-прежнему открывают create. Специалист на чужой студии: дуга на своём будущем pending-слоте. Без LLM / AiRouter.
|
||||||
|
|
||||||
|
**Share / заместитель (Back#73, согласование Front#49):** `GET /v1/calendars` включает shared;
|
||||||
|
busy Time Arc += события personal с `mirror_to_default`; ghost/demand/phrase у share
|
||||||
|
`write|admin`; Following не смешивать с share. Confirmed foreign bookings overlay на
|
||||||
|
default personal — только `role=owner` ids в ownSet.
|
||||||
|
|
||||||
## 4. Маршруты
|
## 4. Маршруты
|
||||||
|
|
||||||
Публичные:
|
Публичные:
|
||||||
@@ -379,7 +384,8 @@ legal stubs; позиция по подписке **владельца**; DNS/SP
|
|||||||
- waitlist: API Back#72; включение — чекбокс «Лист ожидания»
|
- waitlist: API Back#72; включение — чекбокс «Лист ожидания»
|
||||||
(`settings.waitlist_enabled`) в настройках commercial; join/leave на
|
(`settings.waitlist_enabled`) в настройках commercial; join/leave на
|
||||||
карточке события при полном слоте — Front#70
|
карточке события при полном слоте — Front#70
|
||||||
- шаринг календаря с правами (`calendar_share`) — низкий приоритет, не путать со specialist_invite
|
- шаринг календаря с правами (`calendar_share`) — Back#73 API + Front list/invites/mirror;
|
||||||
|
не путать со specialist_invite / Following
|
||||||
- явный `client_type=mobile` — хвост (фаза 3 бэка / нативный клиент)
|
- явный `client_type=mobile` — хвост (фаза 3 бэка / нативный клиент)
|
||||||
|
|
||||||
**Вне горизонта (не фаза 2):** оплата услуги клиентом (B2C).
|
**Вне горизонта (не фаза 2):** оплата услуги клиентом (B2C).
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
## 4. [Правила работы (workflow)](WORKFLOW.md)
|
## 4. [Правила работы (workflow)](WORKFLOW.md)
|
||||||
## 5. [Stage UI в Cursor Browser](STAGE-BROWSER.md) (HTTPS `*.calentiq.com`; proxy — fallback)
|
## 5. [Stage UI в Cursor Browser](STAGE-BROWSER.md) (HTTPS `*.calentiq.com`; proxy — fallback)
|
||||||
## 6. [UX backlog (Client UI)](UX-BACKLOG.md)
|
## 6. [UX backlog (Client UI)](UX-BACKLOG.md)
|
||||||
|
## 7. [Карта архитектуры для Zed/агентов](ZED-ARCHITECTURE.md)
|
||||||
|
|
||||||
# **Репозитории разработки EventHub**
|
# **Репозитории разработки EventHub**
|
||||||
## 1. [EventHub Backend](https://git.sabilin.com/EventHub/EventHubBack)
|
## 1. [EventHub Backend](https://git.sabilin.com/EventHub/EventHubBack)
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ E2E и smoke иногда оставляют commercial-календари с р
|
|||||||
|
|
||||||
1. **Не** удалять «живые» smoke-календари пилота без явного фильтра.
|
1. **Не** удалять «живые» smoke-календари пилота без явного фильтра.
|
||||||
2. Чистить только по безопасным маркерам (название/тег/описание содержат `e2e`, `E2E`, `playwright`, `ift-litter`, или owner = известный e2e user).
|
2. Чистить только по безопасным маркерам (название/тег/описание содержат `e2e`, `E2E`, `playwright`, `ift-litter`, или owner = известный e2e user).
|
||||||
3. Soft-delete через API (`DELETE /v1/calendars/:id` владельцем / admin), не ручной wipe Mnesia.
|
3. Soft-delete через API (`DELETE /v1/calendars/:id` владельцем / admin) для точечного litter; **полный** сброс стенда — Back CI `workflow_dispatch` `wipe-mnesia-ift` / `wipe-mnesia-stage` (`scripts/ci-full-mnesia-wipe.sh`), только вручную, не prod.
|
||||||
4. Front already tracks calendars created in-session via `e2e/helpers/standCleanup.ts` — предпочитать это для новых тестов.
|
4. Front already tracks calendars created in-session via `e2e/helpers/standCleanup.ts` — предпочитать это для новых тестов.
|
||||||
|
5. Catalog BizCal (`short_name` `bizcal-*`, tag `BizCal`) и disposable LargeCal — не путать с smoke; seed: Front `seed-catalog-ift|stage` (нужны `BIZCAL_CLIENT_*` в `.env` стенда).
|
||||||
|
|
||||||
## Suggested filter (admin / ops)
|
## Suggested filter (admin / ops)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
# EventHub — карта для AI-агентов (Zed / Cursor)
|
||||||
|
|
||||||
|
Кросс-репо ориентир: где какой модуль и куда класть правки. Детали API — в `EventHubBackSpec.md` / `EventHubFrontSpec.md` / `EventHubFrontAdminSpec.md`. Процесс — `WORKFLOW.md`.
|
||||||
|
|
||||||
|
## Репозитории
|
||||||
|
|
||||||
|
| Репо | Стек | Зона |
|
||||||
|
|------|------|------|
|
||||||
|
| `EventHubBack` | Erlang/OTP 28, Cowboy, Mnesia | Public + Admin API |
|
||||||
|
| `EventHubFront` | React + Vite + TanStack Query | Клиентский SPA (CalenTIQ) |
|
||||||
|
| `EventHubFrontAdmin` | React + Vite | Admin SPA |
|
||||||
|
| `EventHubSpec` | Markdown | Спеки, UI-PARITY, workflow |
|
||||||
|
| `EventHubAiRouter` | FastAPI + LiteLLM | Zed gateway / routing |
|
||||||
|
| `EventHubDevOps` | Docker Swarm | Стенды, deploy |
|
||||||
|
|
||||||
|
**Правило:** один агент / один PR — один репо. Не смешивать Front и Back в одной сессии правок.
|
||||||
|
|
||||||
|
Бренд UI: **CalenTIQ**. Внутренние имена репо `EventHub*` не менять.
|
||||||
|
|
||||||
|
## Домен (кратко)
|
||||||
|
|
||||||
|
- **User** — владелец календарей / запись на слоты
|
||||||
|
- **Calendar** — расписание, специалисты, follow
|
||||||
|
- **Event** — слоты (в т.ч. recurring)
|
||||||
|
- **Booking / booking-request** — запись / заявка
|
||||||
|
- **Ticket / Review / Report** — поддержка, отзывы, жалобы
|
||||||
|
- **Subscription** — подписка владельца
|
||||||
|
- **Specialist invite** — приглашение специалиста в календарь
|
||||||
|
- **Admin** — модерация, аудит, stats (отдельный JWT)
|
||||||
|
|
||||||
|
Стенды: **dev** локально, **IFT**, **stage** (`https://stage.calentiq.com`, admin `https://admin.stage.calentiq.com`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## EventHubBack (`src/`)
|
||||||
|
|
||||||
|
Слои (сверху вниз):
|
||||||
|
|
||||||
|
| Слой | Путь | Ответственность |
|
||||||
|
|------|------|-----------------|
|
||||||
|
| HTTP | `handlers/`, `handlers/admin/` | Cowboy handlers, валидация входа/ответа |
|
||||||
|
| Logic | `logic/` | Бизнес-правила, оркестрация core |
|
||||||
|
| Core | `core/` | Mnesia-таблицы / CRUD сущности |
|
||||||
|
| Infra | `infra/` | Mnesia, auth helpers, workers, migrations |
|
||||||
|
| Middleware | `middlewares/` | Auth JWT и т.п. |
|
||||||
|
|
||||||
|
### Типичные пары handler → logic → core
|
||||||
|
|
||||||
|
| Домен | Handler(s) | Logic | Core |
|
||||||
|
|-------|------------|-------|------|
|
||||||
|
| Auth / session | `handler_login`, `handler_register`, `handler_auth`, `handler_refresh`, `handler_verify` | `logic_auth`, `logic_auth_session`, `logic_user` | `core_user`, `core_session`, `core_auth_session`, `core_verification` |
|
||||||
|
| Password reset | `handler_forgot_password`, `handler_reset_password` | `logic_password_reset` | `core_password_reset` |
|
||||||
|
| Calendars | `handler_calendars`, `handler_calendar_by_id`, `handler_calendar_view` | `logic_calendar` | `core_calendar` |
|
||||||
|
| Specialists | `handler_calendar_specialists`, `handler_specialist_invites`, `handler_calendar_specialist_invites` | `logic_calendar_specialist`, `logic_specialist_invite` | `core_calendar_specialist`, `core_specialist_invite` |
|
||||||
|
| Follow | `handler_calendar_follow`, `handler_user_following` | `logic_calendar_follow` | `core_calendar_follow` |
|
||||||
|
| Events | `handler_events`, `handler_event_by_id`, `handler_event_occurrences` | `logic_event`, `logic_recurrence` | `core_event` |
|
||||||
|
| Bookings | `handler_bookings`, `handler_booking_by_id`, `handler_user_bookings`, `handler_user_booking_requests` | `logic_booking` | `core_booking` |
|
||||||
|
| Reviews | `handler_reviews`, `handler_review_by_id`, `handler_user_reviews`, `handler_review_vote` | `logic_review` | `core_review`, `core_review_vote` |
|
||||||
|
| Tickets | `handler_tickets`, `handler_ticket_by_id` | `logic_ticket` | `core_ticket` |
|
||||||
|
| Reports | `handler_reports` | `logic_report` | `core_report` |
|
||||||
|
| Search / lookup | `handler_search`, `handler_users_lookup` | `logic_search`, `logic_user_lookup` | — |
|
||||||
|
| Subscription | `handler_subscription` | `logic_subscription` | `core_subscription` |
|
||||||
|
| Notifications | — | `logic_notification` | `core_notification` |
|
||||||
|
| Admin API | `handlers/admin/admin_handler_*` | `logic_admin`, `logic_moderation`, `logic_automoderation`, `logic_stats` | `core_admin`, `core_admin_*`, `core_banned_words`, `core_automod_*` |
|
||||||
|
|
||||||
|
Миграции: `src/migrations/`. Сборка/тесты: **только WSL** (`source scripts/wsl-dev-env.sh`, `rebar3 eunit`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## EventHubFront (`src/`)
|
||||||
|
|
||||||
|
| Путь | Зачем |
|
||||||
|
|------|--------|
|
||||||
|
| `pages/` | Экраны маршрутов |
|
||||||
|
| `api/*Api.ts` | HTTP к Back |
|
||||||
|
| `components/calendar/` | UI календаря / workspace |
|
||||||
|
| `layouts/AppShell.tsx` | Оболочка |
|
||||||
|
| `store/` | Zustand (auth, mood, locale, calendar context) |
|
||||||
|
| `hooks/` | React hooks (API, WS, swipe) |
|
||||||
|
| `lib/` | Утилиты (payloads, schedule, booking display) |
|
||||||
|
| `i18n/` | Локали |
|
||||||
|
|
||||||
|
### Страницы → смысл
|
||||||
|
|
||||||
|
| Page | Маршрут (ориентир) | Тема |
|
||||||
|
|------|-------------------|------|
|
||||||
|
| `DiscoverPage` | discover | Поиск / лента |
|
||||||
|
| `CalendarsPage` | calendars | Список календарей |
|
||||||
|
| `CalendarWorkspacePage` | `/c/:id` | Основной workspace слотов |
|
||||||
|
| `BookingsPage` | bookings | Мои записи / inbox заявок |
|
||||||
|
| `ReviewsPage` | reviews | Отзывы |
|
||||||
|
| `TicketsPage` / `TicketDetailPage` | tickets | Тикеты |
|
||||||
|
| `SubscriptionPage` | subscription | Подписка |
|
||||||
|
| `FollowingPage` | following | Подписки на календари |
|
||||||
|
| `SpecialistInvitesPage` | invites | Инвайты специалиста |
|
||||||
|
| `ProfilePage` / `MorePage` | profile / more | Профиль / меню |
|
||||||
|
| `pages/auth/*` | login/register/… | Auth |
|
||||||
|
|
||||||
|
npm / Playwright — **только WSL** (см. `.cursor/rules/npm-wsl.mdc`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## EventHubFrontAdmin (`src/`)
|
||||||
|
|
||||||
|
| Путь | Зачем |
|
||||||
|
|------|--------|
|
||||||
|
| `pages/dashboard` | Дашборд |
|
||||||
|
| `pages/users`, `calendars`, `events` | Справочники |
|
||||||
|
| `pages/tickets`, `reports`, `inbox` | Модерация / inbox |
|
||||||
|
| `pages/reviews`, `automod`, `banned-words` | Контент / автомод |
|
||||||
|
| `pages/subscriptions`, `admins`, `audit`, `monitoring` | Биллинг / админы / аудит / метрики |
|
||||||
|
| `api/*Api.ts` | Admin API client |
|
||||||
|
| `store/authStore.ts` | Admin session |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## EventHubAiRouter (`router/`, `config/`)
|
||||||
|
|
||||||
|
| Файл / папка | Зачем |
|
||||||
|
|--------------|--------|
|
||||||
|
| `router/router.py` | Zed `/v1/chat/completions`, ветка tools vs text |
|
||||||
|
| `router/hierarchical.py` | Plan (Qwen3.8-Max) / workers / verify (DeepSeek) / plan_confirm |
|
||||||
|
| `router/agent_hier.py` | При `tools`: Coder-30B executor + tool_calls (Zed Write) |
|
||||||
|
| `router/orchestrator.py` | Tiers / lanes A–B–C, Redis session |
|
||||||
|
| `config/orchestration.yaml` | Модели plan/verify/executor, таймауты, synthetic=never |
|
||||||
|
| `config/providers.yaml` | Novita / VPN (`novita-planner` = Max) |
|
||||||
|
| `scripts/gen-litellm-config.py` | Генерация LiteLLM YAML |
|
||||||
|
|
||||||
|
IFT: `https://ai-router.ift.calentiq.com/v1`, модель `smart-router`.
|
||||||
|
Клиент-канон: **Zed Agent** (openai-compatible) → AiRouter; не Claude Code/Codex ACP как primary (обход бюджета).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Куда править (шпаргалка)
|
||||||
|
|
||||||
|
| Задача | Репо | Куда смотреть |
|
||||||
|
|--------|------|----------------|
|
||||||
|
| Баг API / Mnesia | Back | `handlers` → `logic` → `core` |
|
||||||
|
| UI клиент | Front | `pages` + `api` + `components/calendar` |
|
||||||
|
| UI админки | FrontAdmin | `pages` + `api` |
|
||||||
|
| Контракт / процесс | Spec | `*Spec.md`, `WORKFLOW.md`, `design/` |
|
||||||
|
| Zed routing / hierarchical | AiRouter | `router/*`, `config/orchestration.yaml` |
|
||||||
|
|
||||||
|
Перед крупным планом: **утвердить** шаги с пользователем; не раздувать scope (не трогать соседний репо «заодно»).
|
||||||
+18
-15
@@ -20,17 +20,20 @@ SoT: [`boards/calentiq-ui-board-final.png`](boards/calentiq-ui-board-final.png).
|
|||||||
| 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 **остаётся** обязательным |
|
| 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; compact mood на auth; taglines — в mood sheet AppShell (не в `/more`); **без** Google OAuth |
|
| Login §D phone chrome | три phone frames + (на борде) Google CTA | brand-first hero + mood `--auth-*`; email/password only; compact mood на auth; taglines — в mood sheet AppShell (не в `/more`); **без** Google OAuth |
|
||||||
| Mood placement | board §C switcher cards | **Front#40/#45:** mood chip в AppShell рядом с BrandWordmark (+ compact auth); mobile chip icon-only (полный CalenTIQ wordmark); **не** в `/more`, **не** в toolbar Month/Week/Day; 3 moods, без flip / CREATOR; switcher glyphs §C (sprout/bolt/briefcase), Time Arc appicon — только favicon |
|
| Mood placement | board §C switcher cards | **Front#40/#45:** mood chip в AppShell рядом с BrandWordmark (+ compact auth); mobile chip icon-only (полный CalenTIQ wordmark); **не** в `/more`, **не** в toolbar Month/Week/Day; 3 moods, без flip / CREATOR; switcher glyphs §C (sprout/bolt/briefcase), Time Arc appicon — только favicon |
|
||||||
| Lens + strip | board Today/Upcoming rail only | **Front#39 + controls IA:** lens Обзор/На сегодня/Заявки в **шапке strip** (underline tabs / mobile sheet); view Месяц/Неделя/День рядом с периодом; owner schedule actions отдельным рядом; localStorage only; soft-default по mood |
|
| Lens + strip | board Today/Upcoming rail only | **Front#46:** lens инфо-табло **под AppShell** (не в ViewMode toolbar); Обзор/Сегодня/Заявки + метрики; soft-default по mood без auto-pin; view Месяц/Неделя/День в calendar chrome; commercial owner: «+ событие» в toolbar, WeekFill во вкладке Студия |
|
||||||
| Week view headers | — | **Front#40:** одна строка day-headers (без дубля `WeekDayColumn`); scroll `.eh-cal-week-scroll` (mobile ~390) |
|
| Week view headers | — | **Front#40:** одна строка day-headers (без дубля `WeekDayColumn`); scroll `.eh-cal-week-scroll` (mobile ~390) |
|
||||||
| Favicon set | 3 отдельных favicon файла на борде | mood appicon через `applyMood` |
|
| Favicon set | 3 отдельных favicon файла на борде | mood appicon через `applyMood` |
|
||||||
| Desktop nav | phone bottom tabs на борде | desktop: **одна строка** chrome — logo + mood \| nav (Календарь / Найти / Записи / Ещё) \| nickname + logout; bottom nav — mobile |
|
| Desktop nav | phone bottom tabs на борде | desktop: **одна строка** chrome — logo + mood \| nav (Календарь / Найти / Записи / Ещё) \| nickname + logout; bottom nav — mobile |
|
||||||
| Header identity | — | nickname primary (не raw email); email только в `title` tooltip |
|
| Header identity | — | nickname primary (не raw email); email только в `title` tooltip |
|
||||||
| Bookings past/expired | — | `expired` бейдж + Cancel скрыт; empty-state с CTA Discover / календари; soft list time |
|
| Bookings past/expired | — | `expired` бейдж + Cancel скрыт; empty-state с CTA Discover / календари; soft list time |
|
||||||
| Agenda empty copy | — | personal «Нет событий»; commercial «Нет слотов» |
|
| Agenda empty copy | — | personal «Нет событий»; commercial «Свободных окон нет» |
|
||||||
|
| Default personal | — | **Front#46 / Back#64:** один personal title `Default` / UI «По умолчанию»; `/default`; overlay только на нём; create UI только Студия |
|
||||||
|
| Owner rail Team/Studio | — | **Front#46:** фиксированная высота panel; Команда invite свёрнут; Студия = hub (WeekFill + Edit); ScrollRegion стрелки |
|
||||||
| More invites | пункт меню | скрыт при pending=0; badge счётчика при pending > 0 |
|
| More invites | пункт меню | скрыт при pending=0; badge счётчика при pending > 0 |
|
||||||
| D9 guest specialist schedule | — | **модель B (Front#35):** roster карточек → тап → фильтр слотов по `event.specialist_id`; «Все слоты студии»; book без specialist в body; **не** chip-filter |
|
| D9 guest specialist schedule | — | **модель B (Front#35):** roster карточек → тап → фильтр слотов по `event.specialist_id`; «Все слоты студии»; book без specialist в body; **не** chip-filter; в «Все» — studio aggregate occupancy |
|
||||||
| Confirmed на personal | — | Front-only overlay confirmed чужих commercial на default personal; title `событие · студия · спец`; клик → `/c/:foreignCalId/e/:eventId` |
|
| Confirmed на personal | — | Front-only overlay confirmed чужих commercial на default personal; title `событие · студия · спец`; клик → `/c/:foreignCalId/e/:eventId` |
|
||||||
| SpecialistsPanel density | — | mobile: имя/теги сверху, actions снизу full-width (Front#33/#35) |
|
| SpecialistsPanel density | — | mobile: имя/теги сверху, actions снизу full-width (Front#33/#35); owner первым + бейдж «Владелец» + галочка «Я специалист» (Front#48) |
|
||||||
|
| Studio / Мастер commercial grid | board §E denser studio | Owner: Студия (агрегат `start_time`+`duration`, `K/N свободно`, popover) / Мастер (roster filter); месяц — density markers; rail Расписание\|Команда\|Студия; `booking_occupancy` из API; ★ 0.0 на free не показывать; personal без агрегата |
|
||||||
| Stage | полный smoke на stage | **done** 2026-07-27: Login×3 / Calendar×3 + agenda / Discover / CONTROL commercial + Confirm·Decline / More moods |
|
| Stage | полный smoke на stage | **done** 2026-07-27: Login×3 / Calendar×3 + agenda / Discover / CONTROL commercial + Confirm·Decline / More moods |
|
||||||
| Домены calentiq.* | — | публичный stage: `stage.calentiq.com` / `admin.stage.calentiq.com` (канон STAGE-BROWSER.md) |
|
| Домены calentiq.* | — | публичный stage: `stage.calentiq.com` / `admin.stage.calentiq.com` (канон STAGE-BROWSER.md) |
|
||||||
|
|
||||||
@@ -88,7 +91,7 @@ API: `GET /v1/user/booking-requests` (Back) + существующий `PUT /v1/
|
|||||||
изменений.
|
изменений.
|
||||||
3. **SpecialistsPanel density (mobile):** имя/теги сверху, actions снизу full-width.
|
3. **SpecialistsPanel density (mobile):** имя/теги сверху, actions снизу full-width.
|
||||||
|
|
||||||
### Mood + Lens (locked Front#39 / placement Front#40)
|
### Mood + Lens (locked Front#39/#40; dashboard Front#46)
|
||||||
|
|
||||||
1. **Mood** = визуальная атмосфера (`html[data-mood]`, FLOW/MOMENTUM/CONTROL). Primary UX —
|
1. **Mood** = визуальная атмосфера (`html[data-mood]`, FLOW/MOMENTUM/CONTROL). Primary UX —
|
||||||
chip → sheet в **AppShell** рядом с BrandWordmark (Front#40); compact на auth.
|
chip → sheet в **AppShell** рядом с BrandWordmark (Front#40); compact на auth.
|
||||||
@@ -97,16 +100,15 @@ API: `GET /v1/user/booking-requests` (Back) + существующий `PUT /v1/
|
|||||||
полный CalenTIQ wordmark; desktop — полный label; sheet — §C glyph + label + tagline.
|
полный CalenTIQ wordmark; desktop — полный label; sheet — §C glyph + label + tagline.
|
||||||
Glyphs: FLOW sprout, MOMENTUM bolt, CONTROL briefcase (не Time Arc appicon).
|
Glyphs: FLOW sprout, MOMENTUM bolt, CONTROL briefcase (не Time Arc appicon).
|
||||||
2. **Lens** = отдельный контентный режим (`overview` / `today` / `bookings`), не mood и не
|
2. **Lens** = отдельный контентный режим (`overview` / `today` / `bookings`), не mood и не
|
||||||
view month/week/day. UI: Обзор / На сегодня / Заявки. Переключатель — в шапке strip
|
view month/week/day. UI: Обзор / Сегодня / Заявки. **Front#46:** инфо-табло **под
|
||||||
(underline/text tabs на desktop; chip → sheet на mobile), **не** рядом с ViewMode.
|
AppShell** (surface + сегменты + метрики/chips), **не** рядом с ViewMode и не в
|
||||||
Soft-default при смене mood, если lens не pinned: FLOW→overview, MOMENTUM→today,
|
calendar toolbar. Soft-default при смене mood **без auto-pin** при клике:
|
||||||
CONTROL→bookings.
|
FLOW→overview, MOMENTUM→today, CONTROL→bookings.
|
||||||
3. **Strip** над сеткой (не на HTML-архиве месяца): Front-only derive — free gaps /
|
3. Контент lens (не на HTML-архиве): Front-only derive — free gaps / события сегодня /
|
||||||
события сегодня / upcoming bookings. Desktop раскрыт по умолчанию; mobile свёрнут
|
upcoming bookings. Workspace toolbar: селектор / период+view / «+ Новое событие»
|
||||||
(tap раскрывает). Сетка всегда видна. Workspace toolbar: контекст (селектор) /
|
(WeekFill — во вкладке Студия).
|
||||||
виджет (период + view) / owner schedule actions (отдельный ряд).
|
4. **Persistence:** mood → DB; language → `user.language`; lens — session + soft LS
|
||||||
4. **Persistence:** mood → DB; language → `user.language`; lens → `localStorage`
|
(без вечного pin от клика). Без нового Back API.
|
||||||
(`eh.calendar.lens` + pinned). Без нового Back API.
|
|
||||||
5. **Week view (Front#40):** одна строка day-headers (без дубля `WeekDayColumn`);
|
5. **Week view (Front#40):** одна строка day-headers (без дубля `WeekDayColumn`);
|
||||||
горизонтальный скролл `.eh-cal-week-scroll` (mobile ~390).
|
горизонтальный скролл `.eh-cal-week-scroll` (mobile ~390).
|
||||||
|
|
||||||
@@ -121,6 +123,7 @@ API: `GET /v1/user/booking-requests` (Back) + существующий `PUT /v1/
|
|||||||
| D9 B + confirmed personal overlay | **done** Front#35 (`57cfcf3`) |
|
| D9 B + confirmed personal overlay | **done** Front#35 (`57cfcf3`) |
|
||||||
| Mood chip + Lens strip | **done** Front#39 (`c2cb754` / `d38a927`); chip → AppShell Front#40; mobile icon-only Front#45 |
|
| Mood chip + Lens strip | **done** Front#39 (`c2cb754` / `d38a927`); chip → AppShell Front#40; mobile icon-only Front#45 |
|
||||||
| Week view single header + scroll | **done** Front#40 (`db11c41`) |
|
| Week view single header + scroll | **done** Front#40 (`db11c41`) |
|
||||||
|
| Studio / Мастер commercial grid + `booking_occupancy` | Spec: FrontSpec §3.1/§5.4 + BackSpec events; aggregate по `start_time`+`duration`, rail Расписание\|Команда\|Студия; follow-up visual polish board §E |
|
||||||
|
|
||||||
## Mood concept = product surface
|
## Mood concept = product surface
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user