Skip to content

Instantly share code, notes, and snippets.

@SeanTAllen
Created September 29, 2026 10:30
Show Gist options
  • Select an option

  • Save SeanTAllen/793ceef2c4d29aa505c1a6a5b3966a07 to your computer and use it in GitHub Desktop.

Select an option

Save SeanTAllen/793ceef2c4d29aa505c1a6a5b3966a07 to your computer and use it in GitHub Desktop.
PR Overview Example — ponylang/ponyc #6188
<title>PR #6188 Overview</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=Inter:wght@400;500;600;700&display=swap">
<style>
/* Layout: fixed left nav rail, scrolling main content, expand/collapse detail panels */
:root {
--bg: #fafaf9;
--fg: #1c1917;
--muted: #78716c;
--accent: #2563eb;
--accent-soft: #dbeafe;
--border: #e7e5e4;
--code-bg: #f5f5f4;
--added-bg: #dcfce7;
--added-fg: #166534;
--removed-bg: #fee2e2;
--removed-fg: #991b1b;
--card-bg: #ffffff;
--nav-bg: #1c1917;
--nav-fg: #d6d3d1;
--nav-active: #60a5fa;
--diagram-node: #e0e7ff;
--diagram-edge: #6366f1;
--diagram-highlight: #f59e0b;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #0c0a09;
--fg: #e7e5e4;
--muted: #a8a29e;
--accent: #60a5fa;
--accent-soft: #1e3a5f;
--border: #292524;
--code-bg: #1c1917;
--added-bg: #052e16;
--added-fg: #86efac;
--removed-bg: #450a0a;
--removed-fg: #fca5a5;
--card-bg: #1c1917;
--nav-bg: #0c0a09;
--nav-fg: #a8a29e;
--nav-active: #60a5fa;
--diagram-node: #312e81;
--diagram-edge: #818cf8;
--diagram-highlight: #fbbf24;
color-scheme: dark;
}
}
:root[data-theme="dark"] {
--bg: #0c0a09;
--fg: #e7e5e4;
--muted: #a8a29e;
--accent: #60a5fa;
--accent-soft: #1e3a5f;
--border: #292524;
--code-bg: #1c1917;
--added-bg: #052e16;
--added-fg: #86efac;
--removed-bg: #450a0a;
--removed-fg: #fca5a5;
--card-bg: #1c1917;
--nav-bg: #0c0a09;
--nav-fg: #a8a29e;
--nav-active: #60a5fa;
--diagram-node: #312e81;
--diagram-edge: #818cf8;
--diagram-highlight: #fbbf24;
color-scheme: dark;
}
body {
background: var(--bg);
color: var(--fg);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
line-height: 1.6;
padding: 0;
}
/* --- Nav rail --- */
nav {
position: fixed;
top: env(safe-area-inset-top, 0px);
left: 0;
width: 220px;
height: 100vh;
background: var(--nav-bg);
color: var(--nav-fg);
padding: 24px 0;
overflow-y: auto;
z-index: 100;
display: flex;
flex-direction: column;
}
nav .pr-label {
padding: 0 16px 8px;
font-family: 'IBM Plex Mono', monospace;
font-size: 11px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.06em;
}
nav .pr-title {
padding: 0 16px 20px;
font-size: 13px;
font-weight: 600;
border-bottom: 1px solid var(--border);
line-height: 1.4;
}
nav ul { list-style: none; padding: 12px 0; flex: 1; }
nav li { position: relative; }
nav a {
display: block;
padding: 7px 16px 7px 20px;
color: var(--nav-fg);
text-decoration: none;
font-size: 13px;
transition: background 0.15s, color 0.15s;
}
nav a:hover { background: rgba(255,255,255,0.05); }
nav a.active { color: var(--nav-active); background: rgba(96,165,250,0.08); }
nav a.active::before {
content: '';
position: absolute;
left: 0; top: 4px; bottom: 4px;
width: 3px;
background: var(--nav-active);
border-radius: 0 2px 2px 0;
}
nav .sub-items { padding-left: 12px; }
nav .sub-items a {
font-size: 12px;
color: var(--muted);
padding: 3px 16px 3px 20px;
}
nav .kbd-help {
padding: 16px;
border-top: 1px solid var(--border);
font-size: 11px;
color: var(--muted);
line-height: 1.9;
}
kbd {
background: var(--code-bg);
border: 1px solid var(--border);
border-radius: 3px;
padding: 1px 5px;
font-size: 11px;
font-family: 'IBM Plex Mono', monospace;
}
/* --- Main content --- */
main {
margin-left: 220px;
padding: 32px 48px;
max-width: 860px;
}
section {
margin-bottom: 48px;
scroll-margin-top: 24px;
}
h1 { font-size: 24px; font-weight: 700; margin-bottom: 8px; text-wrap: balance; }
h2 {
font-size: 17px; font-weight: 600; margin-bottom: 16px;
padding-bottom: 6px; border-bottom: 2px solid var(--accent);
display: inline-block;
}
h3 { font-size: 15px; font-weight: 600; margin: 20px 0 10px; }
p { margin-bottom: 12px; }
.summary-box {
background: var(--accent-soft);
border-left: 4px solid var(--accent);
padding: 14px 18px;
margin-bottom: 20px;
border-radius: 0 6px 6px 0;
font-size: 14px;
}
/* --- Expand/collapse panels --- */
.panel {
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 6px;
margin-bottom: 12px;
overflow: hidden;
}
.panel-header {
padding: 10px 14px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
user-select: none;
font-size: 14px;
}
.panel-header:hover { background: rgba(0,0,0,0.02); }
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) .panel-header:hover { background: rgba(255,255,255,0.03); }
}
:root[data-theme="dark"] .panel-header:hover { background: rgba(255,255,255,0.03); }
.panel-header .arrow {
transition: transform 0.2s;
font-size: 10px;
color: var(--muted);
flex-shrink: 0;
}
.panel.open .arrow { transform: rotate(90deg); }
.panel-body {
display: none;
padding: 0 14px 14px;
border-top: 1px solid var(--border);
}
.panel.open .panel-body { display: block; padding-top: 14px; }
.badge {
display: inline-block;
padding: 1px 7px;
border-radius: 3px;
font-family: 'IBM Plex Mono', monospace;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
flex-shrink: 0;
}
.badge-removed { background: var(--removed-bg); color: var(--removed-fg); }
.badge-added { background: var(--added-bg); color: var(--added-fg); }
.badge-changed { background: var(--accent-soft); color: var(--accent); }
.sig {
font-family: 'IBM Plex Mono', monospace;
font-size: 13px;
min-width: 0;
}
/* --- Before / After --- */
.before-after {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin: 12px 0;
}
.before-after > div { border-radius: 5px; overflow: hidden; min-width: 0; }
.before-after label {
display: block;
padding: 3px 10px;
font-family: 'IBM Plex Mono', monospace;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.before-after .before label { background: var(--removed-bg); color: var(--removed-fg); }
.before-after .after label { background: var(--added-bg); color: var(--added-fg); }
pre {
background: var(--code-bg);
padding: 10px 14px;
border-radius: 5px;
overflow-x: auto;
font-family: 'IBM Plex Mono', monospace;
font-size: 12px;
line-height: 1.5;
}
.before-after pre { border-radius: 0 0 5px 5px; margin: 0; }
code {
font-family: 'IBM Plex Mono', monospace;
font-size: 12px;
background: var(--code-bg);
padding: 1px 4px;
border-radius: 3px;
}
/* --- File map --- */
.file-group-label { font-weight: 600; font-size: 13px; }
.file-count { font-size: 12px; color: var(--muted); }
.file-list {
list-style: none;
padding-left: 18px;
}
.file-list li {
padding: 3px 0;
font-family: 'IBM Plex Mono', monospace;
font-size: 12px;
color: var(--muted);
}
.file-list .stat { font-size: 11px; margin-left: 6px; }
.file-list .stat-add { color: var(--added-fg); }
.file-list .stat-del { color: var(--removed-fg); }
/* --- Stats row --- */
.stats {
display: flex;
gap: 16px;
font-size: 13px;
color: var(--muted);
margin-bottom: 4px;
}
/* --- Diagram --- */
.diagram-container {
margin: 16px 0;
padding: 20px;
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 6px;
overflow-x: auto;
}
.diagram-container svg { display: block; margin: 0 auto; }
svg text { font-family: 'Inter', sans-serif; fill: var(--fg); }
ul.migration-list { margin: 8px 0 8px 20px; }
ul.migration-list li { margin-bottom: 6px; }
/* --- Mobile --- */
@media (max-width: 768px) {
nav {
position: static;
width: 100%;
height: auto;
padding: 12px 16px;
}
nav ul { display: flex; flex-wrap: wrap; gap: 2px; padding: 8px 0; }
nav li { flex: none; }
nav a { padding: 5px 10px; font-size: 12px; border-radius: 4px; }
nav a.active::before { display: none; }
nav .sub-items { display: none; }
nav .kbd-help { display: none; }
main { margin-left: 0; padding: 16px; }
.before-after { grid-template-columns: 1fr; }
}
</style>
<nav>
<div class="pr-label">ponylang/ponyc</div>
<div class="pr-title">#6188 &mdash; Reject runtime-reserved signals</div>
<ul>
<li><a href="#summary" class="active">Summary</a></li>
<li><a href="#api">API Changes</a>
<div class="sub-items">
<a href="#api-removed">Removed</a>
<a href="#api-changed">Changed</a>
</div>
</li>
<li><a href="#behavior">Behavioral Changes</a></li>
<li><a href="#migration">Migration</a></li>
<li><a href="#architecture">Architecture</a></li>
<li><a href="#files">File Map</a></li>
</ul>
<div class="kbd-help">
<kbd>j</kbd> / <kbd>k</kbd> next / prev section<br>
<kbd>Enter</kbd> expand focused item<br>
<kbd>Esc</kbd> collapse all
</div>
</nav>
<main>
<section id="summary">
<h1>Reject runtime-reserved signals in HandleableSignalValidator</h1>
<div class="summary-box">
<code>HandleableSignalValidator</code> now rejects signals the runtime reserves for tracing.
Programs that registered a <code>SignalHandler</code> for SIGINFO (BSD/macOS) or real-time signals
32&ndash;35 (Linux) will get a <code>ValidationFailure</code> instead of silently replacing the runtime's handler.
</div>
<div class="stats">
<span>3 files changed</span>
<span style="color:var(--added-fg)">+50</span>
<span style="color:var(--removed-fg)">&minus;81</span>
<span class="badge badge-changed">changed</span>
</div>
</section>
<section id="api">
<h2>API Changes</h2>
<div id="api-removed" class="panel">
<div class="panel-header" onclick="this.parentElement.classList.toggle('open')">
<span class="arrow">&#9654;</span>
<span class="badge badge-removed">Removed</span>
<span class="sig">Sig.info() from handleable whitelist (BSD/macOS)</span>
</div>
<div class="panel-body">
<p>
<code>Sig.info()</code> was accepted by <code>HandleableSignalValidator</code> on BSD
and macOS. The runtime uses SIGINFO for its tracing pause mechanism, so user handlers
silently replaced the runtime's.
</p>
<div class="before-after">
<div class="before">
<label>Before</label>
<pre>_assert_valid(h, Sig.prof())
_assert_valid(h, Sig.winch())
_assert_valid(h, Sig.info()) // accepted
_assert_valid(h, Sig.usr1())</pre>
</div>
<div class="after">
<label>After</label>
<pre>_assert_valid(h, Sig.prof())
_assert_valid(h, Sig.winch())
// gone
_assert_valid(h, Sig.usr1())</pre>
</div>
</div>
</div>
</div>
<div id="api-changed" class="panel">
<div class="panel-header" onclick="this.parentElement.classList.toggle('open')">
<span class="arrow">&#9654;</span>
<span class="badge badge-changed">Changed</span>
<span class="sig">Sig.rt(n) &mdash; real-time signals 0&ndash;3 now rejected on Linux</span>
</div>
<div class="panel-body">
<p>
The first four real-time signals (<code>Sig.rt(0)</code> through <code>Sig.rt(3)</code>) are
now rejected. The C library reserves 32&ndash;33 and the Pony runtime reserves 34&ndash;35 for tracing.
The handleable range starts at <code>Sig.rt(4)</code>.
</p>
<div class="before-after">
<div class="before">
<label>Before</label>
<pre>// Linux real-time range started at offset 0
let rt_min: U32 = 32
// Sig.rt(0)? through Sig.rt(31)? all accepted</pre>
</div>
<div class="after">
<label>After</label>
<pre>// Linux real-time range starts at offset 4
let rt_min: U32 = 36
// Sig.rt(0)? through Sig.rt(3)? rejected
// Sig.rt(4)? and above accepted</pre>
</div>
</div>
</div>
</div>
</section>
<section id="behavior">
<h2>Behavioral Changes</h2>
<h3>What happens differently</h3>
<p>
Before: calling <code>MakeHandleableSignal</code> with a runtime-reserved signal succeeded,
returning a <code>HandleableSignal</code>. The user's handler silently replaced the runtime's
tracing mechanism. No error, no warning&nbsp;&mdash; the tracing thread-pause just stopped working.
</p>
<p>
After: the same call returns <code>ValidationFailure</code>. The runtime's handler stays intact.
</p>
<h3>Why now</h3>
<p>
PR #6182 removed the <code>runtime_tracing</code> build option&nbsp;&mdash; tracing is now always compiled in.
Before that, the reservation only mattered for builds that opted into tracing. Now it's unconditional,
so the validator must enforce it unconditionally.
</p>
</section>
<section id="migration">
<h2>Migration</h2>
<p>If your code registers a <code>SignalHandler</code> for any of these signals, it will now
get a <code>ValidationFailure</code> from <code>MakeHandleableSignal</code>:</p>
<ul class="migration-list">
<li><strong>BSD/macOS:</strong> <code>Sig.info()</code></li>
<li><strong>Linux:</strong> <code>Sig.rt(0)?</code> through <code>Sig.rt(3)?</code></li>
</ul>
<p>
If your <code>match</code> on the result of <code>MakeHandleableSignal</code> is exhaustive,
you already handle <code>ValidationFailure</code>&nbsp;&mdash; these signals will take that path now.
If you were only matching <code>HandleableSignal</code>, the compiler will tell you.
</p>
</section>
<section id="architecture">
<h2>Architecture</h2>
<div class="diagram-container">
<svg width="600" height="280" viewBox="0 0 600 280">
<rect x="200" y="10" width="200" height="40" rx="6" fill="var(--diagram-node)" stroke="var(--diagram-edge)" stroke-width="1.5"/>
<text x="300" y="35" text-anchor="middle" font-size="13" font-weight="600">MakeHandleableSignal</text>
<line x1="300" y1="50" x2="300" y2="80" stroke="var(--diagram-edge)" stroke-width="1.5" marker-end="url(#ah)"/>
<rect x="170" y="80" width="260" height="40" rx="6" fill="var(--diagram-highlight)" stroke="var(--diagram-edge)" stroke-width="2"/>
<text x="300" y="105" text-anchor="middle" font-size="13" font-weight="700">HandleableSignalValidator</text>
<text x="300" y="135" text-anchor="middle" font-size="11" fill="var(--muted)">this is what changed</text>
<line x1="220" y1="120" x2="100" y2="170" stroke="var(--diagram-edge)" stroke-width="1.5" marker-end="url(#ah)"/>
<line x1="300" y1="120" x2="300" y2="170" stroke="var(--diagram-edge)" stroke-width="1.5" marker-end="url(#ah)"/>
<line x1="380" y1="120" x2="500" y2="170" stroke="var(--diagram-edge)" stroke-width="1.5" marker-end="url(#ah)"/>
<rect x="30" y="170" width="140" height="36" rx="6" fill="var(--diagram-node)" stroke="var(--diagram-edge)" stroke-width="1.5"/>
<text x="100" y="193" text-anchor="middle" font-size="12">Reject fatal</text>
<rect x="230" y="170" width="140" height="36" rx="6" fill="var(--diagram-node)" stroke="var(--diagram-edge)" stroke-width="1.5"/>
<text x="300" y="193" text-anchor="middle" font-size="12">Reject uncatchable</text>
<rect x="430" y="170" width="150" height="36" rx="6" fill="var(--diagram-highlight)" stroke="var(--diagram-edge)" stroke-width="2"/>
<text x="505" y="193" text-anchor="middle" font-size="12" font-weight="600">Reject reserved</text>
<text x="505" y="207" text-anchor="middle" font-size="10" fill="var(--muted)">(new)</text>
<text x="100" y="228" text-anchor="middle" font-size="10" fill="var(--muted)">SIGKILL, SIGSTOP</text>
<text x="300" y="228" text-anchor="middle" font-size="10" fill="var(--muted)">SIGCONT, SIGCHLD</text>
<text x="505" y="228" text-anchor="middle" font-size="10" fill="var(--muted)">SIGINFO, rt(0-3)</text>
<line x1="100" y1="238" x2="300" y2="263" stroke="var(--diagram-edge)" stroke-width="1" stroke-dasharray="4"/>
<line x1="300" y1="238" x2="300" y2="263" stroke="var(--diagram-edge)" stroke-width="1" stroke-dasharray="4"/>
<line x1="505" y1="238" x2="300" y2="263" stroke="var(--diagram-edge)" stroke-width="1" stroke-dasharray="4"/>
<text x="300" y="276" text-anchor="middle" font-size="12" fill="var(--removed-fg)" font-weight="600">ValidationFailure</text>
<defs>
<marker id="ah" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="var(--diagram-edge)"/>
</marker>
</defs>
</svg>
</div>
</section>
<section id="files">
<h2>File Map</h2>
<div class="panel open">
<div class="panel-header" onclick="this.parentElement.classList.toggle('open')">
<span class="arrow">&#9654;</span>
<span class="file-group-label">Core change</span>
<span class="file-count">1 file</span>
</div>
<div class="panel-body">
<ul class="file-list">
<li>
packages/signals/handleable_signal.pony
<span class="stat"><span class="stat-add">+18</span> <span class="stat-del">&minus;49</span></span>
</li>
</ul>
</div>
</div>
<div class="panel">
<div class="panel-header" onclick="this.parentElement.classList.toggle('open')">
<span class="arrow">&#9654;</span>
<span class="file-group-label">Tests</span>
<span class="file-count">1 file</span>
</div>
<div class="panel-body">
<ul class="file-list">
<li>
packages/signals/_test.pony
<span class="stat"><span class="stat-add">+27</span> <span class="stat-del">&minus;27</span></span>
</li>
</ul>
</div>
</div>
<div class="panel">
<div class="panel-header" onclick="this.parentElement.classList.toggle('open')">
<span class="arrow">&#9654;</span>
<span class="file-group-label">Release notes</span>
<span class="file-count">1 file</span>
</div>
<div class="panel-body">
<ul class="file-list">
<li>
.release-notes/reject-runtime-reserved-signals.md
<span class="stat"><span class="stat-add">+5</span></span>
</li>
</ul>
</div>
</div>
</section>
</main>
<script>
const sections = document.querySelectorAll('section');
const navLinks = document.querySelectorAll('nav a[href^="#"]');
let currentSection = 0;
function updateActiveNav() {
navLinks.forEach(a => a.classList.remove('active'));
const id = sections[currentSection]?.id;
if (id) {
const link = document.querySelector('nav a[href="#' + id + '"]');
if (link) link.classList.add('active');
}
}
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const idx = Array.from(sections).indexOf(entry.target);
if (idx >= 0) { currentSection = idx; updateActiveNav(); }
}
});
}, { rootMargin: '-20% 0px -60% 0px' });
sections.forEach(s => observer.observe(s));
document.addEventListener('keydown', e => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
if (e.key === 'j' || e.key === 'J') {
e.preventDefault();
currentSection = Math.min(currentSection + 1, sections.length - 1);
sections[currentSection].scrollIntoView({ behavior: 'smooth' });
updateActiveNav();
} else if (e.key === 'k' || e.key === 'K') {
e.preventDefault();
currentSection = Math.max(currentSection - 1, 0);
sections[currentSection].scrollIntoView({ behavior: 'smooth' });
updateActiveNav();
} else if (e.key === 'Enter') {
e.preventDefault();
sections[currentSection].querySelectorAll('.panel:not(.open)').forEach(el => el.classList.add('open'));
} else if (e.key === 'Escape') {
document.querySelectorAll('.panel.open').forEach(el => el.classList.remove('open'));
}
});
navLinks.forEach(a => {
a.addEventListener('click', e => {
e.preventDefault();
const target = document.querySelector(a.getAttribute('href'));
if (target) {
target.scrollIntoView({ behavior: 'smooth' });
const idx = Array.from(sections).indexOf(target);
if (idx >= 0) { currentSection = idx; updateActiveNav(); }
}
});
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment