From 85d7d62fcd0db92bb930fc4573b38d29e80d5923 Mon Sep 17 00:00:00 2001 From: Sergey <93754860+slaid098@users.noreply.github.com> Date: Sat, 1 Aug 2026 02:56:40 +0300 Subject: [PATCH] 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 --- .opencode/draw-image/templates/cover.svg | 1 + .../089-pr-201-restore-cover-subicon-slot.md | 31 +++++++++++++++++++ .../pr-201-restore-cover-subicon-slot.md | 22 +++++++++++++ docs/project-map/README.md | 2 +- 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 docs/decisions/089-pr-201-restore-cover-subicon-slot.md create mode 100644 docs/handoff/pr-201-restore-cover-subicon-slot.md diff --git a/.opencode/draw-image/templates/cover.svg b/.opencode/draw-image/templates/cover.svg index 9eff6dc..ade20dd 100644 --- a/.opencode/draw-image/templates/cover.svg +++ b/.opencode/draw-image/templates/cover.svg @@ -1,5 +1,6 @@ + {{title}} \ No newline at end of file diff --git a/docs/decisions/089-pr-201-restore-cover-subicon-slot.md b/docs/decisions/089-pr-201-restore-cover-subicon-slot.md new file mode 100644 index 0000000..e191f3b --- /dev/null +++ b/docs/decisions/089-pr-201-restore-cover-subicon-slot.md @@ -0,0 +1,31 @@ +# 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-``. Это было демонстрацией отвязки тестов от продакшн-шаблона (тесты переехали на `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`: + `` + (та самая, что удалил PR#197). Изменений в коде движка (`src/render.ts`) и команде `/cover` нет. +2. **Только `sub-icon`** — `badge` и `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 тестирует движок, продакшн — конкретный бренд-стандарт. diff --git a/docs/handoff/pr-201-restore-cover-subicon-slot.md b/docs/handoff/pr-201-restore-cover-subicon-slot.md new file mode 100644 index 0000000..f4cb4ed --- /dev/null +++ b/docs/handoff/pr-201-restore-cover-subicon-slot.md @@ -0,0 +1,22 @@ +--- +pr: 201 +title: "fix(cover): restore sub-icon slot in cover.svg" +--- + +## Что сделано +Восстановлена ровно одна строка slot-комментария `sub-icon` в `.opencode/draw-image/templates/cover.svg` (та самая, что удалил PR#197): +`` +Изменений в коде движка (`src/render.ts`) и команде `/cover` нет — ситуативность (1 или 2 иконки) обеспечивается условным рендером слотов (PR#144). Структурных изменений нет (одна строка в существующем файле). + +Проверено: 58/58 тестов проходят (тесты на `tests/fixtures/cover.svg`, не на продакшн-шаблоне); `assets/cover.png` (`icon=opencode`, без `sub-icon`) побайтово идентичен (18077 bytes, `cmp` → IDENTICAL); рендер с двумя иконками → 23171 bytes (больше, как ожидалось). + +## Почему +PR#197 удалил slot-комментарий `sub-icon` из продакшн-шаблона, из-за чего переданный `sub-icon` молча отбрасывался — `buildSvg` итерирует по слотам, объявлённым в шаблоне, а не по `args.slots`. Команда `/cover` (PR#199) документирует `sub-icon` как optional и передаёт его в `draw-image` — возникала рассогласованность: команда обещает `sub-icon`, рендерер его игнорирует. Восстановление слота делает сценарий «icon + sub-icon + title» рабочим, а условный рендер (PR#144) сохраняет одно-иконочный кейс побайтово идентичным. Частичный реверс ADR-087 (только `sub-icon`; `badge`/`subtitle` остаются удалёнными — вне scope). + +## Pending +— (после merge: memory-syncer может зафиксировать, что продакшн cover.svg снова объявляет sub-icon — project-map уже обновлён в этом PR) + +## Watch out +- **Только `sub-icon` восстановлен** — `badge` и `subtitle` остаются удалёнными из продакшн-шаблона (PR#197). Lime-стандарт (ADR-088) запрещает subtitle; badge не используется. Не путать с `tests/fixtures/cover.svg`, где все 4 типа слотов присутствуют (fixture тестирует движок, продакшн — бренд-стандарт). +- **Условный рендер (PR#144) — ключевая инварианта**: одно-иконочный кейс побайтово идентичен. Если будущий PR меняет `render.ts` или slot-parser, проверить `cmp assets/cover.png` не сломался. +- **`buildSvg` итерирует по шаблонным слотам, не по `args.slots`** — это контракт движка: шаблон объявляет layout (слоты), args — данные. Добавить новый слот в продакшн = добавить slot-комментарий в `templates/cover.svg` (как сделано здесь), а не менять движок. diff --git a/docs/project-map/README.md b/docs/project-map/README.md index f0eeea2..28c9e01 100644 --- a/docs/project-map/README.md +++ b/docs/project-map/README.md @@ -70,7 +70,7 @@ opencode-config/ │ │ ├── render.mjs # SVG string → PNG via sharp (fontFiles: Geist TTF bundle) │ │ ├── cli.ts # CLI entry: render