# Техническое задание: gntodo

Персональный менеджер задач для повседневных дел и личных проектов.

| | |
|---|---|
| Версия ТЗ | 0.28 |
| Дата | 2026-09-21 |
| Статус | На обсуждении |

---

## 1. Общие сведения

### 1.1. Назначение

Продвинутый личный таск-менеджер. Ключевая идея — **минимальный трением ввод**: задача закидывается в систему в одну строку, без выбора проекта, тегов и приоритета. Система сама делает предварительную детализацию, а пользователь позже утверждает или правит её при разборе.

### 1.2. Пользователи

- Один пользователь (владелец).
- Авторизация — через централизованный SSO-сервер **auth.gnexus.space** с клиентской библиотекой **gnexus-gauth** (см. 6.4).
- Косвенные «пользователи» — ИИ-агенты, действующие от имени владельца через MCP.

### 1.3. Развёртывание

- Собственный сервер (VPS), доступ из интернета.
- HTTPS, реальный домен.
- Данные хранятся только на своём сервере.

## 2. Сценарии использования

**Сценарий А — веб-интерфейс (основной).** Разбор стека входящих, детализация и утверждение задач, работа с проектами, планирование, просмотр дерева задач.

**Сценарий Б — ИИ-агент через MCP.** Агент от имени пользователя добавляет задачи (например, из переписки или диалога с LLM). Большинство AI-задач выполняется на стороне агента; сервер предоставляет MCP-интерфейс к данным.

## 3. Функциональные требования

### 3.1. Быстрый захват задач (стек входящих)

- Задача создаётся одним текстом (заголовок). Выбор проекта, тегов, приоритета — **не обязателен** и не предлагается принудительно при вводе.
- Такие «сырые» задачи попадают в стек входящих (`raw`) и ожидают детализации.
- Стек — отдельное представление с количеством входящих, видимое с главного экрана.

### 3.2. Автоматическая предварительная детализация

При попадании задачи в стек система самостоятельно предлагает черновые метаданные:

- теги — максимум 3, выбираются из существующего справочника; если подходящих нет,
  LLM может предложить 1–2 новых (создаются при утверждении);
- проект — выбирается из **открытых проектов с их описаниями** (LLM видит заметку
  каждого проекта, чтобы соотнести задачу по смыслу); если ни один не подходит —
  краткое название нового проекта;
- короткий заголовок — если исходный перегружен деталями, суть переносится в описание;
- описание с нумерованными шагами — если шаги выполнения очевидны;
- возможно, приоритет и оценка длительности.

Требования:

- Это **не окончательная** детализация — промахи ожидаемы и нормальны.
- При разборе задачи в стеке доступны варианты:
  1. **«Да, всё верно»** — принять предложенные метаданные одним действием;
  2. **отредактировать** — изменить предложенное вручную, затем утвердить.
- Задача считается утверждённой (`approved`) только после явного действия пользователя.
- Ответ LLM валидируется: строки-заглушки («NULL», пустые/пробельные, «n/a»)
  отбрасываются, длина ограничивается, дубли тегов схлопываются; вне шкалы
  приоритет и длительность обнуляются.
- Механизм предсказания — см. 3.11 и раздел открытых вопросов.

### 3.3. Содержание и редактирование задачи

- Описание задачи — **Markdown** (редактирование с форматированием).
- Вложения — изображения, в том числе **вставка из буфера обмена** (Ctrl+V прямо в редактор).
- Отображение Markdown в описании (просмотр и редактирование).
- **Чекбоксы списков задач** (`- [ ]` / `- [x]`) в отображаемом описании кликабельны:
  клик переключает пункт и сохраняет статус в markdown описания.
- **Инлайн-редактирование** на странице задачи: каждое редактируемое поле правится
  прямо на месте (клик по значению → редактор на его месте, ✓/✗ или Esc); пустые
  поля тоже показываются («— не указано») и редактируются кликом. То же — на
  странице проекта (название, заметка, актуальность, приоритет). Полный режим
  правки (вся форма целиком) сохраняется наряду с инлайном.

### 3.4. Статусы, дедлайны, прогнозы, бюджет

**Тип задачи**: каждая задача имеет тип — **разовая** или **регулярная** (повторяющаяся; см. 3.5).

