| 2026-10-10 |

Системные уведомления: транспорт Web Push, события агента, настройки (0.88)
...
О работе агента владелец узнавал только из открытой вкладки: SSE-кадры
task.claimed/task.review живут ровно столько, сколько она открыта, а агент
работает как раз тогда, когда вкладки нет. Web Push — второй транспорт тех же
событий на той же шине; SSE остаётся за мгновенный refetch интерфейса.
Backend: pywebpush и пара ключей VAPID (app/vapid.py печатает готовые строки
для .env; пустые ключи — push тихо выключен, а не падение на старте),
таблица push_subscriptions (endpoint уникален глобально: тот же браузерный
профиль может войти другим аккаунтом — подписка перепривязывается),
app/api/push.py (config/subscribe/unsubscribe/subscriptions), тексты
уведомлений на ru/en/uk собирает сервер — у service worker локалей нет.
Отправка идёт вне HTTP-ответа (BackgroundTasks в REST, поток в MCP) со своей
сессией; мёртвые подписки (404/410, серия ошибок) удаляются лениво.
Frontend: свой src/sw.ts через strategies: 'injectManifest' — в generateSW
обработчик push вставить некуда; precache, SPA-fallback и CacheFirst переехали
из vite.config.ts в воркер, у SW отдельный tsconfig (WebWorker не уживается с
DOM). Подавление при видимой вкладке — по visibilityState клиента, иначе
системное уведомление дублировало бы тост. Разрешение спрашиваем при входе
(жеста нет — браузер вправе проигнорировать), надёжный путь — переключатель
в Настройках.
Тесты: подписка/отписка/перепривязка, отправка с подменённым webpush, связка
событий. Фоновую отправку в тестах глушим: она идёт в чужом потоке по общему
SQLite-соединению и перемешивала транзакции тестов мандата.
ТЗ 0.88: §3.21 «Системные уведомления», правки §3.14, §3.15, §5, §7.
root
committed
10 hours ago
|

Журнал действий по задачам и API журнала (0.85)
...
- services/tasklog.py — единственная точка записи в task_events, общая для REST и
MCP: иначе «кто закрыл задачу» отвечалось бы по-разному в двух путях. Заголовок
хранится снапшотом, запись об удалении ссылку не оставляет вовсе (FK с ON DELETE
SET NULL обнулил бы её сам, но в SQLite тестов внешние ключи не проверяются, и
журнал ссылался бы на несуществующую задачу). status_transition() пишет переход
кодами («to_do→in_progress») — журнал не хранит переводов, подписи даёт интерфейс.
- api/task_events.py: GET /api/task-events — первый пагинированный список API,
конверт {items, total, page, per_page}, потолок per_page 100, фильтры по типу
события, актору, каналу, проекту и задаче; порядок — свежие первыми, id вторым
ключом (у записей одной секунды иначе нет устойчивого порядка).
- Запись событий из HTTP: создание, закрытие (с комментарием), смена статуса, выдача
и снятие доступности агенту, удаление. Пишутся только настоящие перемены: PATCH,
ничего не изменивший, журнал не засоряет.
- Запись событий из MCP: те же события в тех же местах; у update_task и complete_task
появился параметр comment — что сделано (у агента он станет обязательным вместе с
приёмкой).
- main.py: роутер журнала подключён; conftest: стаб mcp_ctx принимает актора, а
agent_ctx() даёт контекст агента (по умолчанию актора нет — действие владельца,
так вызывают тулы тесты, написанные до мандата).
- tests/test_task_events.py: 9 тестов — события из UI и MCP, фильтры, конверт
пагинации и потолок per_page, изоляция журнала по пользователю, снапшот заголовка
после удаления.
ТЗ 0.85.
root
committed
11 hours ago
|
| 2026-10-09 |

