From 2aa2384b8a5d19e5c29278accc5f7b785ac6f915 Mon Sep 17 00:00:00 2001 From: Andrejus Date: Thu, 29 Jan 2026 10:16:09 +0000 Subject: [PATCH 1/5] feat: vim colour schemes --- files/home/.vimrc | 73 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/files/home/.vimrc b/files/home/.vimrc index d16e2d2..21b1dca 100644 --- a/files/home/.vimrc +++ b/files/home/.vimrc @@ -76,7 +76,7 @@ highlight Constant guifg=#F88C14 highlight String guifg=#2CB494 highlight Number guifg=#F88C14 highlight Identifier guifg=NONE -highlight Function guifg=#0C48CC gui=bold +highlight Function guifg=#7290B8 gui=bold highlight Statement guifg=#4068D4 highlight Keyword guifg=#4068D4 highlight Type guifg=#2CB494 @@ -95,7 +95,7 @@ highlight DiffText guifg=#CCE0D0 guibg=#1A2A3A gui=bold highlight diffAdded guifg=#2CB494 gui=bold highlight diffRemoved guifg=#F40404 gui=bold -highlight diffFile guifg=#0C48CC gui=bold +highlight diffFile guifg=#7290B8 gui=bold highlight diffIndexLine guifg=#88409C highlight diffLine guifg=#00E4FC highlight diffSubname guifg=#F88C14 @@ -114,6 +114,75 @@ highlight FoldColumn guifg=#808080 guibg=NONE highlight QuickFixLine guibg=#1A3050 gui=bold +" UI Groups +highlight Title guifg=#00E4FC gui=bold +highlight Underlined guifg=#CCE0D0 gui=underline +highlight Directory guifg=#7290B8 +highlight MatchParen guifg=#3C3C3C guibg=#2CB494 +highlight NonText guifg=#808080 +highlight SpecialKey guifg=#808080 +highlight EndOfBuffer guifg=#808080 +highlight Conceal guifg=#808080 +highlight CursorLine guibg=#0A2A1A gui=NONE +highlight CursorColumn guibg=#0A2A1A +highlight SignColumn guifg=#808080 guibg=NONE +highlight WarningMsg guifg=#FCFC38 +highlight ErrorMsg guifg=#F40404 gui=bold +highlight ModeMsg guifg=#CCE0D0 gui=bold +highlight MoreMsg guifg=#2CB494 +highlight Question guifg=#2CB494 +highlight WildMenu guifg=#3C3C3C guibg=#FCFC38 +highlight TabLine guifg=#808080 guibg=#000080 gui=NONE +highlight TabLineFill guibg=#000080 +highlight TabLineSel guifg=#CCE0D0 guibg=#000080 gui=bold +highlight SpellLocal guifg=#00E4FC gui=undercurl guisp=#00E4FC + +" Syntax Groups +highlight Boolean guifg=#F88C14 +highlight Float guifg=#F88C14 +highlight Character guifg=#2CB494 +highlight Conditional guifg=#4068D4 +highlight Repeat guifg=#4068D4 +highlight Label guifg=#88409C +highlight Operator guifg=#CCE0D0 +highlight Exception guifg=#F40404 +highlight Include guifg=#F88C14 +highlight Define guifg=#F88C14 +highlight Macro guifg=#F88C14 +highlight PreCondit guifg=#F88C14 +highlight StorageClass guifg=#4068D4 +highlight Structure guifg=#4068D4 +highlight Typedef guifg=#4068D4 +highlight Delimiter guifg=#CCE0D0 +highlight SpecialChar guifg=#88409C +highlight SpecialComment guifg=#FCFC38 +highlight Debug guifg=#F032E6 +highlight Tag guifg=#7290B8 +highlight Ignore guifg=#808080 + +" HTML/Markdown Groups +highlight htmlBold guifg=#CCE0D0 gui=bold +highlight htmlItalic guifg=#CCE0D0 gui=italic +highlight htmlTagName guifg=#4068D4 +highlight htmlLink guifg=#CCE0D0 gui=underline +highlight htmlTag guifg=#808080 +highlight htmlEndTag guifg=#808080 +highlight markdownH1Delimiter guifg=#808080 +highlight markdownH2Delimiter guifg=#808080 +highlight markdownH3Delimiter guifg=#808080 +highlight markdownH4Delimiter guifg=#808080 +highlight markdownH5Delimiter guifg=#808080 +highlight markdownH6Delimiter guifg=#808080 + +" Language-specific Groups +highlight pythonBuiltinObj guifg=#F88C14 +highlight pythonDottedName guifg=#CCE0D0 +highlight rubyInterpolation guifg=#88409C +highlight rubyInterpolationDelimiter guifg=#88409C +highlight rubyBlockParameter guifg=#CCE0D0 +highlight rubyBlockArgument guifg=#CCE0D0 +highlight jsxCloseString guifg=#4068D4 + function! GitRoot() let l:root = systemlist('git rev-parse --show-toplevel')[0] if v:shell_error == 0 && isdirectory(l:root) From 28ba4584b3f13681b2f755406f599353a642511e Mon Sep 17 00:00:00 2001 From: Andrejus Date: Thu, 29 Jan 2026 10:16:24 +0000 Subject: [PATCH 2/5] feat: preview workflow --- .github/workflows/vim-preview.yml | 324 ++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 .github/workflows/vim-preview.yml diff --git a/.github/workflows/vim-preview.yml b/.github/workflows/vim-preview.yml new file mode 100644 index 0000000..902374d --- /dev/null +++ b/.github/workflows/vim-preview.yml @@ -0,0 +1,324 @@ +name: Vim Theme Preview + +on: + pull_request: + paths: + - 'files/home/.vimrc' + +permissions: + contents: read + pull-requests: write + +jobs: + preview: + name: Generate theme preview + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Playwright + run: npx playwright install chromium --with-deps + + - name: Generate HTML preview and take screenshots + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const path = require('path'); + + // === GENERATE HTML PREVIEW === + const vimrc = fs.readFileSync('files/home/.vimrc', 'utf8'); + + // Parse highlight definitions + const highlights = {}; + const highlightRegex = /^highlight\s+(\w+)\s+(.+)$/gm; + let match; + while ((match = highlightRegex.exec(vimrc)) !== null) { + const [, group, attrs] = match; + const parsed = {}; + const guifg = attrs.match(/guifg=(#?\w+)/); + const guibg = attrs.match(/guibg=(#?\w+)/); + const gui = attrs.match(/gui=(\S+)/); + if (guifg) parsed.fg = guifg[1]; + if (guibg) parsed.bg = guibg[1]; + if (gui) parsed.style = gui[1]; + highlights[group] = parsed; + } + + function toStyle(group) { + const h = highlights[group] || {}; + let style = ''; + if (h.fg && h.fg !== 'NONE') style += `color:${h.fg};`; + if (h.bg && h.bg !== 'NONE') style += `background:${h.bg};`; + if (h.style) { + if (h.style.includes('bold')) style += 'font-weight:bold;'; + if (h.style.includes('italic')) style += 'font-style:italic;'; + if (h.style.includes('underline')) style += 'text-decoration:underline;'; + } + return style; + } + + const palette = { + red: '#F40404', blue: '#0C48CC', teal: '#2CB494', yellow: '#FCFC38', + purple: '#88409C', orange: '#F88C14', brown: '#703014', white: '#CCE0D0', + green: '#088008', paleGreen: '#74A47C', paleYellow: '#FCFC7C', cyan: '#00E4FC', + pink: '#FFC4E4', olive: '#808000', lime: '#D2F53C', navy: '#000080', + magenta: '#F032E6', grey: '#808080', black: '#3C3C3C', tan: '#ECC4B0', + darkAqua: '#4068D4', blueGrey: '#7290B8' + }; + + const lightColors = ['yellow','paleYellow','lime','cyan','white','tan','pink']; + const paletteHtml = Object.entries(palette).map(([name, hex]) => + `
+
${name}
${hex}
+
` + ).join('\n'); + + const html = ` + Vim Color Scheme Preview + +