**Статусы выполнения**:

| Статус | Смысл |
|---|---|
| `to_do` — К выполнению | Принята в работу, ожидает выполнения |
| `in_progress` — В работе | Выполняется сейчас |
| `done` — Завершено (закрыто) | Выполнена |
| `cancelled` — Отменено | Была актуальной, затем перестала быть актуальной. **Не равно удалению** — задача сохраняется, т.к. может снова стать актуальной |
| `deferred` — Отложено | На текущий момент не нужна или не в приоритете; возможно, позже это изменится |

- Статус `cancelled` и `deferred` — обратимы: задача может быть возвращена в `to_do`.
- Удаление — отдельное явное действие, не статус.
- Детализационный жизненный цикл (`raw` → `approved`) — **отдельная плоскость**, не статус выполнения: «сырая» задача из стека получает статус только после утверждения.

**Дедлайны — двух видов**:
  - *строгий*: «сделать до конкретной даты»;
  - *нестрогий*: «сделать в течение недели / месяца / года» — не привязан к конкретному дню, задаётся периодом.

**Прогнозирование времени выполнения** — система оценивает длительность задачи (на основе истории завершённых задач; алгоритм уточняется).

**Бюджет** — опциональный, на задачу; есть не у каждой задачи:
  - задаётся пользователем вручную (деньги и/или время);
  - рядом с бюджетом отображается **оценка затрат** по задаче, чтобы сразу видеть: задачу можно взять в работу или на неё не хватает денег.

### 3.5. Регулярные задачи

- Отдельный **тип задачи** — регулярная (повторяющаяся) в противовес разовой.
- Повторение создаёт новую задачу по правилу после выполнения/истечения предыдущей.
- Точный набор правил повторения (ежедневно/еженедельно/интервалы) — уточняется.

### 3.6. Представления задач

- **Классический вид**: список с фильтрами (проект, тег, статус, приоритет).
- **Пагинация**: длинные списки (список задач, стек на главной, задачи проекта,
  проекты, архив) разбиваются на страницы **по 30 элементов**; навигация кнопками
  (‹ номера ›, с усечением «…» и подписью «Страница N из M»). Смена фильтров
  возвращает на первую страницу; при одной странице контрол не показывается.
- Подзадачи показываются карточками внутри задачи и в списке; представление
  «дерево» убрано (проба 0.5 показала, что оно мешает — возможен возврат позже).
  Связь подзадачи с родителем (`parent_task_id`) в модели данных сохраняется.

### 3.7. Главная страница (дашборд + стек)

- Главная совмещает два блока: **дашборд активного** сверху и **стек входящих** снизу.
- Дашборд: «В работе», «Просрочено», дедлайны на неделю, «Следующие» (to_do по
  приоритету, топ-5); сайдбар — сводка по статусам и активные проекты с прогрессом.
- Пустые секции дашборда не показываются; при отсутствии и активных, и проектов —
  нейтральное пустое состояние.

### 3.8. Проекты

- Статус **актуальности** проекта (активен / приостановлен; «закрыт» выражается архивом, см. 3.8.1).
- **Приоритет** проекта.
- **Заметка к проекту**: Markdown-поле для ссылок на ресурсы проекта и прочего контекста; заметка — тот же «документ», что и описание задачи: в неё можно вставлять изображения из буфера (Ctrl+V).

#### 3.8.1. Архив

Проект, с которым пользователь закончил работу, **отправляется в архив** — один архив
на все проекты, отдельных архивов задач нет.

- Архивация — с подтверждением («Отправить проект и его N задач в архив?»).
- Архивный проект пропадает из рабочих видов: списка проектов, фильтров, выдачи
  задачи (3.9); его задачи не видны в активных списках задач. **Задачи живут и
  уезжают в архив только вместе с проектом**: отдельной архивации/удаления
  для задач внутри проекта нет (удаление отдельной задачи остаётся — гигиена).
- Статистика и XP не пересчитываются: закрытия в архивных проектах остаются в истории.
- **Страница «Архив»** (отдельный раздел меню): история архивных проектов —
  открытие проекта, все задачи, заметка. Восстановление возвращает проект
  со всеми задачами и статусами как были. Доступно и «удалить навсегда».
