Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
36134cd
fix: prevent log output from overlapping other widgets
IMDelewer May 13, 2026
ddd0976
feat: add action menu widget with numbered items and color support
IMDelewer May 13, 2026
152be91
feat: add vertical tab groups for in-page navigation
IMDelewer May 13, 2026
ed77d25
fix: double vertical lines on group borders and theme backgrounds
IMDelewer May 13, 2026
b582b61
fix: output help and terminal info to terminal panel
IMDelewer May 13, 2026
e473ce1
feat: add RigiCheckbox widget
IMDelewer May 13, 2026
111528b
feat: add transparent background setting with opacity control
IMDelewer May 13, 2026
14377e4
feat: add vertical tabs and action menu examples
IMDelewer May 13, 2026
314ee6f
fix: show current transparency percent value in settings input
IMDelewer May 13, 2026
532240e
fix: export RigiVerticalTabs and RigiActionMenuItemData from main module
IMDelewer May 13, 2026
ad481cc
refactor: use direct main module imports in examples
IMDelewer May 13, 2026
6949558
feat: add keyboard support and focus to RigiCheckbox
IMDelewer May 13, 2026
c367baa
fix: use unicode symbols in RigiCheckbox to avoid markup parsing issues
IMDelewer May 13, 2026
cea6b40
feat: rename all public classes removing Rigi prefix
IMDelewer May 13, 2026
c4293ce
fix: single-row horizontal tabs with underline, transparent modal bac…
IMDelewer May 13, 2026
d8c9f76
fix: small 1px tab underline, unified separator line, menu panel self…
IMDelewer May 13, 2026
0a29478
feat: narrow hamburger menu to fit longest item, add editable table t…
IMDelewer May 13, 2026
399afe5
fix: use Any for DataTable key lists in example 10
IMDelewer May 13, 2026
62b02c2
feat: row cursor, right-click/E action menu with edit modal and delet…
IMDelewer May 13, 2026
cbbcec6
fix: action menu as overlay, right-click via mouse_down, pyright/ruff…
IMDelewer May 13, 2026
d265976
fix: update action panel in place to avoid DuplicateIds on rapid re-t…
IMDelewer May 14, 2026
64fd1cd
chore: bump 1.3.1
IMDelewer May 14, 2026
83bf7d9
feat: add PR summary
IMDelewer May 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
346 changes: 346 additions & 0 deletions .github/workflows/pr-summary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
name: PR Summary

on:
pull_request:
types:
- opened
- synchronize
- reopened

permissions:
pull-requests: write
contents: read

jobs:
pr-summary:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Generate PR summary
id: summary
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;

const commits = await github.paginate(
github.rest.pulls.listCommits,
{
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
per_page: 100
}
);

const files = await github.paginate(
github.rest.pulls.listFiles,
{
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
per_page: 100
}
);

const groups = {
feat: [],
fix: [],
refactor: [],
perf: [],
docs: [],
test: [],
chore: [],
ci: [],
style: [],
build: [],
revert: [],
other: []
};

const titles = {
feat: "✨ Features",
fix: "🐛 Fixes",
refactor: "♻️ Refactoring",
perf: "⚡ Performance",
docs: "📝 Documentation",
test: "🧪 Tests",
chore: "🔧 Chores",
ci: "🚀 CI",
style: "🎨 Style",
build: "📦 Build",
revert: "⏪ Reverts",
other: "📌 Other"
};

let additions = 0;
let deletions = 0;

const contributors = new Map();
const scopeStats = new Map();
const dirStats = new Map();

for (const file of files) {
additions += file.additions;
deletions += file.deletions;

const dir =
file.filename.includes("/")
? file.filename.split("/")[0]
: "root";

dirStats.set(
dir,
(dirStats.get(dir) || 0) + 1
);
}

for (const commit of commits) {
const sha = commit.sha.substring(0, 7);
const url = commit.html_url;

const author =
commit.author?.login ||
commit.commit.author.name;

contributors.set(
author,
(contributors.get(author) || 0) + 1
);

const message =
commit.commit.message.split("\n")[0];

const match = message.match(
/^(\w+)(\((.*?)\))?:\s(.+)$/
);

let type = "other";
let scope = "";
let description = message;

if (match) {
type = match[1];
scope = match[3] || "";
description = match[4];
}

if (!groups[type]) {
type = "other";
}

if (scope) {
scopeStats.set(
scope,
(scopeStats.get(scope) || 0) + 1
);
}

groups[type].push({
sha,
url,
scope,
description,
author
});
}

const topFiles = [...files]
.sort((a, b) => b.changes - a.changes)
.slice(0, 10);

const topScopes = [...scopeStats.entries()]
.sort((a, b) => b[1] - a[1]);

const topDirs = [...dirStats.entries()]
.sort((a, b) => b[1] - a[1]);

function progress(value, total) {
const width = 20;
const filled = Math.round((value / total) * width);

return (
"█".repeat(filled) +
"░".repeat(width - filled)
);
}

