current_tasks
Current Tasks
[DONE] Фиксы после merge feat/webhooks
Контекст
Code review merge feat/webhooks выявил критические баги, HIGH проблемы и мелочи. Исправлено перед продакшн-деплоем.
План
CRITICAL
-
skip_webhooksфлаг не работает — добавленSkipWebhooks boolвappreq.Request, заполняется вcheckapikey.resolveAPIKey, проверяется вHandleLatestNotesAfterSave.WebhookDepthпередаётся вместо hardcoded 0. - Removed notes не доставляются — добавлен
Path stringвNoteChange, remove events обрабатываются когда noteView nil. Webhook trigger интегрирован в HideNotes resolver (schema.resolvers.go).
HIGH
- Agent writes обходят write_patterns — создан
webhookutil/patterns.goсParseJSONStringArrayиMatchesAny. Валидация path против writePatterns перед InsertNote в обоих deliver*webhook. - Delivery "success" при ошибке agent changes — при applyErr без retries теперь mark as "failed" + return, без fallthrough к "success".
- Валидация: max_depth ≤ 999, timeout ≥ 1s, max_retries ≤ 100 — добавлена во все 4 CRUD resolver (create/update webhook/cronwebhook).
MEDIUM
- HTTP >= 300 = ошибка — порог изменён с >= 500 на >= 300 в обоих deliver*webhook.
- Debug endpoint: JSON injection fix —
json.Marshal(string(body))вместо строковой конкатенации. - SQL:
updated_atтриггеры — миграция20260211120000_add_updated_at_triggers_for_webhooks.sql.
Cleanup
-
Удалить
turbo.js— файл удалён, embed directive и закомментированная строка убраны. -
Partial changes: обернуть в транзакцию— требует рефакторинг InsertNote/Env interface, отложить -
GraphQL:— уже есть (createdAtв AdminCronWebhookschema.graphqls:711) -
Frontend: readPatterns/writePatterns в формах (отдельная сессия)
Заметки
- SSRF — не фиксим, localhost обращения к соседним контейнерам — by design
- Missing indexes — не нужны пока, мало данных
- CHECK constraints — не критично
parseJSONStringArrayдублируется в 3 пакетах — вынести вwebhookutilпри фиксе write_patterns (нуженmatchesAnyтоже)createdAtвAdminCronWebhook— проверено, уже есть в schema.graphqls:711- shortapitoken
SkipWebhooks: false(checkapikey/resolve.go:116) — правильно, shortapitoken использует depth вместо skip
[DONE] Рефакторинг конфига — Фаза 1
Контекст
Переход от монолитной таблицы config_versions к атомарным таблицам настроек. Фаза 1: добавляем site_title_template + инфраструктуру для новых конфигов.
Подробный план: docs/config_refactoring.md
План
- Миграция:
create table config_site_title_templates - sqlc queries
- GraphQL: interface
AdminConfigValue, типыAdminConfigStringValue,AdminConfigBoolValue - GraphQL: query
configValues,configValue(id) - GraphQL: mutation
setConfigStringValue,setConfigBoolValue - Registry конфигов:
internal/configregistry/ - Resolver:
internal/case/admin/setconfigstringvalue/ - Env method:
SiteTitleTemplate() string - rendernotepage:
formatTitle() - Frontend: новая страница
/admin/config - Тесты (backend)
Заметки
- Таблица
config_versionsсохранена для обратной совместимости - Все 5 конфигов мигрированы в атомарные таблицы
LatestConfig()оставлен без изменений- Фронтенд
/admin/configполностью работает со всеми конфигами
[DONE] Внедрить site_title_template в рендер + Удалить старый config UI
Контекст
Настройка site_title_template теперь используется при рендере страниц. Также удалён старый UI и GraphQL код для config_versions.
План
- Найти где формируется
<title>в rendernotepage - SiteTitleTemplate() метод уже существует в Env
- formatTitle() уже использует шаблон (resolve.go:156)
- Исправить стандартный layout: использовать resp.Title вместо resp.Note.Title
- Добавить title в переменные кастомного layout (endpoint.go:164)
- Удалить старый admin UI: assets/ui/admin/configversion/
- Удалить старый GraphQL: AdminConfigVersion, createConfigVersion, allConfigVersions, latestConfig
- Удалить resolvers и case/admin/createconfigversion/
- Написать тесты для title template
- Запустить make test && make lint
Заметки
- Стандартный и кастомный layout теперь оба используют отформатированный title
- В кастомных layout доступна переменная
{{ title }}с отформатированным заголовком - Старая система config_versions полностью удалена
[IN PROGRESS] Фронтенд: SSE подписка currentTime
Контекст
SSE подписки работают через fasthttp + fasthttpadaptor + gqlgen. Демо подписка currentTime отдаёт время каждую секунду. Нужно реализовать клиентскую часть, чтобы убедиться что весь pipeline работает end-to-end и можно использовать подписки для реальных задач.
В будущем подписки будут использоваться для: статусы задач, синхронизация файлов (страница обновляется при правках без кнопки обновить), live preview в редакторе.
План
- SSE клиент (
$trip2g_sse_host) — fetch + ReadableStream, SSE парсер, автореконнект - Статус-виджет (
$trip2g_sse_status+$trip2g_sse_icon) — по паттерну yuf/ws/status -
$trip2g_graphql_raw_subscription— реализация вместо stub - Компонент
$trip2g_time_current— показывает время + статус иконку - Codegen fix: запятая в subscription overloads, return type →
$trip2g_sse_host - Тест SSE подписки (sse.test.ts)
- Документация: TypeScript codegen секция в docs/graphql.md
- Проверить в браузере: подключение, получение events, автореконнект
- Удалить демо после проверки (или оставить как dev tool)
Заметки
- SSE клиент написан с нуля (fetch + ReadableStream), без graphql-sse зависимости
- graphqlmol.js: исправлен баг — отсутствие запятой между query и variables в subscription overloads
- graphqlmol.js: subscription overloads теперь возвращают
$trip2g_sse_hostвместо data type $mol_wait_timeout is not a function— предсуществующий баг, не связан с SSE- Бэкенд:
schema.graphqlstype Subscription +schema.resolvers.goCurrentTime resolver - Endpoint:
POST /graphqlсAccept: text/event-stream - Документация: docs/graphql.md, docs/gqlgen_fasthttp.md
- Data race fix:
fasthttpadaptor.NewFastHTTPHandlerиспользует sync.Pool, writer возвращается в пул при ошибке записи, а SSE горутина ещё пишет → race. Решение:internal/fastgql.NewSSEHandler— обёртка без sync.Pool, с mutex и отменой контекста при disconnect
[TODO] Добавить конфиг EnableNotFoundTracking
Контекст
Сейчас система трекает все 404 ошибки (таблицы not_found_paths, not_found_ip_hits), что создаёт лишнюю нагрузку на БД. Нужно добавить булевый конфиг для включения/выключения этого трекинга. По умолчанию — выключен.
План
- Добавить
EnableNotFoundTracking(bool, default false) вconfigregistry - Добавить поле в
model.SiteConfig - Загружать в
app.SiteConfig() - Обернуть трекинг 404 в проверку конфига
- Добавить в admin UI конфигов
[DONE] RSS
Контекст
Любая заметка — RSS лента. Markdown AST преобразуется в RSS feed: каждая ссылка → RSS item. Дизайн: docs/rss.md
План
- Markdown AST → RSS: извлечение ссылок из заметки
- Роутинг:
/path/to/note.rss.xml - Метаданные из целевых заметок (description, created_at)
- Frontmatter:
rss_title,rss_description - Конфиг:
EnableRSS(bool) в SiteConfig - Package
internal/rssfeed/с генератором - Middleware
handleRSSFeedвcmd/server/main.go - Тесты
Заметки
- Поддерживаются wikilinks и markdown links
- Internal links обогащаются метаданными из целевых заметок
- AST walking пропускает embedded images и media files
created_at/created_onиз фронтматтера переопределяет значение из БД- E2E тесты:
e2e/rss.spec.js
[DONE] Sitemap.xml
Контекст
Генерация sitemap.xml из всех опубликованных страниц. Генерируется вместе с заметками в NoteViews и отдаётся по запросу. Включаются только free: true заметки. Документация: docs/sitemap.md
План
- Добавить поле
Sitemap []byteвNoteViews - Генерировать sitemap при загрузке заметок в
mdloader - Middleware
handleSitemapдля отдачи по/sitemap.xml - Конфиг
EnableSitemap(bool, default true) - Тесты
- make test && make lint
Заметки
- Реализация завершена в
internal/sitemap/ - Генерируется при загрузке заметок в
noteloader - Включаются только
free: trueзаметки - Системные страницы
/_*исключаются lastmodберётся изCreatedAtили frontmattercreated_at/created_on
[DONE] Рефакторинг: ShowDraftVersions → LiveNoteViews()
Контекст
Логика ShowDraftVersions дублировалась в rendernotepage/resolve.go. Перенесена в app.LiveNoteViews().
План
- Перенести логику ShowDraftVersions в
app.LiveNoteViews() - Убрать дополнительную логику из
rendernotepage/resolve.go - Проверить все вызовы LiveNoteViews() — нигде не сломается
- make test && make lint
Заметки
LiveNoteViews()теперь возвращает latest когда ShowDraftVersions включён- RSS, sitemap и все middleware автоматически используют правильные заметки
- Админы по-прежнему могут переключаться через
?version=
[IN PROGRESS] Редактор файлов
Контекст
Веб-редактор markdown файлов. Модалка на весь экран, доступна на любой фронт-странице через кнопку в хедере. Milkdown WYSIWYG редактор. Пока прикидываем интерфейс.
Подробный дизайн: docs/editor.md
План
Интерфейс (текущий этап)
- Модалка на
<dialog>с состоянием в$mol_state_arg - Кнопка открытия в
$trip2g_user_space - Toolbar: заголовок, тогглы Files/Preview, кнопка закрытия
- 3 колонки: navigator | editor | preview
- Скрытие navigator и preview через тогглы
- Milkdown бандл собран (esbuild IIFE,
assets/milkdown/) - Async загрузка через
$mol_import.script -
embed.goиCaddyfileобновлены - Исправить закрытие модалки (raw CSS
display:noneдляdialog:not([open])) - Добавить русские переводы (locale файлы)
- Milkdown рендерится в content (
$mol_wire_syncдля async create) - Переход на Crepe (toolbar, block edit, link tooltip, theme)
- Починить загрузку CSS Crepe — браузер пытается загрузить
prosemirror.cssи др. через mol paths
Файловый навигатор (следующий этап) ← текущий
- GraphQL query для списка файлов
- Дерево файлов в navigator
- Выбор файла → загрузка в редактор
Загрузка и сохранение
- Загрузка содержимого файла (notePaths GraphQL)
- Сохранение изменений (pushNotes GraphQL)
- Индикация несохраненных изменений
- Ctrl+S для сохранения
Превью
- Live preview рендеринг (renderNotePreview GraphQL)
- Синхронизация скролла
Дополнительно
- Автоопределение текущей страницы (meta tag trip2g:path)
- История версий файла
- Wikilinks автодополнение
Заметки
- Tiptap бандл: ~990KB unminified (prosemirror внутри, без Vue.js)
- Загрузка async через
$mol_import.script('/assets/tiptap/tiptap.js') - Компоненты:
editor/navigator/,editor/content/,editor/preview/,editor/pane/ - Raw CSS нужен для
dialog:not([open])— mol$mol_style_defineне поддерживает attribute selectors $mol_wire_syncдля интеграции async tiptap create() в mol реактивную систему- Wiki-links: markdown-it плагин в tiptap бандле
- CSS инлайнятся в JS через esbuild plugin (inline-css)
- Включены: StarterKit, TaskList, Link, Placeholder, slash menu, wiki-links
- Tiptap: заменил Milkdown. Бандл 990KB vs 5MB. Тот же интерфейс (create/destroy/getMarkdown/setMarkdown/onChange). Slash menu, task lists, wiki-links. Подключён в UI через
content.view.ts
[DONE] Change Webhooks для изменений заметок
Контекст
Вебхуки уведомляют внешние сервисы (агенты, автоматизации) об изменениях заметок (create/update/remove). Агент получает POST с батчем изменений и может отреагировать — запустить линтер, пересобрать индекс, вызвать AI-агента. Агент может вернуть изменения в ответе (agent response).
Подробный дизайн: docs/change_webhooks.md
План
Этап 1: Ядро (MVP)
- Миграция: таблицы
change_webhooks+change_webhook_deliveries+cron_webhooks+cron_webhook_deliveries+ alterapi_keys - SQL-запросы (sqlc) +
make sqlc -
internal/shortapitoken/— JWT sign/parse с depth в claims -
internal/webhookutil/— HMAC, HTTP client, payload, secret generation - Admin mutations: createWebhook/updateWebhook/deleteWebhook/regenerateSecret
-
internal/case/handlenotewebhooks/— depth check, glob-матчинг (doublestar), enqueue -
internal/case/backjob/deliverchangewebhook/— HTTP POST + HMAC подпись + shortapitoken + парсинг agent response - Расширить
checkapikey— поддержкаAuthorization: Bearerдля shortapitoken - Интеграция в
HandleLatestNotesAfterSave(create/update) - Admin queries: webhooks, webhookDeliveries
- Debug endpoints (
DEV_MODE=true):/debug/test_webhook,/debug/test_webhook_calls,/debug/wait_all_jobs,/debug/run_cron_job - Wiring в main.go: jobs, env methods, cron jobs
- Cleanup cron jobs: delivery logs (7d), deliveries (30d)
Этап 2: Admin UI
- Фронтенд: CRUD вебхуков в админке (catalog/show/create/update)
- Фронтенд: просмотр истории доставок (встроено в show page)
Заметки
- Батчинг: все совпавшие заметки → один POST на вебхук
- Include/exclude паттерны — JSON arrays, матчинг через doublestar.Match
- HMAC-SHA256 подпись всегда (secret автогенерируется при создании)
instruction— текстовая инструкция для агента (один endpoint, разные задачи)include_content(default true) — содержимое заметок в payload (remove → null)- shortapitoken (JWT, 30 мин TTL) — depth+1, read+write доступ к API
- Depth-based recursion protection: depth в JWT + max_depth в webhook + skip_webhooks в api_keys
- Agent response: опциональный
changes[]сexpected_hashдля optimistic concurrency - X-Webhook-Chain-ID: генерируется при depth=0, передаётся в headers всех вызовов в цепочке, записывается в delivery log для трейсинга полной цепочки обработки
[TODO] Рефакторинг: стандартный шаблон через templateviews.Note
Контекст
Стандартный шаблон (view.html + view.html.go) обращается к resp.Note напрямую вместо templateviews.Note. Из-за этого domain-aware логика (выбор DomainHTML[""]) дублируется в двух местах: Response.NoteHTML() и templateviews.Note.HTMLString() — что уже привело к багу (пропустили фикс в HTMLString()).
Нужно переделать стандартный шаблон так, чтобы он использовал templateviews.Note (или аналогичный view-объект) как единую точку доступа к HTML заметки. Заодно убрать накопившееся легаси в view.html.
План
- Проанализировать текущее использование
resp.Noteвview.html/view.html.go - Определить что нужно от view-объекта (HTML, Title, Permalink, метаданные)
- Перейти на
templateviews.Note(или расширить его) в стандартном шаблоне - Удалить
Response.NoteHTML()/Response.SidebarHTML()— логика остаётся только вtemplateviews - Почистить легаси в
view.html - make test && make lint
Заметки
- Баг-сигнал:
NoteHTML()вresolve.goиHTMLString()вtemplateviews/note.goимели одинаковый баг (domainHost != "") — признак дублирования логики - Пока не делаем: запланировано вместе с общим рефакторингом стандартного шаблона
[TODO] Рефакторинг: vectorMinSimilarity в конфиг
Контекст
Порог cosine similarity для векторного поиска захардкожен в internal/case/sitesearch/resolve.go как vectorMinSimilarity = 0.75. Пользователь должен сам подбирать порог под свою базу заметок.
План
- Добавить
VectorSearchMinSimilarity float64вfeatures.VectorSearchConfig(internal/features/vector_search.go) - Использовать из
sitesearch/resolve.goвместо константы (default 0.75 если не задан) - Обновить документацию в
docs/vector_search.mdи.env.example
[TODO] Рефакторинг: обработка ошибок doublestar.Match в templateviews
Контекст
В internal/templateviews/query.go:82 ошибка doublestar.Match игнорируется (match, _ := ...). Нужно обрабатывать ошибку и pre-compile паттерны, чтобы невалидный glob обнаруживался раньше.
План
- Обработать ошибку
doublestar.Matchвquery.go:82 - Pre-compile glob паттерны (валидация при создании NoteQuery)
- make test && make lint
[DONE] Cron Webhooks — вызов агентов по расписанию
Контекст
Cron webhooks вызывают внешние агенты по расписанию (cron expression). Агент получает инструкцию + shortapitoken и может вернуть изменения (новые/обновлённые заметки) синхронно в ответе или async через API.
Подробный дизайн: docs/cron_webhooks.md
План
- Миграция: таблицы
cron_webhooks+cron_webhook_deliveries - SQL-запросы (sqlc) +
make sqlc - Admin mutations: create/update/delete cron webhook + regenerateSecret
- Cron job registration в
cmd/server/cronjobs.go(executecronwebhooks) - Background job: HTTP POST + парсинг ответа + импорт changes (delivercronwebhook)
- Admin queries + фронтенд CRUD (catalog/show/create/update)
- Delivery history view (встроено в show page)
Заметки
- Общая инфраструктура с change_webhooks: shortapitoken, HMAC, delivery log
- Агент может отвечать sync (changes в body) или async (202 + работа через API)
- response_schema передаётся агенту как документация, не валидируется сервером
- timeout настраиваемый (default 60s)
[TODO] Рефакторинг: expected_hash в pushNotes API
Контекст
Optimistic concurrency control для заметок. При pushNotes можно передать expected_hash — если note_paths.latest_content_hash не совпадает, сервер отклоняет изменение. Защита от перезаписи чужих правок агентами.
План
- Добавить
expectedHashопциональное поле в PushNotesInput (GraphQL) - Проверка в InsertNote: если expected_hash задан и не совпадает → ошибка
- Добавить expected_hash в shortapitoken flow
- Тесты
- make test && make lint
Заметки
- Аналог If-Match / ETag в HTTP, CAS в concurrent programming
- Используется в ответах change_webhooks и cron_webhooks (agent response)
- Для новых файлов expected_hash пустой
[TODO] updateNote мутация (find/replace)
Контекст
Атомарная операция find/replace для обновления части заметки без полной перезаписи. Фундамент для агентов (inbox, task toggle, AI-правки). Автокоммит, без отдельного commitNotes.
Подробный дизайн: docs/update_note_mutation.md
План
- SQL-запрос
LatestNoteContentByPath+make sqlc -
internal/case/updatenote/— resolve + тесты - GraphQL schema:
updateNotemutation - Resolver в
schema.resolvers.go - Расширить
AgentChangestruct (find/replace поля) -
applychanges.go— добавить find/replace mode - Тесты agent response с find/replace
[TODO] Webhook agent response: поддержка assets
Контекст
Сейчас в webhook agent response можно вернуть только changes: [{ path, content }]. Но агент может также хотеть прикрепить ассеты к заметке (картинки, файлы). Нужно добавить поддержку массива assets в каждом change.
Аналог GraphQL mutation uploadNoteAsset — агент передаёт URL для скачивания файла, бекенд скачивает и сохраняет как ассет заметки.
План
- Расширить
AgentChangestruct: добавитьAssets []AgentAsset -
AgentAssetstruct:RelativePath string,URL string(URL для скачивания) - Валидация
relativePath(без../, только внутри папки заметки) - В
applyAgentChanges: после InsertNote → скачать и загрузить каждый ассет - Использовать существующую логику
uploadNoteAsset(скачивание, хеш, MinIO) - Проверка
writePatternsдля путей ассетов - Тесты: agent response с assets
- E2E тест: webhook возвращает change с картинкой
- Документация: обновить docs/shared_webhooks.md
Заметки
- Ассеты скачиваются с внешних URL (агент может сгенерировать картинку и залить на свой сервер)
relativePathотносительный от заметки (напримерimages/diagram.pngдляblog/post.md)- Бекенд должен проверить
writePatternsдля полного пути ассета (blog/images/diagram.png) - MinIO upload + SHA256 hash +
note_version_assetsтаблица (уже есть)
[TODO] Agents: subprocess agent + telegram inbox agent
Контекст
Внешние агенты, подключаемые к trip2g через вебхуки. Два примера: subprocess agent (обёртка для CLI типа claude -p) и telegram inbox agent (TG → заметка-inbox).
Дизайн: docs/agents.md, docs/subprocess_agent.md, docs/telegram_inbox_agent.md
План
Структура проекта
-
agents/subprocess/main.go— entry point -
agents/subprocess/Dockerfile -
agents/tginbox/main.go— entry point -
agents/tginbox/Dockerfile -
docker-compose.yml— локальный запуск trip2g + агентов
Subprocess agent (subot)
- HTTP handler для webhook (HMAC verify)
- Запуск subprocess (
-cmdфлаг) - Формирование промпта из instruction + changes
- MCP bridge (проксирование в trip2g API)
- Token store (
/tmp/subot_{id})
Telegram inbox agent
- TG bot long polling
- Message buffer
- Webhook handler (cron trigger → flush buffer → changes response)
- TG message → markdown форматирование
- Token store
Docker Compose
- trip2g сервер
- subprocess agent (с
claude -pили mock) - tginbox agent
- Общая сеть, ENV конфигурация
[TODO] Рефакторинг: pushNotes → единый API для записи заметок
Контекст
Текущий pushNotes требует отдельного commitNotes, не поддерживает find/replace, и дублирует часть логики с updateNote. Нужно унифицировать API записи заметок.
План
- Проанализировать текущее использование pushNotes + commitNotes
- Определить единый API (поддержка full replace и find/replace)
- Рефакторинг с обратной совместимостью
- Тесты
[TODO] UTM-метки для ссылок из заметок
Контекст
Заметка — источник трафика (пост в TG канале, рассылка, лендинг). Нужна возможность указать UTM-метки у заметки, чтобы все исходящие ссылки автоматически получали UTM-параметры. Это позволяет видеть в аналитике, из какого конкретно поста/канала пришёл клик.
План
- Frontmatter поля:
utm_source,utm_medium,utm_campaign,utm_content,utm_term - При рендере заметки — ко всем внешним ссылкам добавлять UTM-параметры
- TG посты: автоматически проставлять
utm_source=telegram,utm_medium=post - Wikilinks (внутренние) — не трогать, только внешние ссылки
- Конфиг или шаблон дефолтных UTM для TG канала
[TODO] Telegram dialogs: лимит + поиск + кеш (FLOOD_WAIT)
Контекст
При запросе списка диалогов через GraphQL (tg accounts → прикрепить заметки) Telegram возвращает FLOOD_WAIT (29). Причина: бекенд пытается выгрузить ВСЕ диалоги аккаунта (множество RPC-вызовов с пагинацией). Нужно: лимит 100 диалогов по умолчанию + поиск по query (тоже max 100) + кеш.
Ошибка: failed to list dialogs: callback: failed to get dialogs: rpcDoRequest: rpc error code 420: FLOOD_WAIT (29)
Дизайн: docs/cache.md
План
Лимит и поиск (корневая причина)
- Найти где выгружаются диалоги (бекенд, Telegram RPC)
- Ограничить выгрузку до 100 диалогов (без query — первые 100)
- Добавить параметр
queryв GraphQL — поиск по имени, тоже max 100 - Фронтенд: поле поиска в UI выбора диалога
Кеш (опционально, после анализа)
- Проверить нужен ли кеш после лимита 100 — возможно одного RPC-вызова хватит
- Если нужен: jellydator/ttlcache/v3, TTL 30 сек, ключ accountID+query
Проверка
- make test && make lint
Заметки
- Корневая причина: выгрузка ВСЕХ диалогов = много RPC = FLOOD_WAIT
- Telegram API:
messages.GetDialogsподдерживает limit + offset_peer - Telegram API:
contacts.Search— поиск по query - Кеш — вторая линия защиты, но без лимита одного кеша недостаточно
- Либа:
jellydator/ttlcache/v3— generics, auto-cleanup