- У архивного проекта страница доступна на чтение (с плашкой «в архиве»);
  создание задач и правки спрятаны.

### 3.9. Выдача задачи (бывший режим «3 вариантов»)

Анти-прокрастинационный режим: пользователь указывает доступное время в часах (например, «3», можно 0.5), система предлагает подходящие задачи; предложение — те же карточки, что в списке задач.

- Выбор кандидатов учитывает: прогнозируемую длительность ≤ доступного времени, приоритеты, статус актуальности проектов.
- Варианты должны быть осмысленно разными (не дубликаты).
- Отдельной кнопки выбора/выполнения на странице выдачи нет: пользователь открывает карточку задачи (переход несёт `?via=options`) и берёт её в работу на странице задачи; закрытие задачи, открытой из выдачи, помечается «выбрал и сделал».

### 3.10. MCP-интерфейс

- Собственный **MCP-сервер** как часть backend.
- Инструменты (первичный набор, расширяется):
  - `create_task` — добавить задачу (достаточно текста; метаданные опциональны);
  - `update_task`, `complete_task`;
  - `list_tasks` / `search_tasks` — фильтры по проекту, тегу, статусу;
  - `get_task` — полное описание с вложениями.
- Аутентификация агентов — через SSO-механизм (токен, выданный централизованной системой; детали после предоставления данных SSO).
- Агент действует от имени пользователя; отдельной мультитенантности нет.
- Закрытие задачи агентом (`update_task`/`complete_task`) идёт по тому же общему
  пути, что и в UI: спавн регулярной, XP, монеты, растение (3.5, 3.13); в открытых
  вкладках — скромный тост о награде (3.14).

### 3.11. Локальные AI-функции

- Автодетализация (3.2) выполняется **маленькой LLM, запущенной локально через Ollama**:
  - модель и параметры — **конфигурацией приложения**, без зашивки в код;
  - LLM классифицирует текст задачи: предлагает теги (из справочника или новые), проект
    по описаниям открытых проектов, короткий заголовок, описание с шагами, приоритет;
  - результат — черновые метаданные для утверждения (3.2), не окончательное решение.
- Все вызовы LLM — на собственном сервере, данные не покидают VPS.
- Спекулярные LLM-сценарии вне метаданных — не в скоупе: большинство ИИ-задач выполняют внешние агенты через MCP (3.10).

### 3.12. Мультиязычность

- UI поддерживает три языка: **ru** (базовый/fallback), **en**, **uk**.
- Язык по умолчанию берётся из gnexus auth (`profile.locale` из SSO-профиля, scope `profile`).
- В «Настройках» возможно ручное переопределение языка; значение хранится в глобальных
  настройках приложения (`app_settings`, ключ `language`), пустое значение = следовать SSO.
- Даты, суммы и названия дней недели форматируются через Intl по активной локали.
- Переводится только собственный UI: gnexus-ui-kit текстов не содержит (тексты через props).

### 3.13. Геймификация

Маленькая дополнительная награда за выполнение задач — мотивация без негативных
последствий. Принцип **только позитив**: XP только начисляется, ничего не сгорает
и не отнимается; стрики/серии (которые «ломаются» при пропуске) не используются.

- **XP за закрытие задачи**: база 10 + бонус за приоритет (0–15) + бонус за
  объём по оценке времени (0–15). Начисление — один раз на задачу (идемпотентно);
  удаление задачи не отнимает заработанное. Хранение — таблица `xp_events`
  (поле `kind` разделяет события: `task` — закрытие, `create_task` /
  `create_project` — создание).
- **Микронаграды за создание** (без конфетти — скромный тост «+N XP • +M монет»):
  создание задачи — +2 XP и +1 монета, создание проекта — +5 XP и +2 монеты.
  В суммарный опыт (уровень) входят, растений в саду не рождают, счётчик
  закрытых задач не увеличивают; хранятся с `task_id = NULL` — удаление задачи
  или проекта награду не отнимает.
- **Ежедневная награда за визит**: при открытии приложения фронт запрашивает
  `POST /api/xp/daily` — раз в сутки (граница дня UTC) начисляется **50 монет**
  (только монеты, XP нет), повторные визиты в те же сутки молча пропускаются.
  Тост «+50 монет» только в день первого визита.
- **Уровень**: растёт из суммарного XP (уровень не падает); прогресс до следующего
  уровня и **название звания** показываются в «Саду». Звания ботанические
  (Росток, Сеянцев, Садовник, … Легенда сада — 13 званий; порог уровня
  L = 100·L·(L−1)/2 XP). **Дерево уровней** — модалка со списком всех уровней:
  звание, порог XP, что открывается (виды растений с ценой семени и
  premium-пометкой, расширения карты); пройденные отмечены, текущий подсвечен.
- **Момент награды**: при закрытии задачи — короткая конфетти-анимация и тост
  «+N XP» с похвалой. Особые закрытия (редкое растение, крупная задача) —
  усиленное «золотое» конфетти.
- **Сад** (отдельная страница): живая **сцена** — графическое представление сада
  с домиком в центре; вокруг сад на мелкой сетке (ячейка ~20px, привязка при
  перемещении, наложения разрешены). Каждая закрытая задача — растение в саду;
  сад только растёт, продавать и удалять растения нельзя. Растения и декорации
  можно перетаскивать (drag&drop). Рядом — уровень, **кошелёк** (баланс монет),
  **«последний росток»** (что и когда закрыто последним), **история по месяцам**
  (12 месяцев, столбики) и ачивки.
  - **Отрисовка сцены** — пиксель-арт на **PixiJS 8** (WebGL): `nearest`
    масштабирование без сглаживания, целочисленный «нативный пиксель» (матрицы
    16px × 2 = 32px на клетку). Все спрайты (тайлы травы/дикой земли, изгородь,
    домик, 13 видов растений × 3 стадии, декорации, кольца редкости, блёстки)
    генерируются из пиксель-матриц прямо в коде — внешних файлов-ассетов нет.
    Рендерер (`src/game/GardenRenderer.ts`) — обычный класс, **не** оборачивается
    во Vue-reactive (deep-прокси ломает внутренние identity-проверки Pixi);
    Pixi загружается динамическим импортом (отдельный чанк). Инвентарь, поповер
    и drag-ghost — DOM поверх канваса.
- **Две награды: опыт и монеты.** Опыт — «ранг садовника»: только растёт, ничего
  не открывает в магазине напрямую. Монеты — «валюта сада»: тратятся. Общий
  принцип: **контент открывается за опыт, покупается за монеты** — опыт (уровень)
  открывает виды растений и землю, монеты покупают рост растений, декорации и
  расширения карты.
- **Монеты**: начисляются при закрытии задачи — `floor(xp / 2)` (заголовок
  `X-Earned-Coins` рядом с XP в тосте), и бонусом **25 × L** при достижении
  уровня L. Хранение — таблица `coin_events` (журнал заработка и трат; баланс =
  сумма amount). Траты — отрицательные события (`source = shop`); продажи нет
  («только позитив»).
- **Редкие растения**: редкость решается в момент закрытия по весу задачи —
  чем больше XP, тем выше шанс редкого; эпик-растения — только у крупных
  закрытий. Редкость хранится в `xp_events`, визуально — золото + бейдж.
- **Растения и виды**: растение рождается только от закрытой задачи (отпечаток
  работы), вид выбирается детерминированно из пула видов, открытых **уровнем на
  момент рождения** (premium-виды — только для rare/epic закрытий), и
  фиксируется за растением (`garden_items.item_key`) — открытые позже виды не
  перерисовывают старые растения. Растение привязано к событию XP (переживает
  удаление задачи).
- **Семена растений** (покупка видов за монеты): в маркете блок **«Семена
  растений»** — открытые уровнем виды продаются семенами (цены 30–1200 по
  «дороговизне» вида; 13 видов, открытие растянуто по уровням 1–13 —
  прогрессия рассчитана примерно на 300 закрытых задач при среднем
  ~26 XP за закрытие). Покупка кладёт растение-росток в **инвентарь** (как
  декорации); перетаскивание из инвентаря на свободную клетку сажает его.
  Размещённое семя обратно в инвентарь вернуть нельзя (сад только растёт);
  редкость — common, задача-родитель отсутствует. Доступность семени — по
  уровню вида (🔒 «откроется на уровне N»), независимо от premium-флага вида
  (вопрос о premium-видах в семенах — открыт, см. раздел 8).
- **Рост растений за монеты**: растение растёт стадиями росток → куст →
  цветение, каждая стадия покупается монетами (цена по редкости:
  common 20/50, rare 40/100, epic 80/200). Автороста по времени нет.
- **Маркет сада**: в модалке — **расширения карты** (первым блоком),
  **декорации**, **семена растений** и каталог видов (справка о редкости).
  У каждой покупки три состояния: 🔒 «откроется на уровне N» (если уровень
  не открывает), цена золотом (кнопка активна), цена серым + «не хватает
  N монет».
  - **Декорации** — чисто косметика: фонарь, лавочка, поилка, пруд, беседка,
    флюгер, скворечник, садовый самоцвет, теплица (уникальные), изгородь,
    клумба и куст (повторяемые). Двигаются как растения. **Изгородь —
    самосоединяющиеся секции**: текстура выбирается по маске соседей (N/E/S/W),
    секция соединяется только с соседними секциями изгороди. **Постройки
    в изометрии (домик, теплица, беседка) — крупная изометрия 2:1** (домик
    с двором, теплица со стеклянными стенами и каркасом, беседка с площадкой
    у входа). Дорожки убраны (не прижились — сложность рендера и соединений).
  - **Подпись выбранного объекта**: клик по любому объекту сада (растение
    или декорация) показывает у клетки чип с иконкой и названием
    («Скворечник», «Куст», «Подсолнух»); у растений одновременно открывается
    поповер роста. Повторный клик по объекту или клик по пустому месту
    снимает выбор.
  - **Инвентарь декораций**: покупка кладёт декорацию в **инвентарь** под картой
    (`garden_items.x/y = NULL`) — место покупки всегда видно пользователю. Из
    инвентаря декорация перетаскивается на карту; перетаскивание размещённой
    декорации обратно на полосу инвентаря убирает её с карты (растения в
    инвентарь вернуть нельзя — сад только растёт).
  - **Расширения карты**: стартовая сетка 32×20 ячеек; каждое расширение
    (+8 клеток периметра) открывается уровнем (I — ур. 3, II — ур. 5,
    III — ур. 8, IV — ур. 9, V — ур. 11) и покупается монетами
    (100/250/500/750/1000). Финал ~72×60.
- **Ачивки с тирами**: у каждой ачивки ступени I/II/III (например, 10/50/100
  задач), прогресс к следующей ступени виден всегда. Набор: вехи закрытий,
  уровни, макс. задач за день, разные проекты, суммарные часы, строгие
  дедлайны, закрытия через выдачу задачи (см. 3.9), регулярная, проект.
- **Связка с выдачей задачи** (3.9): закрытие задачи, открытой из выдачи, помечается
  (`earned_via`) и празднуется меткой «выбрал и сделал» + счётчик-ачивкой;
  XP при этом не меняется.
- **Страница «Статистика»**: закрываемость за выбранный **день / неделю / месяц**
  (период листается стрелками назад/вперёд). Метрики — закрытые задачи, XP,
  созданные задачи, часы работы, «дни с результатом» — каждая со сравнением
  с **предыдущим аналогичным периодом** (дельта и проценты). Анимированный
  **сравнительный** график закрытых задач по бакетам периода (часы / дни недели /
  дни месяца): текущий и предыдущий период — пары столбиков. **Сетка активности**
  «как в гитхабе» за полгода или год (переключатель диапазона; цвет по закрытым
  задачам или XP, переключается).
  Данные считаются из `xp_events` и дат задач, без новых эндпоинтов.
- **Метка «ментально сложная»**: у незакрытой задачи есть кнопка-переключатель
  **«Не хочется делать»** (warning-стиль) — она ставит метку «ментально сложная»:
  трудность не по объёму, а из-за требуемых волевых усилий. Метку можно снять до
  закрытия, после закрытия она не меняется. Закрытие такой задачи даёт **+10 XP**
  (сила воли) и всегда празднуется золотым конфетти; ачивка **«Преодоление»**
  (5/25/50 закрытых «ментально сложных») — в саду.
- **Справка «О саде»**: кнопка на странице сада открывает модалку с объяснением
  механик (XP, уровни, растения и редкости, ачивки, принцип «только позитив»).

### 3.14. Реактивность: серверные события (SSE)

