opencode-config/docs/decisions/089-pr-201-restore-cover-subicon-slot.md
Sergey 85d7d62fcd
fix(cover): restore sub-icon slot in cover.svg (#201)
* fix(cover): restore sub-icon slot in cover.svg

* docs(cover): add ADR-089 and handoff for PR #201

---------

Co-authored-by: opencode-agent <agent@opencode.local>
2026-08-01 02:56:40 +03:00

5.6 KiB
Raw Permalink Blame History

ADR-089: Restore sub-icon slot in production cover.svg (PR#201)

Статус

Accepted (2026-07-31)

Контекст

PR#197 (ADR-087) упростил продакшн-шаблон templates/cover.svg до единственного icon-слота 520×520 + {{title}} y=830, удалив slot-комментарии sub-icon, badge и subtitle-<text>. Это было демонстрацией отвязки тестов от продакшн-шаблона (тесты переехали на tests/fixtures/cover.svg). Удалённые слоты в продакшн-рендере тогда не использовались, и assets/cover.png остался побайтово идентичным.

Однако движок buildSvg итерирует по слотам, объявлённым в шаблоне (slot-parser читает HTML-комментарии из шаблона), а не по args.slots, переданным в вызов. Поэтому после PR#197 переданный sub-icon=... молча отбрасывался — слот не объявлен в шаблоне → рендерер его не видит.

Команда /cover (PR#199, ADR-088) документирует sub-icon как optional-слот и передаёт его в draw-image (пример: sub-icon=opencode). Возникла рассогласованность: команда обещает sub-icon, рендерер его игнорирует. Сценарий «icon + sub-icon + title» (например, иконка фичи + логотип OpenCode) не работал.

Условный рендер слотов (PR#144, ADR-061) уже поддерживает опциональность: передан sub-icon → рендерится; не передан → слот пропускается. Это доказано fixture-тестами (render.optional.test.ts на tests/fixtures/cover.svg, где sub-icon присутствует). Движок поддерживал sub-icon всегда — не хватало только объявления слота в продакшн-шаблоне.

Решение

  1. Восстановлена ровно одна строка slot-комментария sub-icon в templates/cover.svg: <!-- slot: name=sub-icon, x=412, y=600, w=200, h=200, fit=contain, recolor=accent --> (та самая, что удалил PR#197). Изменений в коде движка (src/render.ts) и команде /cover нет.
  2. Только sub-iconbadge и subtitle остаются удалёнными из продакшн-шаблона (вне scope PR#201). /cover (ADR-088) запрещает subtitle стандартом; badge не используется в Lime-стандарте.
  3. Условный рендер (PR#144) сохраняет одно-иконочный кейс побайтово идентичным: assets/cover.png (icon=opencode, без sub-icon) → 18077 bytes, cmp → IDENTICAL. Рендер с двумя иконками (icon=mic,sub-icon=opencode) → 23171 bytes (больше, как ожидалось).
  4. Тесты не затронуты: 58/58 проходят — тесты используют tests/fixtures/cover.svg (PR#197), а не продакшн-шаблон, поэтому изменение продакшн-шаблона их не ломает.

Альтернативы

  • Не восстанавливать слот, убрать sub-icon из /cover (ADR-088): отвергнуто — sub-icon — полезный сценарий (иконка фичи + логотип проекта), и ADR-088 уже документирует его как optional. Убрать из команды = потерять функциональность, которую движок уже поддерживает.
  • Восстановить также badge и subtitle: отвергнуто — вне scope issue #200. Lime-стандарт (ADR-088) запрещает subtitle; badge не используется. Восстановление мёртвых слотов вернёт проблему, которую решал PR#197.
  • Изменить buildSvg чтобы итерировать по args.slots, а не по шаблонным слотам: отвергнуто — крупное архитектурное изменение движка ради одной строки. Текущая модель (шаблон объявляет слоты, рендерер их заполняет) чище и детерминированнее: шаблон = контракт на layout, args = данные.

Последствия

  • Частичный реверс ADR-087 (PR#197): продакшн templates/cover.svg снова объявляет sub-icon (наряду с icon). badge и subtitle остаются удалёнными — реверс точечный.
  • Сценарий «icon + sub-icon» теперь работает в продакшн-рендере; одно-иконочный кейс побайтово идентичен (условный рендер PR#144).
  • Тест-контракт (tests/fixtures/cover.svg) не изменён — продакшн и fixture снова расходятся по набору слотов (fixture имеет все 4 типа, продакшн — 2), но это намеренно: fixture тестирует движок, продакшн — конкретный бренд-стандарт.