Vim Color Scheme Preview

+

Auto-generated from .vimrc highlight definitions.

+

Color Palette

+
${paletteHtml}
+ +

Python

+
Python
+            import os
+            from typing import Optional
+
+            @dataclass
+            class User:
+                # User model
+                name: str
+                age: int = 0
+                active: bool = True
+
+            def process(data: Optional[str])  float:
+                if data is None:
+                    raise ValueError("No data")
+                return 3.14
+ +

TypeScript

+
TypeScript
+            import { useState } from 'react';
+
+            interface Props {
+                name: string;
+                count?: number;
+            }
+
+            const Button = ({ name, count = 0 }: Props) => {
+                const [active, setActive] = useState(false);
+                return (
+                    <button onClick={() => setActive(true)}>
+                        {name}: {count}
+                    </button>
+                );
+            };
+ +

Go

+
Go
+            package main
+
+            import (
+                "fmt"
+                "errors"
+            )
+
+            type User struct {
+                Name   string
+                Active bool
+            }
+
+            func process(id int) (*User, error) {
+                if id < 0 {
+                    return nil, errors.New("invalid")
+                }
+                return &User{Active: true}, nil
+            }
+ +

Ruby

+
Ruby
+            require 'json'
+
+            class User
+              attr_accessor :name, :active
+
+              def initialize(name)
+                @name = name
+                @active = true
+              end
+
+              def greet
+                "Hello, #{@name}!"
+              end
+            end
+
+            users = [1, 2, 3].map { |id| User.new("user_#{id}") }
+ +

