Skip to content

feat(card-attachments): ссылка на карточку task-tracker'а из панели#246

Merged
gently-whitesnow merged 2 commits into
masterfrom
feat/card-attachment-web-url
Jul 10, 2026
Merged

feat(card-attachments): ссылка на карточку task-tracker'а из панели#246
gently-whitesnow merged 2 commits into
masterfrom
feat/card-attachment-web-url

Conversation

@gently-whitesnow

Copy link
Copy Markdown
Owner

Цель

В панели прикреплённой карточки (CardAttachmentRow) сейчас нет способа открыть саму карточку в task-tracker'е — только текстовый title, координата и меню действий. Даём кликабельную ссылку, открывающуюся в новой вкладке.

Что меняется

Backend

  • ITaskTrackerConnectionProvider.BuildCardWebUrl(descriptor, cardId) — новый метод в generic core: провайдер решает, как строить URL, и возвращает null, если не умеет.
  • KaitenTaskTrackerProvider: короткая форма {baseUrl}/{cardId} с нормализацией trailing slash. Kaiten сам редиректит на человекочитаемый /space/{s}/boards/card/{prefix}-{n} — так мы не тянем board metadata (space_id, prefix) в CardSnapshot.
  • CardAttachmentDto.web_url: string | null в openapi + регенерирован backend/frontend codegen.
  • CardAttachmentDtoMapper перевёден с static на sealed class с DI (ITaskTrackerProviderRegistry, ITaskTrackerConnectionStore). ToDtoListAsync батчит чтение connections по distinct tracker, чтобы list-endpoint остался O(providers).
  • web_url — производное поле в момент маппинга, не персистится в CardSnapshot (ADR-0052 non-authoritative).
  • KaitenTaskTrackerFailureMap — выделен status→health / board-read failure маппинг из KaitenTaskTrackerProvider, чтобы файл провайдера остался в пределах maintainability budget (≤200 строк).

Frontend

  • CardAttachmentRow.tsx: title — <a target="_blank" rel="noopener noreferrer"> когда web_url != null, иначе как было (<span>).

Явно ВНЕ scope

  • Полный URL со space_id/prefix.
  • Персист URL в снапшоте.
  • Ссылки в других местах UI (board-card-browser и т.п.).
  • Провайдеры кроме Kaiten (для них web_url = null, UI покажет текст).

Как проверить

  • bash scripts/quality/verify.sh — все 19 гейтов PASS локально.
  • Kaiten: приложить карточку → в панели title кликабельный, ведёт на {baseUrl}/{cardId} (короткая форма → Kaiten редиректит на человекочитаемую).
  • Удалить коннект и обновить страницу → web_url = null, title рендерится текстом без ссылки.
  • Provider unit: KaitenTaskTrackerProviderCardTests.BuildCardWebUrl_* — 7 кейсов (trailing slash, пустой baseUrl/cardId, non-numeric id).
  • Api integration: IntentCardAttachmentsControllerTests.Attach_happy_path проверяет web_url в отдаваемом DTO; List_web_url_null_when_connection_missing — деградация до null без коннекта.
  • Web: CardAttachmentsList.test.tsx — рендерит title как <a> при web_url, как <span> при null.

liapota added 2 commits July 10, 2026 15:34
…рикреплённой карточки

Панель прикреплённой карточки теперь показывает title как кликабельную
ссылку в task-tracker'е — открывается в новой вкладке.

Решения:
- URL — производное поле в CardAttachmentDtoMapper, не персистится в
  CardSnapshot (ADR-0052 non-authoritative).
- Построение URL — задача провайдера: новый метод BuildCardWebUrl на
  ITaskTrackerConnectionProvider, generic core вызывает и получает string?.
- Kaiten: короткая форма `{baseUrl}/{cardId}` (trailing slash нормализуется);
  Kaiten сам редиректит на человекочитаемый URL с prefix/space.
- Батчинг: ToDtoListAsync читает connection один раз на distinct tracker,
  чтобы list-endpoint остался O(providers), а не O(attachments).

Отброшено:
- Полный URL `/space/{s}/boards/card/{prefix}-{n}` — потребовал бы board
  metadata (space_id, prefix), которых нет в CardSnapshot и KaitenCard, и
  ломал бы провайдер-нейтральность.
- Хранить web_url в снапшоте — противоречит ADR-0052 (write-through нет,
  URL меняется вместе с baseUrl коннекта).

Ограничения:
- Только Kaiten; для других провайдеров BuildCardWebUrl вернёт null и UI
  покажет title без ссылки.
- Ссылка добавлена только в CardAttachmentRow; другие места UI
  (board-card-browser) — вне scope.
Решения:
- Расширили TaskTrackerCardDto полем web_url (nullable) — тот же
  контракт, что и у CardAttachmentDto: провайдер-нейтральное поле,
  которое собирает конкретный tracker-провайдер из base_url
  сохранённого коннекта. Non-authoritative, никогда не персистится
  вместе с проекцией (ADR-0052).
- TaskTrackerCardDtoMapper переведён из static в DI'd sealed class
  по образцу CardAttachmentDtoMapper: async, один lookup коннекта на
  весь response (карточки принадлежат одному tracker), формирует
  web_url через ITaskTrackerConnectionProvider.BuildCardWebUrl.
- BoardCardDetail: title карточки оборачивается в
  <a target="_blank" rel="noopener noreferrer">, когда web_url задан;
  список остаётся кликабельной кнопкой без вложенной ссылки, чтобы
  не ломать выбор карточки.

Отброшено:
- Возвращать web_url из BoardCardBrowserService — это утечка wire-
  представления в application-слой; кэш коннекта в mapper решает то
  же на уровне API.
- Добавить ссылку в BoardCardRow (список): row — это <button> для
  выбора; nested <a> внутри интерактивной строки конфликтует с
  клавиатурной навигацией и жестами.

Ограничения:
- Формат URL — короткий (base_url/card_id). Kaiten редиректит на
  человекочитаемый /space/{s}/boards/card/{prefix}-{n}, но prefix
  нам недоступен без обхода OpenAPI Kaiten.
@gently-whitesnow
gently-whitesnow merged commit 4d14bf6 into master Jul 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants