Commit Graph

271 Commits

Author SHA1 Message Date
3964d861ae fix(gps): yandexMapDisablePoiInteractivity — отключены POI-хотспоты Яндекса (403 search/v2, вмешательство в клики по маркерам) 2026-09-23 10:58:19 +03:00
5c68d84cff fix(csp): разрешён core-renderer-tiles.maps.yandex.net (script-src) — Яндекс.Карты 2.1 грузит JSONP hit-test объектов с него, блокировка CSP ломала клики по маркерам 2026-09-23 10:25:27 +03:00
d238b005f4 fix(gps): двойной путь доставки клика по маркеру — data-gps-action на корне (capture-делегирование) + placemark.events 2026-09-23 10:04:06 +03:00
dadd49c15f fix(gps): свои панели вместо попапов карт — клик по точке показывает карточку задачи, клик по кластеру — вертикальный список (устранена ненадёжность balloon/popup) 2026-09-23 09:16:55 +03:00
6c1cea39b4 fix(gps): клик по точке открывает попап с названием (делегирование только по data-gps-action); спайдерфай кластеров в одной координате на max zoom 2026-09-23 08:49:15 +03:00
27a14ec513 feat(gps): клик по точке объекта показывает название (попап + «Открыть задачу»); точки объектов кластеризуются, список кластера вертикальный 2026-09-22 22:11:38 +03:00
dfedb3ebfa feat(gps): настройки слоя «Объекты» сохраняются (форма/поле/названия), подписи в две строки: объект / заказчик 2026-09-22 21:39:27 +03:00
fe174535b7 feat(gps): кнопка «Названия» для слоя объектов (по умолчанию скрыты); подписи сокращены до «объект · заказчик» (≤40 символов) 2026-09-22 21:15:31 +03:00
42894aaf8f feat(gps): на вкладке «Карта» слой задач формы (селектор формы+geo-поля) + создание объекта правым кликом с координатами 2026-09-22 20:59:54 +03:00
fe57e368da fix(gps): подложка OSM-режима на Esri World Street Map (CARTO теперь требует API-ключ — водяные знаки) 2026-09-22 20:08:19 +03:00
db6b18f679 fix(gps): OSM-подложка переведена с tile.openstreetmap.org (403 Access blocked в проде) на CARTO Basemaps 2026-09-22 19:54:13 +03:00
8d11ad3b33 fix(auth): список сессий без мусора ротаций — фильтр isReplaced, перенос UA/IP, чистка старых
- listUserSessions: + isReplaced = false — иначе каждый рефреш токена добавлял
  в список устройств «новую сессию» (цепочка ротации видна только для детекта кражи);
- rotateFamilySession: userAgent/ipAddress переносятся из исходной сессии —
  rotated-строки больше не «Неизвестное устройство»;
- новый worker session-cleanup (тик раз в сутки + прогон при старте): удаляет
  replaced-сессии старше 72ч (grace-окно детекта кражи) и истёкшие.
2026-09-22 09:01:59 +03:00
13db5be030 fix(chat): React #310 — ChatSection вынесен на module level вместо useCallback
useCallback для ChatSection стоял после раннего return (if (!task)) —
в рендерах без задачи хук не вызывался → «Rendered more hooks than during
the previous render». Правильное решение: компонент на module level с пропом
taskId (стабильный тип + нет зависимости от хуков).
2026-09-21 22:10:57 +03:00
693834ac27 fix(chat): настоящая первопричина дёрганья — remount чата из-за inline-компонента ChatSection + первый кадр не у низа
- TaskDetail: ChatSection объявлен inline в теле рендера → новый тип компонента
  на каждый рендер → React перемонтировал весь чат при ЛЮБОМ ре-рендере TaskDetail
  (SSE-событие, refetch задачи, возврат на вкладку): скролл сбрасывался вверх,
  пользователь видел «старые сообщения → прыжок к последним». Фикс: useCallback
  со стабильной идентичностью (тот же паттерн, что у мобильного accordion).
- TaskChat/useChatController: скролл к низу при открытии чата — useLayoutEffect
  СИНХРОННО ДО paint (первый кадр уже у низа) + докрутка первого кадра, когда
  сообщения пришли после монтирования без кэша.