Данные меняются не только из UI: MCP-агенты (3.10) и фоновая автодетализация (3.2)
пишут в БД вне HTTP-запроса. Все виды обновляются событиями от сервера — без
обновления страницы и переходов между страницами.

- **Эндпоинт**: `GET /api/events` (авторизация штатная, 401 — на вход).
  Транспорт — **SSE** (`text/event-stream`), по одному соединению на вкладку
  (`EventSource`, авто-reconnect браузером). Keep-alive: при тишине дольше 20 с
  сервер шлёт кадр `ping`, чтобы прокси (Vite dev, nginx) не рвал поток.
- **Модель — грубозернистые события**: событие это `kind` + опциональные данные;
  фронт реагирует **refetch'ем** (перечитывает данные целиком), а не патчами.
  Трейлинг-debounce 300 мс коалесцирует бёрсты (закрытие задачи = 2–3 события).
  Виды подписываются на свои kind'ы: стек/список — `task.changed`,
  `task.deleted`, `detail.changed`, `project.changed`, `project.deleted`;
  статистика — `xp.changed`, `task.changed`; сад — `garden.changed`, `xp.changed`,
  `task.changed` (рефetch подавляется на время drag по сцене); страница задачи —
  перечитывается при `task.changed`/`detail.changed` своего `id`.
- **Точки публикации** (строго после коммита транзакции): HTTP-хендлеры задач,
  проектов, сада, XP-дейли и настроек; MCP-инструменты и фоновая детализация —
  broadcast (всем вкладкам). Список kind'ов: `task.changed {id}`,
  `task.deleted {id}`, `detail.changed {id}`, `project.changed {id}`,
  `project.deleted {id}`, `garden.changed {reason}`, `xp.changed {amount?,
  celebrate}`, `settings.changed`, `ready` (первый кадр), `ping`.
  Упрощение: закрытие задачи через MCP не шлёт `garden.changed` — растение
  создаётся лениво, refetch по `task.changed` покрывает.
- **Дедуп XP-тостов**: действие своей вкладки празднуется по заголовкам ответа
  (`X-Earned-XP`, ТЗ 3.13); SSE-эхо и эхо соседней вкладки молчат — окно
  активности 3 с (localStorage). Соседние вкладки показывают скромный тост
  «+N XP» без конфетти.
- **Reconnect**: при обрыве — backoff 1→2→5→15 с; 401 при проверке сессии —
  редирект на вход; после переподключения — форс-рефреш всех открытых видов
  (данные за время разрыва могли измениться).

### 3.15. PWA

Приложение устанавливается на устройство как приложение (Chrome/Android; вопрос
оффлайн-режима данных — открытый вопрос 8.4, пока приложение всегда онлайн).

- **Манифест**: standalone, тёмная палитра кита (`#16161e` — фон и theme-color,
  чтобы не мигало белым при запуске и не зеленела шапка окна), иконки 192/512 +
  maskable-512, `id`/`start_url` = `/`, язык ru.
- **Service Worker** (vite-plugin-pwa, `autoUpdate`): precache оболочки — собранные
  ассеты, шрифты/иконки кита (~6 МБ лимит), `/api/*`, `/auth/*`, `/mcp/*` никогда
  не кэшируются и не получают fallback на `index.html`.
- **Оффлайн-оболочка**: при отсутствии сети SW отдаёт оболочку приложения
  (SPA-fallback) — интерфейс открывается, данные при этом недоступны (запросы к
  API не кэшируются). Вложения и статические ассеты — CacheFirst.
- **Обновление**: новая версия подхватывается автоматически при следующем визите
  (`autoUpdate`); SSE-соединение при обновлении рвётся — reconnect покрывает (3.14).
- **Установка с телефона**: требует secure context — при доступе по LAN по HTTP
  (не localhost) браузер SW не зарегистрирует; нужен HTTPS (M7 Docker/nginx).
- **Dev-режим**: SW не включён (`devOptions` выключен) — PWA проверяется на
  прод-сборке (`dist`), не на dev-сервере.


## 4. Нефункциональные требования

