opencode-config/docs/decisions/060-pr-134-draw-image-svg-template-renderer.md
Sergey 56b67d2d5c
feat(draw-image): SVG template renderer for on-brand covers (#134)
* feat(draw-image): add SVG template render engine

* feat(draw-image): add opencode plugin and tool tests

* test(draw-image): add vitest unit integration e2e suite

* chore(draw-image): wire CI dependabot docs and ADR

* docs(handoff): set PR number

* docs(handoff): fix PR number placeholder in frontmatter

* fix(ci): use tempfile and trailing newline in draw-image tests

---------

Co-authored-by: opencode-agent <agent@opencode.local>
2026-07-29 23:42:50 +03:00

24 lines
No EOL
2.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ADR-060: SVG template renderer for on-brand covers
## Статус
Accepted (2026-07-29)
## Контекст
В продуктовых репо slaid098 нет ни одного cover-ассета. Обложки для slaid098.dev рисовались вручную или генерировались fallback-функцией (svgFallback в route.ts — HSL-градиент + буква, off-brand). Нужна утилита, которая берёт SVG-шаблон со слотами, подставляет палитру из brand.json + контент из args, рендерит PNG через sharp с bundled Geist TTF — always on-brand.
Архитектурный выбор: размещение в `.opencode/` (opencode plugin + render-движок) через volume bind-mount → видна во всех воркспейсах глобально. Отдельный package.json в `.opencode/draw-image/` (deps: sharp, lucide-static) — не загрязняет `.opencode/package.json` (используется для tools/*.ts).
## Решение
1. **opencode plugin** (`.opencode/tools/draw-image.ts`) — spawnSync wrapper вокруг `node cli.ts render ...`, возвращает `{ path, hash, status }`
2. **Слот-система** — HTML-комментарии в SVG-шаблоне парсятся в SlotSpec (name/x/y/w/h/fit/recolor/bg/border/radius). Контент резолвится: Lucide lookup → brand-logos lookup → file path
3. **Render pipeline** — buildSvg (assemble) → render.mjs (sharp → PNG 1024×1024, fontFiles: Geist TTF)
4. **Идемпотентность** — cover.meta.json с sha256(brand+template+args), skip при совпадении хеша
5. **Lucide-иконки** — npm-пакет lucide-static, postinstall sync в icons/lucide/, recolor stroke → accent
6. **Шрифты** — Geist Sans TTF bundled в fonts/, переданы в sharp через fontFiles (без этого fallback = off-brand)
7. **Тесты** — vitest (48: unit + integration + e2e) + pytest (5: tool wrapper via _ts_loader.mjs)
## Альтернативы
- **@resvg/resvg-js вместо sharp** — sharp уже используется в экосистеме, fontFiles опция покрывает потребность. resvg был бы отдельной dep.
- **Шрифты через fontconfig (системная установка)** — негибко, требует root в CI. Bundle TTF в fonts/ + fontFiles опция sharp — self-contained.
- **Шаблоны в коде (не SVG-файлы)** — менее гибко, требует правок кода для нового шаблона. SVG-файлы в templates/ — ноль правок кода.
- **Отдельный package.json vs расширить .opencode/package.json** — отдельный package.json изолирует deps (sharp тяжёлая native dep) от tools/*.ts.