2026-09-21 21:33:26 +03:00
d0d73a59f2 ci(deploy): app-образ собирается в GitHub Actions и пушится в GHCR — сервер только скачивает
Раньше образ собирался на VPS (3 ядра) при каждом деплое: 13–15 минут даунтайма.
Теперь: job build-image (buildx + кэш слоёв GHA) пушит ghcr.io/iisultanov/iistwin-app:latest
и :<sha>; SSH-скрипт скачивает образы ДО остановки контейнеров (даунтайм ~10 секунд
на подмену), fallback на сборку на сервере сохранён при недоступности GHCR.
Инфра-репо: сервису app добавлен image: (iistwin-infra@0960563).
2026-09-21 20:59:31 +03:00
34838a302d feat(chat): онлайн-доставка как в Telegram — watchdog SSE, тихий скролл, персистентный кэш сообщений, невидимое обновление SW
- SSE: серверный heartbeat теперь именованное событие ping (25с) — клиент может отличить живое соединение от мёртвого; клиентский watchdog (45с без активности → принудительный reconnect), reconnect 1с вместо 3с, polling-фолбэк 5с вместо 10с
- Скролл: на скрытой вкладке скролл не трогаем (раньше автоскролл «выбрасывал» пользователя вниз при возврате); возврат на вкладку — pin к низу если был у низа; при чтении истории плавающий индикатор «↓ N новых» (оба чата)
- Персистентный кэш последних 100 сообщений чата в localStorage → initialData: мгновенный рендер после перезагрузки страницы без «Загрузка сообщений...», свежие данные в фоне; gcTime: Infinity
- SW: reload на controllerchange только на скрытой вкладке (обновление незаметно), предыдущая версия кэша сохраняется для ленивых чанков старой страницы; кэш v15
2026-09-21 20:29:13 +03:00
f8d78afd2e feat(auth): retry рефреша при 401 + вкладка «Сессии» в профиле с отзывом устройств 2026-09-21 18:13:47 +03:00
c957a70260 feat(auth): access 24ч, jti против гонок ротации, транзакционная ротация, отзыв сессий по sid 2026-09-21 18:13:46 +03:00
2f9d049718 fix(chat): catch-up сообщений при возврате на вкладку; fix(sw): фокус на открытой вкладке вместо навигации всех; fix(history): числа не резолвятся в имена пользователей 2026-09-21 17:03:01 +03:00
1f878b61a1 feat(chat): персистентные черновики сообщений — текст переживает перезагрузку страницы 2026-09-21 15:14:30 +03:00
006d020d15 fix(fields): миграция 0085 — верное имя unique-констрейнта form_fields_form_id_code_unique 2026-09-21 14:46:57 +03:00
e3f0ebfb89 feat(fields): мягкое удаление полей формы — архив удалённых с восстановлением и блок «Удалённые поля» в задаче 2026-09-21 14:35:45 +03:00
d5f2925886 feat(tags): полный тинт строк/карточек, цветное значение поля вместо чипа в шапке; защита от дубль-клика при открытии задач 2026-09-21 14:35:45 +03:00
c1463c71a1 feat(fields): цветные опции select — глобальная подсветка задач (главная, реестр, канбан, шапка) 2026-09-21 11:13:44 +03:00
aa316141be feat(reminders): получатели в отложенных/входящих, приватные сообщения чата, календарь, бейдж уведомлений 2026-09-21 10:26:53 +03:00
17e6ebf412 fix(ui): удалён FAB закладок — перекрывал поле ввода чата в PWA
FAB (z-40, fixed right-bottom) ложился поверх композера чата и кнопок отправки.
Добавление закладки уже доступно: меню значка «Закладки» в MobileBottomNav
(мобильный) и сайдбар (десктоп) — функционал дублировался.
2026-09-21 08:24:08 +03:00
5c308274f2 docs(log): auth/gps/chat фиксы — логины, 429-шторм, скачущий чат, упоминания 2026-09-17 22:02:10 +03:00
267af046a3 fix(chat): автоскролл задач только у низа, картинки с фикс. размером, упоминания по @ и 3+ буквам имени/фамилии
- TaskChat: atBottomRef-гвард (scrollHeight-scrollTop-clientHeight<80) — чужие
  сообщения больше не дёргают чат вниз, когда пользователь читает историю;
  свои сообщения и смена задачи скроллят как раньше
- ChatAttachments: img w-180 h-180 object-cover вместо max-* — высота строки
  не меняется при догрузке, measureElement виртуализатора не дёргает список
- упоминания: голый @ открывает список сразу; текстовый триггер от 3 букв;
  сервер ищет ILIKE по имени/отчеству/фамилии/email и отдаёт всех при пустом q;
  починен мёртвый fallback-резолв (Response без .json())