| Требование | Значение |
|---|---|
| Архитектура | Клиент-серверное; SPA + REST API + MCP |
| Фронтенд | SPA на **Vue 3** с **gnexus-ui-kit** (`npm: gnexus-ui-kit@^0.4.0`, peer: Vue ^3.4) |
| Бэкенд | **Python** (FastAPI — предположительно), клиент SSO — `gnexus-gauth` |
| СУБД | PostgreSQL (рекомендация для VPS) |
| Авторизация | SSO через **auth.gnexus.space**, библиотека **gnexus-gauth** (`https://git.gnexus.space/git/root/gnexus-auth-client-py.git`) |
| Хостинг | VPS, HTTPS |
| AI | **Ollama** на сервере; модель задана конфигом |
| Упаковка | В итоге всё пакуется в **Docker** (docker-compose: API, PostgreSQL, фронт, Ollama + модель) |
| Мобильность | На старте — **только PWA**; Android-приложение — вне скоупа стартовой версии (вернуться к нему позже) |
| Приватность | Все данные и LLM-вызовы — на собственном сервере |
| Реактивность | SSE (`/api/events`): грубозернистые события + refetch, см. 3.14 |
| PWA | манифест + SW (precache оболочки, autoUpdate), оффлайн-оболочка без кэша API, см. 3.15 |

Внешние зависимости проекта (не форкать, обновлять через пакетный менеджер):

- `gnexus-ui-kit` — часто обновляется, использовать только публичный API кита;
- `gnexus-gauth` — клиентская библиотека SSO.

## 5. Модель данных (черновик)

```
Task
  id, title
  description      markdown — живёт в Document (owner_type = task)
  task_type         one_time | recurring          — тип задачи: разовая / регулярная
  status            to_do | in_progress | done | cancelled | deferred
  detail_state      raw | approved                — плоскость детализации (стека)
  parent_task_id    nullable  — дерево подзадач
  project_id        nullable
  tag_ids           []
  priority          nullable  (заполняется автодетализацией / вручную)
  deadline          nullable  — дедлайн:
                        strict  { date }                  — «до конкретной даты»
                        soft    { period: week|month|year } — «в течение периода»
  recurrence        nullable  — правило повторения (для task_type = recurring)
  time_estimate     nullable  — прогноз времени (система)
  budget            nullable  — ручной бюджет (деньги и/или время), опционален
  estimated_cost    nullable  — оценка затрат по задаче (сравнивается с бюджетом)
  actual_time       nullable  — фактические затраты (для обучения прогноза)
  created_at, approved_at, done_at

Project
  id, name
  relevance_status  active | paused               — статус актуальности
  is_archived       bool — архив (3.8.1): проект и его задачи скрыты
                    из рабочих видов, восстановимы из «Архива»
  priority
  note              markdown — живёт в Document (owner_type = project):
                    ссылки, контекст, картинки

Tag           — справочник тегов
Document      — markdown-текст с полиморфной привязкой к владельцу:
                owner_type (task | project), owner_id, body (у задачи —
                описание, у проекта — заметка); document всегда один на владельца
Attachment    — файлы документов (изображения), document_id, mime.
                URL скачивания файла неизменен: /api/attachments/{id}/file —
                он зашит в сохранённый markdown-текст

XpEvent       — начисление XP за закрытую задачу (идемпотентно по task_id):
                amount, rarity (common | rare | epic), via_options

CoinEvent     — журнал монет сада (заработок и траты; баланс = Σ amount):
                task_id (nullable), level (nullable — бонус уровня),
                source (task | level | shop), amount (+/−), item_key (ключ
                покупки для трат), created_at

GardenItem    — элемент сцены сада: kind (plant | decoration),
                ref_id (→ xp_events для растений; уникально в паре с kind),
                item_key (вид растения / ключ декорации), x, y (ячейки сетки),
                stage (0 росток, 1 куст, 2 цветение)
```

История завершённых задач (фактическое время) — источник для прогнозирования длительности.

## 6. Архитектура (черновик)

```
┌─────────────┐     ┌────────────────────── VPS (docker-compose) ─────────────┐
│  Web SPA    │────▶│  API (FastAPI) ──▶ PostgreSQL                           │
│  Vue 3 +    │     │   ▲    │                                                │
│  gnexus-    │     │   │    ├──▶ Сервис автодетализации ──▶ Ollama (модель    │
│  ui-kit     │     │   │    │      — LLM, модель из конфига)                  │
└─────────────┘     │   │    ├──▶ Хранилище вложений (файлы)                   │
      ▲             │   │    └──▶ MCP-сервер (для ИИ-агентов)                  │
      └─ SSE ───────┤ шина событий (realtime)                                  │
                    └─────────────────────────────────────────────────────────┘
                                     ▲ SSO auth.gnexus.space (клиент gnexus-gauth)
```

