* 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>
24 lines
No EOL
2.9 KiB
Markdown
24 lines
No EOL
2.9 KiB
Markdown
# 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. |