Markdown

+
Markdown
+            # Main Heading
+            ## Section Title
+            ### Subsection
+
+            Regular paragraph text with **bold** and *italic* text.
+
+            - List item one
+            - List item two
+
+            Here's a [link to docs](https://example.com) in the text.
+
+            Inline \`code\` and a code block:
+
+            \`\`\`javascript
+            const x = 42;
+            \`\`\`
+ +

Git Diff

+
Diff
+            diff --git a/file.txt b/file.txt
+            index abc123..def456 100644
+            @@ -1,5 +1,5 @@
+             unchanged line
+            -removed line
+            +added line
+             another unchanged
+ +

UI Elements

+
Messages
+            Warning: File has been modified outside of Vim
+            E488: Trailing characters: xyz
+            Press ENTER or type command to continue
+            Save changes? [Y]es, [N]o, [C]ancel:
+            -- INSERT --
+            TODO: Fix this later
+ + `; + + fs.writeFileSync('/tmp/preview.html', html); + console.log('Generated /tmp/preview.html'); + + - name: Take screenshots + uses: actions/github-script@v7 + with: + script: | + const { chromium } = require('playwright'); + const fs = require('fs'); + const path = require('path'); + + fs.mkdirSync('/tmp/screenshots', { recursive: true }); + + const browser = await chromium.launch(); + const page = await browser.newPage(); + await page.setViewportSize({ width: 1200, height: 800 }); + await page.goto('file:///tmp/preview.html'); + await page.waitForLoadState('networkidle'); + + const sections = [ + { id: 'python', name: 'python' }, + { id: 'typescript', name: 'typescript' }, + { id: 'go', name: 'go' }, + { id: 'ruby', name: 'ruby' }, + { id: 'markdown', name: 'markdown' }, + { id: 'diff', name: 'diff' }, + { id: 'ui', name: 'ui-elements' } + ]; + + for (const section of sections) { + const heading = await page.$(`#${section.id}`); + const pre = await page.$(`#${section.id} + pre`); + if (!heading || !pre) continue; + + const headingBox = await heading.boundingBox(); + const preBox = await pre.boundingBox(); + if (!headingBox || !preBox) continue; + + const padding = 20; + await page.screenshot({ + path: `/tmp/screenshots/${section.name}.png`, + clip: { + x: Math.max(0, Math.min(headingBox.x, preBox.x) - padding), + y: Math.max(0, headingBox.y - padding), + width: Math.max(headingBox.width, preBox.width) + padding * 2, + height: (preBox.y + preBox.height) - headingBox.y + padding * 2 + } + }); + console.log(`Captured: ${section.name}.png`); + } + + const palette = await page.$('.palette'); + if (palette) { + const box = await palette.boundingBox(); + if (box) { + await page.screenshot({ + path: '/tmp/screenshots/palette.png', + clip: { x: Math.max(0, box.x - 20), y: Math.max(0, box.y - 40), width: box.width + 40, height: box.height + 60 } + }); + console.log('Captured: palette.png'); + } + } + + await browser.close(); + + - name: Upload screenshots + uses: actions/upload-artifact@v4 + with: + name: vim-theme-screenshots + path: /tmp/screenshots/*.png + + - name: Create PR comment + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const files = fs.readdirSync('/tmp/screenshots').filter(f => f.endsWith('.png')); + const { owner, repo } = context.repo; + const runId = context.runId; + + const sectionNames = { + 'palette': '🎨 Color Palette', + 'python': '🐍 Python', + 'typescript': '📘 TypeScript', + 'go': '🔵 Go', + 'ruby': '💎 Ruby', + 'markdown': '📝 Markdown', + 'diff': '📊 Git Diff', + 'ui-elements': '🖥️ UI Elements' + }; + + let body = `## 🎨 Vim Theme Preview\n\n`; + body += `Preview of color scheme changes in this PR.\n\n`; + body += `> Screenshots are available as [workflow artifacts](https://github.com/${owner}/${repo}/actions/runs/${runId}).\n\n`; + body += `### Captured Previews\n\n`; + for (const file of files) { + const name = file.replace('.png', ''); + body += `- ${sectionNames[name] || name}\n`; + } + body += `\n---\n*Generated by [vim-preview workflow](https://github.com/${owner}/${repo}/actions/runs/${runId})*`; + + const { data: comments } = await github.rest.issues.listComments({ + owner, repo, issue_number: context.issue.number + }); + + const botComment = comments.find(c => c.user.type === 'Bot' && c.body.includes('Vim Theme Preview')); + + if (botComment) { + await github.rest.issues.updateComment({ owner, repo, comment_id: botComment.id, body }); + } else { + await github.rest.issues.createComment({ owner, repo, issue_number: context.issue.number, body }); + } From fda906d240d1e70df93b265f3e730cf185187efe Mon Sep 17 00:00:00 2001 From: Andrejus Date: Thu, 29 Jan 2026 10:21:53 +0000 Subject: [PATCH 3/5] fix: ci --- .github/workflows/vim-preview.yml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/vim-preview.yml b/.github/workflows/vim-preview.yml index 902374d..95b204d 100644 --- a/.github/workflows/vim-preview.yml +++ b/.github/workflows/vim-preview.yml @@ -22,7 +22,9 @@ jobs: node-version: '20' - name: Install Playwright - run: npx playwright install chromium --with-deps + run: | + npm install playwright + npx playwright install chromium --with-deps - name: Generate HTML preview and take screenshots uses: actions/github-script@v7 @@ -214,13 +216,13 @@ jobs: console.log('Generated /tmp/preview.html'); - name: Take screenshots - uses: actions/github-script@v7 - with: - script: | - const { chromium } = require('playwright'); - const fs = require('fs'); - const path = require('path'); - + run: | + node << 'EOF' + const { chromium } = require('playwright'); + const fs = require('fs'); + const path = require('path'); + + (async () => { fs.mkdirSync('/tmp/screenshots', { recursive: true }); const browser = await chromium.launch(); @@ -274,6 +276,8 @@ jobs: } await browser.close(); + })(); + EOF - name: Upload screenshots uses: actions/upload-artifact@v4 From 37e17814123e7c65901639c9e0357c5f289e74a8 Mon Sep 17 00:00:00 2001 From: Andrejus Date: Thu, 29 Jan 2026 10:27:10 +0000 Subject: [PATCH 4/5] fix: ci --- .github/workflows/vim-preview.yml | 68 +++++++++++++------------------ 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/.github/workflows/vim-preview.yml b/.github/workflows/vim-preview.yml index 95b204d..f15f98b 100644 --- a/.github/workflows/vim-preview.yml +++ b/.github/workflows/vim-preview.yml @@ -220,59 +220,49 @@ jobs: node << 'EOF' const { chromium } = require('playwright'); const fs = require('fs'); - const path = require('path'); - + (async () => { fs.mkdirSync('/tmp/screenshots', { recursive: true }); const browser = await chromium.launch(); const page = await browser.newPage(); - await page.setViewportSize({ width: 1200, height: 800 }); + // Use tall viewport to ensure all elements render with correct bounding boxes + await page.setViewportSize({ width: 1200, height: 4000 }); await page.goto('file:///tmp/preview.html'); await page.waitForLoadState('networkidle'); - const sections = [ - { id: 'python', name: 'python' }, - { id: 'typescript', name: 'typescript' }, - { id: 'go', name: 'go' }, - { id: 'ruby', name: 'ruby' }, - { id: 'markdown', name: 'markdown' }, - { id: 'diff', name: 'diff' }, - { id: 'ui', name: 'ui-elements' } - ]; + const sections = ['python', 'typescript', 'go', 'ruby', 'markdown', 'diff', 'ui']; - for (const section of sections) { - const heading = await page.$(`#${section.id}`); - const pre = await page.$(`#${section.id} + pre`); - if (!heading || !pre) continue; + for (const id of sections) { + const heading = await page.$(`#${id}`); + const pre = await page.$(`#${id} + pre`); + if (!heading || !pre) { + console.log(`Skipped: ${id} (not found)`); + continue; + } + // Create a wrapper div for combined screenshot const headingBox = await heading.boundingBox(); const preBox = await pre.boundingBox(); if (!headingBox || !preBox) continue; - const padding = 20; - await page.screenshot({ - path: `/tmp/screenshots/${section.name}.png`, - clip: { - x: Math.max(0, Math.min(headingBox.x, preBox.x) - padding), - y: Math.max(0, headingBox.y - padding), - width: Math.max(headingBox.width, preBox.width) + padding * 2, - height: (preBox.y + preBox.height) - headingBox.y + padding * 2 - } - }); - console.log(`Captured: ${section.name}.png`); + const padding = 16; + const clip = { + x: Math.max(0, Math.min(headingBox.x, preBox.x) - padding), + y: Math.max(0, headingBox.y - padding), + width: Math.max(headingBox.width, preBox.width) + padding * 2, + height: (preBox.y + preBox.height) - headingBox.y + padding * 2 + }; + + await page.screenshot({ path: `/tmp/screenshots/${id}.png`, clip }); + console.log(`Captured: ${id}.png`); } + // Palette screenshot const palette = await page.$('.palette'); if (palette) { - const box = await palette.boundingBox(); - if (box) { - await page.screenshot({ - path: '/tmp/screenshots/palette.png', - clip: { x: Math.max(0, box.x - 20), y: Math.max(0, box.y - 40), width: box.width + 40, height: box.height + 60 } - }); - console.log('Captured: palette.png'); - } + await palette.screenshot({ path: '/tmp/screenshots/palette.png' }); + console.log('Captured: palette.png'); } await browser.close(); @@ -290,10 +280,10 @@ jobs: with: script: | const fs = require('fs'); - const files = fs.readdirSync('/tmp/screenshots').filter(f => f.endsWith('.png')); const { owner, repo } = context.repo; const runId = context.runId; + const files = fs.readdirSync('/tmp/screenshots').filter(f => f.endsWith('.png')); const sectionNames = { 'palette': '🎨 Color Palette', 'python': '🐍 Python', @@ -302,13 +292,13 @@ jobs: 'ruby': '💎 Ruby', 'markdown': '📝 Markdown', 'diff': '📊 Git Diff', - 'ui-elements': '🖥️ UI Elements' + 'ui': '🖥️ UI Elements' }; let body = `## 🎨 Vim Theme Preview\n\n`; body += `Preview of color scheme changes in this PR.\n\n`; - body += `> Screenshots are available as [workflow artifacts](https://github.com/${owner}/${repo}/actions/runs/${runId}).\n\n`; - body += `### Captured Previews\n\n`; + body += `📸 **[Download screenshots](https://github.com/${owner}/${repo}/actions/runs/${runId})**\n\n`; + body += `### Captured Previews\n`; for (const file of files) { const name = file.replace('.png', ''); body += `- ${sectionNames[name] || name}\n`; From 47fde28a43babc4d956ee168697b55de9eb3345a Mon Sep 17 00:00:00 2001 From: Andrejus Date: Thu, 29 Jan 2026 10:46:32 +0000 Subject: [PATCH 5/5] fix: format --- .github/workflows/vim-preview.yml | 100 ++++++++++++------------------ 1 file changed, 40 insertions(+), 60 deletions(-) diff --git a/.github/workflows/vim-preview.yml b/.github/workflows/vim-preview.yml index f15f98b..7539ddd 100644 --- a/.github/workflows/vim-preview.yml +++ b/.github/workflows/vim-preview.yml @@ -6,7 +6,7 @@ on: - 'files/home/.vimrc' permissions: - contents: read + contents: write pull-requests: write jobs: @@ -31,7 +31,6 @@ jobs: with: script: | const fs = require('fs'); - const path = require('path'); // === GENERATE HTML PREVIEW === const vimrc = fs.readFileSync('files/home/.vimrc', 'utf8'); @@ -65,22 +64,6 @@ jobs: return style; } - const palette = { - red: '#F40404', blue: '#0C48CC', teal: '#2CB494', yellow: '#FCFC38', - purple: '#88409C', orange: '#F88C14', brown: '#703014', white: '#CCE0D0', - green: '#088008', paleGreen: '#74A47C', paleYellow: '#FCFC7C', cyan: '#00E4FC', - pink: '#FFC4E4', olive: '#808000', lime: '#D2F53C', navy: '#000080', - magenta: '#F032E6', grey: '#808080', black: '#3C3C3C', tan: '#ECC4B0', - darkAqua: '#4068D4', blueGrey: '#7290B8' - }; - - const lightColors = ['yellow','paleYellow','lime','cyan','white','tan','pink']; - const paletteHtml = Object.entries(palette).map(([name, hex]) => - `
-
${name}
${hex}
-
` - ).join('\n'); - const html = ` Vim Color Scheme Preview

Vim Color Scheme Preview

-

Auto-generated from .vimrc highlight definitions.

-

Color Palette

-
${paletteHtml}

Python

Python
@@ -258,61 +236,63 @@ jobs:
               console.log(`Captured: ${id}.png`);
             }
             
-            // Palette screenshot
-            const palette = await page.$('.palette');
-            if (palette) {
-              await palette.screenshot({ path: '/tmp/screenshots/palette.png' });
-              console.log('Captured: palette.png');
-            }
-            
             await browser.close();
           })();
           EOF
 
-      - name: Upload screenshots
-        uses: actions/upload-artifact@v4
-        with:
-          name: vim-theme-screenshots
-          path: /tmp/screenshots/*.png
-
-      - name: Create PR comment
+      - name: Upload images and create PR comment
         uses: actions/github-script@v7
         with:
           script: |
             const fs = require('fs');
             const { owner, repo } = context.repo;
-            const runId = context.runId;
+            const prNumber = context.issue.number;
+            const sha = context.payload.pull_request.head.sha.slice(0, 7);
             
             const files = fs.readdirSync('/tmp/screenshots').filter(f => f.endsWith('.png'));
-            const sectionNames = {
-              'palette': '🎨 Color Palette',
-              'python': '🐍 Python',
-              'typescript': '📘 TypeScript',
-              'go': '🔵 Go',
-              'ruby': '💎 Ruby',
-              'markdown': '📝 Markdown',
-              'diff': '📊 Git Diff',
-              'ui': '🖥️ UI Elements'
+            const labels = {
+              python: 'Python', typescript: 'TypeScript',
+              go: 'Go', ruby: 'Ruby', markdown: 'Markdown', diff: 'Git Diff', ui: 'UI Elements'
             };
             
-            let body = `## 🎨 Vim Theme Preview\n\n`;
-            body += `Preview of color scheme changes in this PR.\n\n`;
-            body += `📸 **[Download screenshots](https://github.com/${owner}/${repo}/actions/runs/${runId})**\n\n`;
-            body += `### Captured Previews\n`;
+            // Create release for this commit
+            const tag = `vim-preview-${sha}`;
+            const { data: release } = await github.rest.repos.createRelease({
+              owner, repo, tag_name: tag,
+              name: `Preview ${sha}`,
+              body: `PR #${prNumber}`,
+              prerelease: true
+            });
+            
+            // Upload images
+            const urls = {};
             for (const file of files) {
-              const name = file.replace('.png', '');
-              body += `- ${sectionNames[name] || name}\n`;
+              const { data: asset } = await github.rest.repos.uploadReleaseAsset({
+                owner, repo, release_id: release.id, name: file,
+                data: fs.readFileSync(`/tmp/screenshots/${file}`)
+              });
+              urls[file.replace('.png', '')] = asset.browser_download_url;
             }
-            body += `\n---\n*Generated by [vim-preview workflow](https://github.com/${owner}/${repo}/actions/runs/${runId})*`;
             
+            // Hide previous preview comments
             const { data: comments } = await github.rest.issues.listComments({
-              owner, repo, issue_number: context.issue.number
+              owner, repo, issue_number: prNumber
             });
+            for (const c of comments) {
+              if (c.user.type === 'Bot' && c.body.includes('')) {
+                await github.graphql(`mutation { minimizeComment(input: {subjectId: "${c.node_id}", classifier: OUTDATED}) { clientMutationId } }`);
+              }
+            }
             
-            const botComment = comments.find(c => c.user.type === 'Bot' && c.body.includes('Vim Theme Preview'));
+            // Create new comment
+            const fullSha = context.payload.pull_request.head.sha;
+            const commitUrl = `https://github.com/${owner}/${repo}/commit/${fullSha}`;
+            const workflowUrl = `https://github.com/${owner}/${repo}/actions/runs/${context.runId}`;
             
-            if (botComment) {
-              await github.rest.issues.updateComment({ owner, repo, comment_id: botComment.id, body });
-            } else {
-              await github.rest.issues.createComment({ owner, repo, issue_number: context.issue.number, body });
+            let body = `\n## Vim Theme Preview\n\nCommit: ${commitUrl}\n\n`;
+            for (const [name, url] of Object.entries(urls)) {
+              body += `
${labels[name] || name}\n\n![${name}](${url})\n\n
\n\n`; } + body += `---\n*Generated by [workflow](${workflowUrl})*`; + + await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body });