const totalTypedCommits = Object.values(groups)
.reduce((acc, arr) => acc + arr.length, 0);

let body = "";

body += `<!-- pr-rich-summary -->\n`;

body += `# 📋 PR Summary\n\n`;

body += `### ${pr.title}\n\n`;

body += `> ${pr.user.login} opened a pull request from \`${pr.head.ref}\` → \`${pr.base.ref}\`\n\n`;

body += `---\n\n`;

body += `## 📊 Overview\n\n`;

body += `| Metric | Value |\n`;
body += `|---|---|\n`;
body += `| Commits | \`${commits.length}\` |\n`;
body += `| Changed Files | \`${files.length}\` |\n`;
body += `| Additions | \`+${additions}\` |\n`;
body += `| Deletions | \`-${deletions}\` |\n`;
body += `| Contributors | \`${contributors.size}\` |\n\n`;

body += `---\n\n`;

body += `## 📈 Change Distribution\n\n`;

for (const [type, items] of Object.entries(groups)) {
if (!items.length) continue;

const bar = progress(
items.length,
totalTypedCommits
);

body += `- ${titles[type]} \`${bar}\` ${items.length}\n`;
}

body += `\n---\n\n`;

for (const [type, items] of Object.entries(groups)) {
if (!items.length) continue;

body += `## ${titles[type]}\n\n`;

body += `<details open>\n`;
body += `<summary><b>${items.length} commits</b></summary>\n\n`;

for (const item of items) {
const scope = item.scope
? `\`${item.scope}\` `
: "";

body += `- [\`${item.sha}\`](${item.url}) ${scope}${item.description} — @${item.author}\n`;
}

body += `\n</details>\n\n`;
}

body += `---\n\n`;

body += `## 🎯 Main Impact Areas\n\n`;

for (const [scope, count] of topScopes.slice(0, 8)) {
body += `- \`${scope}\` — ${count} commits\n`;
}

body += `\n---\n\n`;

body += `## 📂 Most Changed Files\n\n`;

body += `\`\`\`diff\n`;

for (const file of topFiles) {
body += `+ ${String(file.additions).padEnd(4)} `;
body += `- ${String(file.deletions).padEnd(4)} `;
body += `${file.filename}\n`;
}

body += `\`\`\`\n\n`;

body += `---\n\n`;

body += `## 🧩 Changed Directories\n\n`;

for (const [dir, count] of topDirs.slice(0, 10)) {
body += `- \`${dir}/\` — ${count} files\n`;
}

body += `\n---\n\n`;

body += `## ⚠️ High Impact Files\n\n`;

const risky = files
.filter(f => f.changes > 200)
.sort((a, b) => b.changes - a.changes);

if (risky.length) {
for (const file of risky) {
body += `- \`${file.filename}\` `;
body += `(+${file.additions} / -${file.deletions})\n`;
}
} else {
body += `No high impact files detected.\n`;
}

body += `\n---\n\n`;

body += `## 👥 Contributors\n\n`;

for (const [user, count] of contributors.entries()) {
body += `- @${user} — ${count} commits\n`;
}

body += `\n---\n\n`;

body += `## 🔎 Raw Commit Messages\n\n`;

body += `<details>\n`;
body += `<summary>Show raw commits</summary>\n\n`;

body += `\`\`\`text\n`;

for (const commit of commits) {
body += `${commit.commit.message}\n\n`;
}

body += `\`\`\`\n`;
body += `</details>\n\n`;

body += `---\n\n`;

body += `<sub>Generated automatically from conventional commits and PR metadata.</sub>`;

core.setOutput("body", body);

- name: Create or update comment
uses: actions/github-script@v7
env:
BODY: ${{ steps.summary.outputs.body }}
with:
script: |
const marker = '<!-- pr-summary -->';

const comments = await github.paginate(
github.rest.issues.listComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number
}
);

const existing = comments.find(comment =>
comment.body.includes(marker)
);

if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: process.env.BODY
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: process.env.BODY
});
}
12 changes: 6 additions & 6 deletions examples/01_minimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

from __future__ import annotations

from rigi import RigiApp, TabDef
from rigi.layout.pane import RigiCard, RigiPane
from rigi import App, TabDef
from rigi.layout.pane import Card, Pane
from rigi.widgets import Label

app = RigiApp(name="minimal", version="1.0.0", description="Simplest possible Rigi app")
app = App(name="minimal", version="1.0.0", description="Simplest possible Rigi app")


def home():
return RigiPane(
RigiCard(
return Pane(
Card(
Label("Welcome to [bold cyan]Rigi[/bold cyan]!"),
Label(""),
Label(" [dim]Ctrl+H[/dim] Help"),
Expand All @@ -25,4 +25,4 @@ def home():
app.add_tab(TabDef(name="Home", key="1", icon="⌂", widget_factory=home))

if __name__ == "__main__":
RigiApp.run_cli(app)
App.run_cli(app)
Loading
Loading