Проекты: цвет, сайт, фавикон, закрепление, README из репозитория (0.63–0.67)
...
- Цвет-метка проекта (0.63): у проекта может быть цвет-кружок. Селектор
цвета — сырым блоком в стиле кита (в ките компонента нет); кружок рисует
общий компонент ProjectColorDot.
- Сайт проекта (0.63): отдельное поле site_url. Иконку сайта тянет
бэкенд-прокси с кэшем (GET /api/projects/{id}/favicon, том favicons) —
сторонние favicon-сервисы не используются, данные не покидают VPS. В 0.65
иконка встала в метку проекта (кружок + фавикон), а ссылка на сайт —
чипом в шапке страницы проекта.
- Карточка списка кликабельна целиком (0.63; кнопка «Открыть» дублировала
ссылку), тайтл крупнее (0.65), клетки прогресса — поверх полосы кита
(0.64): сегментированной полосы в ките нет.
- Закрепление проектов (0.66): поле pinned, закреплённые — секцией сверху,
сортировка списка по свежести активности, подвал карточки — отсчёт до
ближайшего дедлайна.
- Место булавки переставлено (0.67): на карточке — правый верхний угол
рядом с бейджем актуальности и видна всегда (была приглушена в подвале),
на странице проекта — кнопка-булавка рядом с именем (искали строкой
«Закреплён» в параметрах).
- Репозиторий проекта (0.67): ссылка на git-репозиторий — отдельное поле
repository_url, отдельно от заметки и сайта. README из репозитория
показывается вторым описанием — карточкой «README из репозитория» под
заметкой, со ссылкой на репозиторий и сворачиванием длинного текста;
тянет такой же бэкенд-прокси с кэшем (GET /api/projects/{id}/readme),
не нашли — «README не найден». Распознавание ссылки на репозиторий в
заметке проекта снято (у задачи README-вместо-описания остаётся),
данные в БД не мигрировались.
- Фантомный отступ перед именем у проекта без цвета и фавикона (0.67):
ProjectMark рисовал пустой спан в ряду с gap.
- ТЗ 0.63–0.67; MCP зеркалит поля проекта (color, site_url,
repository_url, pinned). Тесты: 207.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
1 day ago
|
| 2026-09-21 |

Мультиюзерность: изоляция данных, per-user MCP-токены, claim при первом логине (ТЗ 0.34, бэкенд)
...
- Модели: User (профиль из SSO), McpToken (только sha256-хэш),
user_id на tasks/projects/tags/documents/xp_events/coin_events/garden_items;
уникальности имени проекта/тега — составные (per-user); app_settings →
per-user (PK user_id+key), старая таблица → app_settings_global (источник claim)
- Миграция f9a0b1c2d3e4 (Postgres): новые таблицы, user_id на 7 таблиц,
составные уникальности, частичные индексы дейли/уровня per-user
- Claim-on-first-login (ТЗ 1.2): первый вошедший забирает строки с user_id NULL
и копирует глобальные настройки; upsert профиля в callback
- Scoping всех API и сервисов: tasks/projects/tags/attachments/settings/xp/
garden + xp/garden/closing/options/recurrence/detailing; чужое — 404
- MCP (ТЗ 3.10): per-user bearer-токены вместо env MCP_TOKEN (middleware →
sha256 → request.state.gntodo_user_id), 8 тулов с Context; SSE publish(uid)
- API /api/mcp-tokens: список/генерация (plaintext один раз)/отзыв
- Тесты: test_multiuser (изоляция двух пользователей, claim, per-user дейли/
настройки), test_mcp_tokens; 135 passed, ruff, mypy strict
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
19 days ago
|

