- vite.config.di2.ts + client-di2/tsconfig.json: алиас @skin → client/src/themes (tokens, skins, generated-skins.css — единый источник токенов)
- client-di2/src/themes/Di2ThemeProvider.tsx: порт логики ThemeProvider основного клиента (localStorage['skin'] → скин организации → 'iistwin'; runtime CSS кастомных скинов через generateSkinCss; data-skin/data-theme/class light|dark на <html>; контекст с toggleTheme). Ключи 'skin'/'theme' общие с iistwin — выбор синхронизируется между приложениями
- main.tsx: обёртка <Di2ThemeProvider> вокруг App
- tailwind.config.ts DI2: цвета hsl(var(--x) / <alpha-value>) → var(--x), радиусы → var(--radius) с каскадом calc, добавлены warning/success/info
- client-di2/src/index.css: собственные HSL-тройки заменены fallback-блоками основного клиента (до гидратации), первой строкой @import общего generated-skins.css; DI2-only переменные (--card-border, --popover-border, *-border, --button-outline, --badge-outline) переопределены через color-mix от семантических токенов; удалены --opaque-button-border-intensity и hsl(from ...) relative-color вычисления; жёсткие --font-sans: Open Sans убраны (шрифты эмитит generateSkinCss из токенов скина)
- client-di2/index.html: lang=ru, viewport без maximum-scale + interactive-widget=resizes-content, мега-link Google Fonts (23 семейства) заменён на Inter + DM Mono как в основном клиенте
- Alpha-модификаторы семантических цветов (166 мест: bg-muted/50, border-primary/30 и т.п.) заменены на arbitrary values color-mix(in oklab, var(--x) NN%, transparent) — Tailwind v3 молча не генерирует /NN для var()-цветов; палитровые цвета (green-500/10 и т.п.) не тронуты
- server/finance/ (19 файлов) удалён: пул финансовой БД перенесён в server/finance-di2/db-core.ts, db-client.ts теперь реэкспортирует из ./db-core
- Шедулер аудита перенесён в server/finance-di2/audit-scheduler.ts (03:00 МСК, setInterval, без node-cron), запуск из registerDi2Routes с защитой от повторного старта
- История аудита DI2 теперь персистентна: загрузка/запись data/finance-audit-history.json в audit-agent.ts (порт из удалённого server/finance/audit.ts)
- registerFinanceRoutes убран из server/routes/index.ts, registerDi2Routes остался на его месте
- Клиент: удалены pages/finance/, components/finance/, hooks/useFinance.ts, hooks/useColumnLabels.ts, lib/format.ts, pages/salary.tsx; роуты /finance*, /income*, /expenses*, /salary и lazy-импорты убраны из App.tsx
- Навигация: «Финансы» в Sidebar и MobileBottomNav ведёт на субапп /di2 (внешняя ссылка), гейт moduleAccess.finance + finance.view сохранён
- index.css: удалён блок .hover-elevate/.hover-elevate-2/.active-elevate (использовался только старым модульным DataTable)
- FinanceBottomNav.tsx сохранён как референс для этапа 3 (.superpowers/sdd/di2-restyle/FinanceBottomNav.reference.tsx)
- Не тронуто: client-di2/, сиды прав finance.view/manage, finance_access (module-access)
DI2-роутер регистрируется перед основным dashboard.routes.ts и перехватывал
GET /api/dashboard (403 без finance.manage и неверный JSON — live-регрессия
главного дашборда CRM). Роут переименован, в DI2_PREFIXES /api/dashboard
заменён на /api/di2, клиент client-di2 ходит на новый путь (fetch + queryKey
+ prefetch в app-sidebar). Форматы старого модуля финансов не тронуты.
- Копия DI2-сервера в server/finance-di2/ с правками: schema/db-client/cache/table-config/override-tables/google-auth/audit-agent/routes
- db-client — обёртка над существующим пулом server/finance/db-client (второй пул не создаётся, добавлен экспорт isConnectionError)
- ai-config.ts — чистое IO конфигов из ai-agent.ts без Telegram-поллинга; роуты /api/ai/toggle и /api/ai/status удалены, без compression/startAuditScheduler/autoStartIfEnabled
- Обёртка registerDi2Routes с auth-gate (authenticateToken + finance.manage), регистрация строго перед registerFinanceRoutes
- Статика /di2 из dist/public-di2 перед веткой vite/static (dev и prod)
- Фикс предсуществующего бага DI2: buildGlobalExclusionConditions без cats в /api/profitability
- Раскладка: шапка-профиль → Основное/Контакты → Роль и доступ/Поля+Замещение → вторичные разделы чипами
- ProfileFieldRow: двойной клик/карандаш, редактор в слоте, Esc, optimistic-кэш, все типы полей
- Замещение: надпись + ⊕ popover, список под строкой
- Удалены RHF-форма, zod-схема и кнопки «Сохранить»
- server: PATCH /api/users/:id (частичное обновление, admin-градация, аудит)
- server: history-number в REST PATCH пишет историю + пересчёт средней
- UI: read-only поля отображаются текстом (у history-number остаётся кнопка графика),
исключены из сохранения; кнопка «Сохранить» скрыта, если все поля вкладки read-only
- Сервер: POST/PATCH field-values профиля отклоняют запись в поле с options.readOnly (400);
автоматизации пишут через storage напрямую и не затрагиваются
- Данные: options.readOnly=true у полей «Оценка» и «Средняя оценка»
- automation-worker-code: getFormFields возвращает поле code (не fieldCode) — поиск поля всегда завершался ошибкой 'Field ... not found', ошибка терялась
- automation.routes: результаты runAutomationsByTrigger (логи, ошибки) теперь пишутся в консоль сервиса с префиксом [automation]
Раньше assigned_to писался только в legacy-колонку tasks.assigned_to, а запись
в task_assignees (модель исполнителей) не создавалась — из-за этого автоматизации,
читающие ctx.tasks.getAssignees (например «Ответственный в состав бригады»),
видели пустой список исполнителей для задач, созданных через MCP
- setUserProfileFieldValueFromAutomation: для history-number значением поля становится переданное число (последняя оценка), а не среднее
- recomputeUserFieldMonthlyAverage: среднее за текущий месяц пишется в поле, указанное в options.avgFieldCode history-number поля (если не задано — в само поле, обратная совместимость)
- Автоматизация «Оценка сотрудникам» (форма 11): сумма трёх числовых полей «Оценка 1/2/3» вместо одного select-поля
- Документация sandbox обновлена во всех четырёх местах (UI, MCP, Obsidian, AGENTS.md)
- setFieldValue для history-number: переданное число пишется в историю, значением поля становится среднее за текущий месяц (автопересчёт)
- ctx.users.recalcFieldAverage(userId, fieldCodeOrId): принудительный пересчёт среднего за месяц (для schedule-автоматизаций; нет записей — поле очищается)
- документация обновлена (Automations.tsx, mcp.ts)
Инфраструктура автоматизаций:
- триггер task.status_changed оживлён: вызов из executeTaskTransition (fire-and-forget) + фильтр triggerConfig.statusId
- ctx.users.setFieldValue(userId, fieldCodeOrId, value): запись user_profile_field_values с аудитом; для history-number — append в user_field_history (миграция 0078)
- документация sandbox обновлена (Automations.tsx, mcp.ts, AGENTS.md) по критическому правилу 2
Мультивыбор пользователей и ролей в user-поле:
- чекбокс «Множественный выбор (пользователи и роли)» в настройках поля (form_fields.allow_multiple)
- значение — массив ['user:ID','role:ID'], обратная совместимость с одиночными/legacy значениями
- UserRoleMultiSelect в карточке задачи, реестре и диалоге создания (роли добавлены и в одиночный сценарий создания)
- синк в task_assignees итерирует массив; role:ID раскрывается в членов роли; PATCH single — diff старого/нового значений
- отображение массивов: реестр, title-шаблоны, условия полей, MCP update_task_fields
- список орг-ролей (/api/roles) загружается всем пользователям (сервер и так был открыт) — роли доступны не только админам
График history-number в профиле:
- GET /api/users/:id/field-history/:fieldId?period=week|month|quarter|year (агрегация AVG по бакетам)
- диалог с LineChart (recharts) и переключателем периодов по клику на иконку у поля
Без этого на Android при открытии клавиатуры viewport не уменьшался,
и поле ввода сообщения в чате/задаче перекрывалось клавиатурой
или «убегало» вверх вместе с контентом.
- openOrFocusChat: includeUncontrolled=true, логирование, try/catch, fallback на clients.openWindow.
- notificationclick: логирование data, fallback построения URL из вложенного data.data.
- markNotificationRead: поддержка type/messageId/conversationId на верхнем уровне и во вложенном data.
- Bump CACHE_NAME до v12 для принудительного обновления SW.
- В настройках кнопки «Создать задачу по форме» добавлен выбор связи: новая задача — родительская или дочерняя (task_relations).
- Добавлено копирование значений полей из текущей задачи в новую через визуальный маппинг в отдельном диалоге (ButtonCopyFieldsDialog).
- Маппинг хранится в form_fields.options.copyFields, связь — в options.relationType.
- CreateTaskDialog/CreateTaskFormContent поддерживают initialValues (объектные значения) и parentTaskId.