- API и MCP-сервер — одно Python-приложение (общая бизнес-логика).
- Вложения — файлы на диске VPS + метаданные в БД.
- Изменения данных публикуются в шину событий и уходят в SPA по SSE (3.14).

## 7. Этапы разработки (предложение)

| Этап | Содержание |
|---|---|
| M0 — каркас | Репозиторий, скелет backend + frontend, SSO-интеграция (gnexus-gauth), деплой на VPS |
| M1 — MVP задач | CRUD задач, быстрый ввод, стек входящих, теги, проекты (базово), ручная детализация |
| M2 — детализация | Интеграция Ollama + модель из конфига, автопредзаполнение метаданных, подтверждение/редактирование, Markdown-редактор, вложения из буфера |
| M3 — структура | Подзадачи (parent_task_id), классический список с фильтрами, заметки проектов |
| M4 — умность | Прогнозирование времени, бюджет, выдача задачи |
| M5 — MCP | MCP-сервер, инструменты для агентов |
| M6 — мобильность | PWA (Android-приложение — позже, вне стартового скоупа) |
| M7 — упаковка | Docker (docker-compose: API, PostgreSQL, фронт, Ollama + модель) |

Порядок M4/M5 может меняться — MCP можно поднять раньше ради ИИ-сценария.

## 8. Открытые вопросы

1. ~~**Правила повторения**~~ — **решено 2026-09-19**: три правила — интервал N дней, дни недели (ISO), день месяца (без дня — последний); якорь — фиксированный календарь от даты выполнения; новый экземпляр рождается при завершении предыдущего.
2. ~~**Прогнозирование времени**~~ — **решено 2026-09-19**: оценка длительности приходит из LLM-детализации (или задаётся вручную); точность не требуется — оценка нужна, чтобы отсекать заведомо большие задачи при малом доступном времени.
3. ~~**Оценка затрат к бюджету**~~ — **решено 2026-09-19**: оба поля (бюджет и оценка затрат) — ручные; валюта — глобальная настройка, выбирается один раз и применяется всюду (UAH, USD, EUR, GBP, PLN).
4. **Оффлайн-режим** в PWA: нужен ли, или всегда онлайн?
5. **Модель для Ollama**: какая конкретно модель и параметры (в конфиг, но нужно выбрать для проверки M2).
6. **Практическое различие `cancelled` / `deferred`**: определения зафиксированы (3.4), но поведение в интерфейсе почти одинаковое — скрыть из активных списков и оставить доступным для возврата. Уточнить при проектировании представлений, нужны ли оба статуса или их поведение сольётся.
7. ~~**Анти-дюп награды за статус**~~ — **решено 2026-09-21**: закрытие (HTTP и MCP) идёт через общий путь; XP — один раз на задачу (grant_task_xp), спавн регулярной — один раз на цепочку (метка `spawned_at`), выход из done сбрасывает `done_at`. Повторные закрытия и обновления закрытой не дублируют награды и экземпляры — покрыто тестами.
8. ~~**Пиксель-арт сцена сада**~~ — **решено 2026-09-20**: сцена переведена на пиксель-арт (PixiJS 8, WebGL); спрайты генерируются из пиксель-матриц в коде (без внешних ассетов и лицензий), см. 3.13.
9. **Premium-виды в семенах**: premium-виды (сейчас кактус) рождаются только из rare/epic закрытий — «отпечаток крупной работы». Семена же продаются за монеты и дают common-растение; сейчас семя premium-вида купить можно. Оставить ли покупку семян premium-видов (простой доступ к виду) или запретить (эксклюзив rare/epic сохраняется) — решить.
10. **Самосоединяющийся забор**: секции изгороди автоматически соединяются с соседними секциями (маска соседей N/E/S/W → текстура); ручного поворота нет. Соединяются только соседние секции забора — диагонали не считаются. Подтвердить, что поведение устраивает.