From 56b67d2d5cea0aa2e3c4cf41fdcd6cb51852ef45 Mon Sep 17 00:00:00 2001 From: Sergey <93754860+slaid098@users.noreply.github.com> Date: Wed, 29 Jul 2026 23:42:50 +0300 Subject: [PATCH] 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 --- .github/dependabot.yml | 7 +- .github/workflows/ci.yml | 4 + .opencode/draw-image/.gitignore | 5 + .opencode/draw-image/brand-logos/.gitkeep | 0 .opencode/draw-image/brand.json | 8 + .opencode/draw-image/cli.ts | 84 + .opencode/draw-image/fonts/Geist-Bold.ttf | Bin 0 -> 128824 bytes .opencode/draw-image/fonts/Geist-Regular.ttf | Bin 0 -> 126048 bytes .opencode/draw-image/icons/lucide/mic.svg | 17 + .opencode/draw-image/icons/lucide/play.svg | 15 + .opencode/draw-image/package-lock.json | 2577 +++++++++++++++++ .opencode/draw-image/package.json | 20 + .opencode/draw-image/render.mjs | 44 + .opencode/draw-image/scripts/sync-lucide.mjs | 23 + .opencode/draw-image/src/config.ts | 56 + .opencode/draw-image/src/fit.ts | 71 + .opencode/draw-image/src/recolor.ts | 26 + .opencode/draw-image/src/render.ts | 81 + .opencode/draw-image/src/resolve.ts | 78 + .opencode/draw-image/src/slot-parser.ts | 57 + .opencode/draw-image/templates/cover.svg | 8 + .opencode/draw-image/tests/cli.test.ts | 57 + .opencode/draw-image/tests/config.test.ts | 42 + .../draw-image/tests/e2e.bad-input.test.ts | 36 + .../draw-image/tests/e2e.no-icon.test.ts | 33 + .opencode/draw-image/tests/e2e.test.ts | 60 + .opencode/draw-image/tests/fit.test.ts | 64 + .../draw-image/tests/idempotency.test.ts | 51 + .opencode/draw-image/tests/recolor.test.ts | 48 + .../tests/render.integration.test.ts | 57 + .../draw-image/tests/slot-parser.test.ts | 58 + .opencode/draw-image/tests/slot.test.ts | 75 + .opencode/draw-image/tsconfig.json | 15 + .opencode/draw-image/vitest.config.ts | 9 + .opencode/tools/draw-image.ts | 32 + ...pr-134-draw-image-svg-template-renderer.md | 24 + ...pr-134-draw-image-svg-template-renderer.md | 29 + docs/project-map/README.md | 36 +- tests/test_draw_image_tool.py | 132 + tests/test_draw_image_tool.ts | 70 + 40 files changed, 4107 insertions(+), 2 deletions(-) create mode 100644 .opencode/draw-image/.gitignore create mode 100644 .opencode/draw-image/brand-logos/.gitkeep create mode 100644 .opencode/draw-image/brand.json create mode 100644 .opencode/draw-image/cli.ts create mode 100644 .opencode/draw-image/fonts/Geist-Bold.ttf create mode 100644 .opencode/draw-image/fonts/Geist-Regular.ttf create mode 100644 .opencode/draw-image/icons/lucide/mic.svg create mode 100644 .opencode/draw-image/icons/lucide/play.svg create mode 100644 .opencode/draw-image/package-lock.json create mode 100644 .opencode/draw-image/package.json create mode 100644 .opencode/draw-image/render.mjs create mode 100644 .opencode/draw-image/scripts/sync-lucide.mjs create mode 100644 .opencode/draw-image/src/config.ts create mode 100644 .opencode/draw-image/src/fit.ts create mode 100644 .opencode/draw-image/src/recolor.ts create mode 100644 .opencode/draw-image/src/render.ts create mode 100644 .opencode/draw-image/src/resolve.ts create mode 100644 .opencode/draw-image/src/slot-parser.ts create mode 100644 .opencode/draw-image/templates/cover.svg create mode 100644 .opencode/draw-image/tests/cli.test.ts create mode 100644 .opencode/draw-image/tests/config.test.ts create mode 100644 .opencode/draw-image/tests/e2e.bad-input.test.ts create mode 100644 .opencode/draw-image/tests/e2e.no-icon.test.ts create mode 100644 .opencode/draw-image/tests/e2e.test.ts create mode 100644 .opencode/draw-image/tests/fit.test.ts create mode 100644 .opencode/draw-image/tests/idempotency.test.ts create mode 100644 .opencode/draw-image/tests/recolor.test.ts create mode 100644 .opencode/draw-image/tests/render.integration.test.ts create mode 100644 .opencode/draw-image/tests/slot-parser.test.ts create mode 100644 .opencode/draw-image/tests/slot.test.ts create mode 100644 .opencode/draw-image/tsconfig.json create mode 100644 .opencode/draw-image/vitest.config.ts create mode 100644 .opencode/tools/draw-image.ts create mode 100644 docs/decisions/060-pr-134-draw-image-svg-template-renderer.md create mode 100644 docs/handoff/pr-134-draw-image-svg-template-renderer.md create mode 100644 tests/test_draw_image_tool.py create mode 100644 tests/test_draw_image_tool.ts diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 188e2a0..026e0d6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,7 +6,12 @@ updates: interval: weekly open-pull-requests-limit: 5 - package-ecosystem: "npm" - directory: "/config" + directory: "/.opencode" + schedule: + interval: weekly + open-pull-requests-limit: 5 + - package-ecosystem: "npm" + directory: "/.opencode/draw-image" schedule: interval: weekly open-pull-requests-limit: 5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57d5151..5f9aecf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,6 +77,10 @@ jobs: - run: sudo apt-get update && sudo apt-get install -y ripgrep - run: npm ci working-directory: .opencode + - run: npm ci + working-directory: .opencode/draw-image + - run: npm test + working-directory: .opencode/draw-image - uses: astral-sh/setup-uv@v3 - run: uv sync --extra dev --python ${{ matrix.python }} - run: uv run --python ${{ matrix.python }} pytest diff --git a/.opencode/draw-image/.gitignore b/.opencode/draw-image/.gitignore new file mode 100644 index 0000000..0a78b69 --- /dev/null +++ b/.opencode/draw-image/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +.tmp-render.svg +icons/lucide/* +!icons/lucide/mic.svg +!icons/lucide/play.svg \ No newline at end of file diff --git a/.opencode/draw-image/brand-logos/.gitkeep b/.opencode/draw-image/brand-logos/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.opencode/draw-image/brand.json b/.opencode/draw-image/brand.json new file mode 100644 index 0000000..db10a32 --- /dev/null +++ b/.opencode/draw-image/brand.json @@ -0,0 +1,8 @@ +{ + "base": "#0a0a0a", + "surface": "#121212", + "fg": "#ededed", + "muted": "#a1a1aa", + "accent": "#ccff00", + "line": "rgba(255,255,255,0.06)" +} \ No newline at end of file diff --git a/.opencode/draw-image/cli.ts b/.opencode/draw-image/cli.ts new file mode 100644 index 0000000..7af443a --- /dev/null +++ b/.opencode/draw-image/cli.ts @@ -0,0 +1,84 @@ +import { writeFileSync, mkdirSync, existsSync, readFileSync } from "node:fs" +import path from "node:path" +import { fileURLToPath } from "node:url" +import { spawnSync } from "node:child_process" +import { loadBrand } from "./src/config.ts" +import { loadTemplate, buildSvg, computeHash } from "./src/render.ts" + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +function parseArgs(argv: string[]): Record { + const out: Record = {} + for (let i = 0; i < argv.length; i++) { + const a = argv[i] + if (a.startsWith("--")) { + const key = a.slice(2) + const val = argv[i + 1] ?? "" + out[key] = val + i++ + } + } + return out +} + +function main() { + const argv = process.argv.slice(2) + if (argv.length < 2 || argv[0] !== "render") { + process.stderr.write('usage: node cli.ts render