- groupByDate/flattenGroupedMessages обёрнуты в useMemo
2026-09-17 22:00:10 +03:00
d0112657b8 fix(auth): живая сессия на устройстве — рефреш на холодном старте, grace-окно ротации, без логаута при временных ошибках
- /api/auth/me при 401 делает одну попытку refresh (раньше refresh-retry был
  отключён для всех /api/auth/* — холодный старт после 15 мин простоя = логаут)
- ротация: повтор старого refresh-токена в пределах RACE_TOLERANCE_MS (5 мин)
  выдаёт новую пару от successor вместо deny; cookie больше не стираются при
  stale-токенах (разлогинивались все вкладки устройства); reuse после окна —
  revoke family как раньше (тесты обновлены)
- refreshSession различает 401/403/400 (сессия мертва) vs 429/5xx/сеть
  (transient — нет logout, отложенный retry)
- единый рефреш: useAuth keep-alive и useOfflineSync переведены на refreshSession;
  межвкладочная дедупликация через BroadcastChannel('auth-refresh')
- refreshLimiter: keyGenerator buildRateLimitKey (per-user, не общий IP-бакет офиса)
2026-09-17 21:59:51 +03:00
c1d5b1cbee fix(gps): остановлен 429-шторм — throttle SSE gps_asset_moved (3 сек/asset) и клиентских инвалидаций (60 сек), polling позиций 60 сек, статусов 5 мин
Каждая точка ingest (~1/сек) broadcast'илась SSE всем клиентам орг., клиент
на каждое событие делал invalidateQueries без throttle → 2+ GET/сек → исчерпание
per-user бакета apiLimiter (1500/15мин) → все API устройства 429 (формы не грузились).
Модель теперь как у нормальных трекеров: точки пишутся в БД, карта читает по интервалу.
+4 unit-теста троттла (server/gps/sse-throttle.ts)
2026-09-17 21:59:34 +03:00
e4f9a1992a feat(automations): ctx.tasks.scheduleStatusChange — отложенная смена статуса (воркер 5с, отмена при ручном переходе) 2026-09-17 16:38:56 +03:00
c1fe1c4e02 fix(registry): непрозрачный фон sticky-шапки, отказ от table-fixed
- шапка: [&_th]:bg-background вместо полупрозрачного bg-muted/50 — иначе сквозь
  sticky-шапку проступают строки и она нечитаема (в plain-варианте фона не было вовсе)
- ресайз: table-fixed + w-full таблицы + w-max wrapper давали циклическую зависимость
  ширин — колонки без заданной ширины схлопывались до нуля при первом drag.
  В auto-раскладке <colgroup> задаёт предпочтительную ширину без схлопывания

check/build чисто, vitest 129/129
2026-09-14 23:54:10 +03:00
9c1cd347bc docs(log): реестр — горизонтальная прокрутка, sticky-шапка, ресайз колонок, лимит строк 2026-09-14 23:31:10 +03:00
e32def495d feat(registry): горизонтальная прокрутка внизу области, sticky-шапка, ресайз колонок, лимит строк в ячейке
- ui/table: пропс wrapperClassName (twMerge), в реестре wrapper = w-max min-w-full
  overflow-visible — переполнение по ширине всплывает в контейнер реестра,
  скроллбар оказывается внизу видимой области, а не под всей таблицей
- TableHeader sticky top-0 (фон tr для plain-варианта скина)
- drag правого края шапки меняет ширину колонки (table-fixed + colgroup,
  min 80 / max 1200 px), двойной клик — сброс; ширины хранятся в localStorage
  per-form (registry-colwidths-<formId>) — у каждого пользователя свои
- настройка «Строк в ячейке» (Авто / 1..8) в диалоге настроек реестра:
  CSS line-clamp на текстовых ячейках, хранится в registry-rowlines-<formId>;
  оценка высоты виртуализатора ниже в однострочном режиме

check/build чисто, vitest 129/129
2026-09-14 23:21:02 +03:00
c8b7741b11 fix(ui): мобильный main стал flex-колонкой — flex-1 PageTransition больше не игнорируется, h-full-цепочки страниц восстановлены
На мобильном (<768px) main#content был блочным: flex-1 обёртки PageTransition
работал вхолостую, высота страницы росла по контенту, весь скролл уходил в main,
а скроллбар широкой таблицы реестра оказывался внизу недостижимой высоты.
Десктопный main уже был flex flex-col (Sidebar.tsx:1687).
2026-09-14 23:08:30 +03:00
981ee050ca docs(log): фикс двойного Sidebar 2026-09-09 08:24:03 +03:00
5af274cd0f fix(ui): двойной Sidebar на реестре задач и карточке задачи — TaskView/TaskDetail рендерили свой Sidebar поверх AppShell 2026-09-09 08:22:41 +03:00
e5d8d4ed37 docs(log): UI-эпопея — 7 фаз стабилизации и выравнивания UI 2026-09-08 22:12:49 +03:00
b94406e057 feat(ui): фаза 7 — self-host шрифты Inter/DM Mono (woff2, precache в SW v13), возвращён focus-visible (кольцо --ring), active:scale для кнопок (фикс мёртвого not-disabled), плавные появление/исчезновение биллинг-оверлея, длительности тостов по шкале токенов 2026-09-08 22:11:32 +03:00
820e8865a5 refactor(chat): фаза 6 — общий useMessagePolling, TaskChat на общий MessageBubble (слоты под task-специфику) и groupByDate, виртуализация мессенджера + автоскролл только у низа, TaskChat без RHF/zod (локальный стейт), анимация появления последнего сообщения; TaskChat 2023→1754 строк 2026-09-08 21:58:03 +03:00
aa8bf6ac33 feat(ui): фаза 5 — Тема 2.0 во всех базовых ui/* (select/textarea/tabs/popover/tooltip/dropdown-menu/checkbox/dialog на рецептах и токенах), data-density/control-variant/button-radius на html, консолидация размеров кнопок (44 места → size=sm), max-h+скролл диалогов 2026-09-08 21:23:25 +03:00
113ed458b6 feat(ui): фаза 4 — скелетоны PageSkeleton/ListSkeleton/CardSkeleton/FormSkeleton (мигрированы ~19 страниц вместо спиннеров), PageTransition на framer-motion (fade 150ms, MotionConfig reducedMotion=user, гейт skinComponents.motion.enabled), StaggerList для списков задач/диалогов/справочников 2026-09-08 21:10:08 +03:00
c4fdfe4eab refactor(ui): фаза 3 — единый AppShell (Sidebar не размонтируется при навигации), Layout стал тонким контейнером, SPA-навигация вместо location.href (уведомления, тосты, TableEditor), scroll restoration, плавное появление биллинг-оверлея 2026-09-08 20:48:28 +03:00
5d219ac794 fix(ui): фаза 2 — стабилизация данных: throttle+invalidate вместо refetch-пачек на SSE, единый useUnreadBadges (polling только при обрыве SSE), TaskChat polling только при потере SSE, one-shot guard в safeLazy, плавная смена скина (transition цветов) 2026-09-08 20:38:01 +03:00
7a98bf7bf3 feat(ui): фаза 1 — шкалы motion/z-index в токенах, boot-скрипт темы в index.html, рецепты компонентов (design-system/recipes.ts) для button/input/badge/card/table, layout.taskDetail в скине, удалён мёртвый CSS (.btn-compact и др.) и зависимость tw-animate-css 2026-09-08 20:26:14 +03:00
c8d7f9b336 fix(auth-ui): тихий фейл 400 при смене пароля, валидация, пароль при создании
PLAN-user-password-fixes.md (инцидент 2026-09-07, user 55/56):
- toServiceError в client auth.service: BadRequestError → {success:false, error}
- полная клиентская валидация пароля в ProfilePasswordRow
- опциональный пароль при создании пользователя (UserModal + POST /api/users)
- onError с toast в PollCard/ReactionBar (этап 4)
- 11 новых тестов (129/129)
2026-09-08 16:42:59 +03:00
0b2319b11d feat(automations): ctx.tasks.sendMessage — сообщение в чат задачи от Системы; userId в triggerData status_changed 2026-09-08 16:31:59 +03:00
163351b345 docs(log): шаги 0.3, 0.6, 0.14 — подтверждены на проде, этап E закрыт 2026-09-08 15:15:17 +03:00
a5ab294608 ci(deploy): compose из инфра-репо напрямую через -f (чистый git status на сервере)
Шаг 0.14 (доработка): без cp в /opt/crm — docker compose -f
/opt/crm-infra/docker-compose.prod.yml --project-directory /opt/crm
2026-09-08 15:02:33 +03:00
0fb3b54b66 ci(deploy): прод-compose из приватного инфра-репо iistwin-infra (шаг 0.14)
Убраны git stash кастомизаций и восстановление compose из бэкапа вне репо.
Источник правды для прод-compose: iisultanov/iistwin-infra (приватный),
клонируется в /opt/crm-infra deploy-ключом при каждом деплое
2026-09-08 14:27:29 +03:00