Пачка 1 ревью: безопасность + порча данных (находки 1-5)
...
Бэкенд:
- DELETE /api/tags/{id} требовал сессии только фактически — добавлен UserDep;
регрессия-тест проверяет require_user на каждом /api-роуте (не пройдёт для
любого нового эндпоинта без UserDep).
- Вложения: SVG отклоняется (скриптуемый формат — inline-отдача = stored XSS),
лимит 10 МБ на файл, X-Content-Type-Options: nosniff на отдаче.
- Open redirect закрыт: return_to только внутренние пути (не //, не внешние).
- MCP-токен сравнивается constant-time (hmac.compare_digest).
- Дефолтный SESSION_SECRET валит старт валидатором — молчаливо подделываемая
кука вместо этого даёт явную ошибку конфигурации.
- backend/data/ (runtime-вложения) в .gitignore.
Фронтенд:
- Черновик инлайн-правки больше не переезжает между задачами: при смене
/tasks/:id сбрасываются descEditing/draft, заголовочный InlineField
перемонтируется по key=taskId (PATCH старого текста в новую задачу).
- Утечка SSE-подписок: onRealtime сам отписывается при dispose scope —
N визитов на страницу больше не оставляют N load() на мёртвых инстансах.
- Out-of-order: токен запроса в load() TaskView/ProjectView — поздний ответ
старого id не перетирает данные нового; ListView — то же на фильтрах.
- Смена фильтра сбрасывает пагинацию: watch(filters, {deep}) (ТЗ 3.6).
- Двойной submit не создаёт дубликаты: busy в TaskForm и quickAdd стека.
- Утечка Pixi: unmount во время init уничтожает рендерер после резолва.
- Оффлайн-старт PWA: /auth/me без сети не роняет onMounted молча.
Тесты: 103 passed (+6: auth-все-роуты, SVG, oversize, nosniff, return_to,
дефолтный секрет). ruff/mypy чисты.
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
19 days ago
|
| 2026-09-19 |
i18n: ru/en/uk, язык из gnexus auth + переопределение в настройках
...
- vue-i18n v11 (legacy:false, fallback ru); локали src/locales/{ru,en,uk}.ts,
en/uk типизированы как typeof ru — полнота проверяется vue-tsc
- резолюция: settings.language (override, ''=auto) → SSO profile.locale → ru
- backend: /auth/callback прокидывает profile.locale в сессию; /api/settings
получил поле language, PUT стал частичным (model_fields_set)
- taskui.ts и все views переведены на t(); дни недели и даты через Intl
- в Настройках — карточка «Язык интерфейса», переключение мгновенное
- тесты: test_settings.py (частичный PUT, 422), AUTH_USER += locale
- ТЗ 0.5: новый пункт 3.11 «Мультиязычность»
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
21 days ago
|
M5: MCP-сервер для ИИ-агентов (ТЗ 3.9)
...
- FastMCP (streamable HTTP), монтируется на /mcp основного приложения
- инструменты: create_task / update_task / complete_task / list_tasks / get_task
- авторизация: bearer-токен MCP_TOKEN (пусто — /mcp закрыт); SSO-токены — после
предоставления данных (открытый вопрос ТЗ)
- автодетализация созданных агентом задач — в фоновом потоке
- lifespan: запуск session manager для mounted ASGI-приложения
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
21 days ago
|
M2: автодетализация (Ollama) и вложения
...
- LLM-детализация в фоне при быстром захвате: черновое предложение
тегов/проекта/приоритета (только из существующего справочника),
применяется только при утверждении («Да, всё верно»)
- Эндпоинты approve (apply_proposal) и redetail; Attachment-модель и
API вложений (изображения, FileResponse)
- Важное исправление: в FastAPI 0.141 фоновые задачи выполняются до
teardown-коммита зависимости get_db — create/redetail теперь коммитят
явно, иначе background worker не видит задачу
- Тесты detailing/attachments с моком LLM; tests как пакет (иначе
дублировался модуль conftest и в памяти оказывались две БД)
- Фронт: чипы предложения, опрос стека, Markdown-редактор с
предпросмотром (marked + DOMPurify), вставка изображений Ctrl+V
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
21 days ago
|
M1: MVP задач — быстрый ввод, стек входящих, ручная детализация
...
- Модели Task/Project/Tag (SQLAlchemy 2.0) + миграция alembic
- API: POST/GET/PATCH/DELETE /api/tasks, approve (raw→approved), проекты, теги
- Все /api эндпоинты за сессией SSO (регрессионный тест на 401)
- Frontend: api-клиент, StackView — быстрый ввод одной строкой, стек,
детализация с проектом/тегами/приоритетом, «Да, всё верно»
- dev-БД: docker postgres:17 на 15432
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
21 days ago
|
M0: OAuth-флоу через gnexus-gauth
...
- /auth/login → redirect на SSO с PKCE, /auth/callback → обмен code, сессия-cookie
- /auth/logout, /auth/me, защищённый /api/me
- SessionMiddleware (подписанная HttpOnly cookie, секрет из окружения)
- frontend: Войти/Выйти в навбаре, proxy /auth на backend
- тесты: health, защита API, redirect с state/PKCE
Co-Authored-By: Claude Code <noreply@anthropic.com>
root
committed
21 days ago
|