записи
Всё подтверждается автоматически, если кто-то не наложит вето до дедлайна. Это единственное место в продукте, где отсутствие за столом имеет цену — и где телефон её снимает. Вкладки переименованы: Ожидают и Утверждены.
Тот же язык строк, что у «Утверждены»: время и дата в ведущей колонке, имя клиента жирным, услуга и длительность подписью. Справа — сколько осталось до автоподтверждения, потому что очередь отсортирована именно по этому и это единственное, что здесь читают бегло. Тап по строке открывает детальную запись (следующий экран). Показывается при calendar:configure или любом из booking:reject/cancel — тот же расширенный гейт, что и в консоли.
Имена — настоящие, и пробел закрыт. Первая версия этого экрана рисовала Услуга 01a08a02 и Мастер 01a084ec, хотя PendingBookingResponse несёт workerDisplayName и serviceName с 26-50, а имя клиента приезжает из реестра персон чата по personId — тем же display-merge, что и на «Утверждены». Когда имени нет — маскированный телефон, потом «Без имени», но никогда не шестнадцатеричный код. Идентификатор календаря на экране не показывается вовсе: это адрес для записи на сервер, а не факт для человека.
Тот же нижний лист, что у детальной записи «Утверждены»: имя заголовком, дата слева и время справа одной строкой, «Услуга»/«Мастер»/«Телефон» строками с тонкими разделителями. «Телефон» рисуется только когда сервер его прислал — null здесь значит «нет права customer:read», а не «телефона нет», и прочерк на этом месте лгал бы.
Почему нет кнопки «Принять». Проверено по коду календаря, а не по ощущению: ConsoleEndpoints публикует для записи ровно три действия — reject, cancel, no-show. Операторского confirm нет: Event.Confirm вызывает только сборщик по дедлайну (право booking:confirm объявлено, но ни один эндпоинт его не проверяет). Очередь — список вето, а не список одобрений: «принять» — это ничего не нажимать, и лист говорит это прямо строкой «Подтвердится через 3 ч» вместо кнопки, на которую сервер не ответит. Ручное подтверждение — продуктовое решение и новый эндпоинт календаря, отдельной задачей.
Ровно два вето, и оба настоящие. Event.Reject принимает только PendingConfirmation; Event.Cancel — и PendingConfirmation, и Booked, под отдельным правом (booking:cancel против booking:reject) — поэтому оба на месте, равным весом. «Не пришёл» убран: Event.MarkNoShow принимает только Booked — неявка утверждает что-то о визите, который был подтверждён и не состоялся, и для ожидающей записи сервер отказывал на каждом нажатии. Тап по вето сначала закрывает лист, потом шлёт запрос: строка гаснет в списке, отказ сервера ложится в баннер над списком, успех перечитывает очередь — и всё это было бы скрыто за открытым листом.
Консоль показывает сетку неделя × мастер — единственный layout в продукте, который принципиально широкий. Android меняет оси местами: лента дат, затем этот день, сгруппированный по мастеру. Вопрос, на который он отвечает («что на четверг»), не меняется.
Строка клиента — это имя. Оно на первом плане, услуга и длительность — подписью под ним. Имя всегда берётся как есть и не проверяется (никаких «недействительно»). Когда имени нет — маскированный телефон и пометка «Без имени», но никогда не шестнадцатеричный код (сбор имени при записи — отдельная задача; телефон обязателен всегда).
Месяц и год — липкий, сжимающийся заголовок. Текущий месяц закреплён у левого края, пока его дни в ленте, тем же приглушённым шрифтом, что и подписи строк, год у каждого месяца. Когда следующий месяц выезжает справа и начинает наезжать на закреплённую подпись, та сжимается (обрезается многоточием), пока следующий не перехватит место и сам не станет закреплённым — и так в обе стороны прокрутки. Кадр справа ловит момент передачи: «Сентябрь» ужат почти в ничто, «Октябрь» вот-вот встанет к краю.
Два значка на строке — чат и телефон (слева направо), Material-глифы, оба всегда. Чат ведёт в диалог-источник записи (один на запись), телефон — к контакту. Тап по всей строке открывает детальную запись (следующий экран).
Момент передачи. Тот же экран «Утверждены», прокрученный так, что дни сентября почти ушли за левый край. «Сентябрь 2026» всё ещё закреплён у края — но подъехавший справа «Октябрь 2026» сжал его до «Се…». Ещё чуть — и «Октябрь» встанет к краю и сам станет закреплённым; прокрутка назад повторит всё в обратную сторону. Поведение чисто геометрическое (первый видимый чип + его смещение), поэтому одинаково воспроизводится в обе стороны.
Тап по строке открывает запись. Дата слева, время справа — двумя выключками в одну строку, без точки между ними. «Мастер» — отдельной строкой, как «Услуга». «Источник» — простым текстом (Виджет на сайте / Telegram / Макс), без стилизованных плашек.
Телефон — за «Показать» (аудируемое раскрытие, как в треде диалога). «Перейти к диалогу» ведёт в тот самый диалог, где создана запись — связь бронь↔диалог в обе стороны: из чипа записи в диалоге — сюда, отсюда — в диалог (новые поля origin_conversation_id на брони и result_booking_id на задаче чата).
Сегментов не может быть больше трёх. Услуги и Часы сначала легли четвёртым и пятым сегментом — на реальном телефоне подписи стали переноситься и обрезаться, ряд физически не резиновый. Правило на будущее: операционное остаётся в сегментах, настроечное уходит за ⋮.
Разделитель простой — спроси не «часто ли этим пользуются», а «есть ли что-то срочное прямо сейчас». Ожидают/Утверждены/Клиенты — живые люди и решения с дедлайном. Услуги и Часы — словари: их читают чаще, чем правят, и ничего не «горит», если зайти туда на день позже.
Открывает то же самое: пункт меню ведёт на уже существующий экран (Услуги/Часы) без изменений внутри него — переезжает только точка входа. Тот же приём годится для любой вкладки, которая начинает разъезжаться: прежде чем резать текст или уменьшать шрифт, спроси, не пора ли часть сегментов сделать пунктами ⋮.
Ожидают: Подтвердить + Отклонить. «Отменить» убрана — на строке, которая сама подтвердится, если её не тронуть, осмысленное отрицание — это вето (Reject), а не отмена подтверждённого визита. «Подтвердить» — главное действие, «Отклонить» — вторичное.
«Подтвердить» — это ранний ручной коммит того же перехода, что делает автосвип (Event.Confirm: PendingConfirmation→Booked). Домен уже так умеет: Event.Confirm намеренно не проверяет дедлайн, потому что и свип, и оператор — законные вызывающие. Не хватает только эндпоинта POST /bookings/{id}/confirm и use case; право booking:confirm уже объявлено и уже выдано роли «Оператор», но пока никем не потребляется.
Строка «Подтвердится через N ч» остаётся — это правда об автоподтверждении: если никто не нажал, свип подтвердит запись на дедлайне. Ручное подтверждение просто делает это раньше и снимает дедлайн; автосвип остаётся резервом.
Утверждены: добавлены «Перенести» и «Отменить». «Открыть диалог» и «Закрыть» остаются. «Отменить» — опасный тон (Booked→Cancelled, CancelledByOperator).
«Не пришёл» здесь не показана — визит ещё не состоялся. Event.MarkNoShow отказывает до конца слота, поэтому кнопка появляется только когда now ≥ EndsAt (следующий экран). Тот же урок, что 26-163 применил к очереди: не предлагать действие там, где сервер его отклонит.
Перенос — отдельное право (booking:reschedule, по аргументу гранулярности adr/0016) либо, как запасной вариант, по booking:cancel — решение автора. Новое право стоит парной строки в каталоге ago-chat и в сид-роли «Оператор» (adr/0093).
«Не пришёл» появляется только после конца слота. Тот же экран, но now ≥ EndsAt: кнопка «Не пришёл» (Booked→NoShow) теперь доступна. «Перенести» на прошедшем визите не показывается — двигать уже состоявшееся время бессмысленно; перенос — только для будущих записей.
Это — настоящее прибытие «Не пришёл» на Android: markNoShow есть в API-слое, но ни один экран его сегодня не вызывает (в WorkerSlotsScreen «Неявка» — только ярлык статуса). Консоль же предлагает «Не пришёл» на очереди «Ожидают», где сервер его отклоняет — это тоже чинится (перенести на подтверждённые).
Набросок флоу переноса. Та же лента дат, что и на «Утверждены», но показывает свободные времена того же мастера. Выбор одного времени включает «Далее». В v1 — только время: тот же мастер, та же услуга (длина прогона не меняется). Другой мастер/услуга — это «отменить и записать заново», отдельная задача.
Перенос в этой модели данных — это отпустить старый прогон и заявить новый. Строка тут — это и слот, и запись; переезда «на месте» нет (целевое время — уже свои Available-строки; возврата в Available у старых строк нет намеренно). Поэтому перенос = отмена старого прогона + атомарный клейм нового, в одной транзакции. Это предмет предлагаемого ADR.
Было → станет, одной парой. Новый прогон приземляется сразу в Booked: оператор, переносящий запись, и есть подтверждающий — нового окна вето нет (как и у ручного «Подтвердить»).
Атомарность названа вслух. Если целевое время займут в гонке — вся транзакция откатывается, старая запись цела. Свободный старый слот заново не открывается (наследуется от текущей отмены); стоит ли открывать — отдельное стоячее решение, оно касается и обычной отмены.
Шесть фактов решают, может ли незнакомец записаться. Сервер считает все шесть одним запросом (GET /booking-readiness) — и это цепочка зависимостей: каждый следующий пункт бессмысленен, пока не выполнен предыдущий. Консоль разносит эти шесть по пяти маршрутам и показывает сам список как пассивную панель на двух из них. Здесь цепочка — это и есть навигация.
Заголовок экрана — это буквально вопрос, который задаёт себе тенант, и он уже есть в консоли как строка: «Может ли клиент записаться прямо сейчас?». Разница в том, что здесь это экран, а не панель на другом экране.
Выполненные пункты свёрнуты в одну строку, первый невыполненный развёрнут и несёт ссылку прямо в то место, где он чинится. Ничего не пересчитывается на клиенте: шесть фактов считает сервер, иначе получилась бы вторая, спорящая копия той же конъюнкции.
Справочники доступны и плоским списком ниже — для того, кто пришёл переименовать услугу, а не выяснять, почему никто не записывается. Цепочка — это ответ на вопрос «почему не работает», а не мастер-визард, через который надо проходить.
Пять пронумерованных секций в том порядке, в котором человек на них отвечает. Главная правка против консоли — четвёртая: рабочие часы и шаблон графика живут здесь вместе. В консоли первое — форма на экране настройки, второе — секция этой карточки, и разъезд этих двух половин и есть та причина, по которой пункты 4 и 5 цепочки ощущаются как один и тот же вопрос, заданный дважды.
Пятая секция отвечает на «а что получилось» прямо здесь, а не через два экрана.
Консоль уже считает арифметическую фразу («услуга 70 мин займёт N слотов», зеркало серверного ComputeSlotsNeeded) — она сохранена дословно. Добавлены две цифры, которые тенант на самом деле хочет видеть: сколько дней покрывает горизонт и сколько слотов выходит в день. Читаются после сохранения, на этом же экране, со ссылкой прямо в нарезанные слоты.
Слово «Расписание» здесь не используется вообще: этот экран — График мастера, а экран выходных и коротких дней — Исключения. Двусмысленности, которую они заменяют, в консоли ровно столько, что её хватило, чтобы заполнять всё «как-то».
Пересчёт мог бы показаться слишком разрушительным для маленького экрана — но это типовая операция для тенанта, который рулит расписанием, и исключать её нельзя: тот, кто меняет график смен, должен починить уже нарезанные дни в тот же день, а не из десктопного браузера.
Карточка на запись, а не строка: смысл этого шага в том, что каждая запись — это чей-то визит. Кнопки «применить ко всем» здесь нет намеренно.
Шаг нельзя оставить наполовину. Счётчик «решено 4 из 7» закреплён внизу, и «Далее» не активна, пока хоть одна запись без решения. Консоль полагается на то, что оператор прокрутит таблицу и заметит; телефон так не может, поэтому счёт назван вслух.
Отменённые записи перечислены поимённо, а не сосчитаны — число «3» ничего не стоит, три конкретных визита стоят. Подтверждающая кнопка называет своё последствие в собственном тексте и остаётся единственной кнопкой опасного тона во всём флоу.
Диалога нет: подтверждение — вторая панель, а не модалка. Это собственная форма подтверждения, уже принятая в консоли (удаление мастера, стирание диалога), и она же здесь.