Skip to content

Instantly share code, notes, and snippets.

@bojanrajkovic
Last active March 9, 2026 19:37
Show Gist options
  • Select an option

  • Save bojanrajkovic/813a06309e80d9e445ab47ec1d8e12dc to your computer and use it in GitHub Desktop.

Select an option

Save bojanrajkovic/813a06309e80d9e445ab47ec1d8e12dc to your computer and use it in GitHub Desktop.
Loupe: Rendered-Mode Comment Re-Anchoring — Four Anchor States Prototype
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Loupe — Rendered-Mode Re-Anchoring States</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0e0e10;
--panel-bg: #131316;
--surface: #1c1c22;
--surface-hover: #202028;
--border: rgba(255, 255, 255, 0.08);
--border-subtle: rgba(255, 255, 255, 0.05);
--text: #e8e6f0;
--text-muted: #7e7a96;
--text-dim: #4e4a66;
/* document text */
--doc-text: #d4d2e0;
--doc-text-muted: #7e7a96;
--doc-heading: #eceaf4;
--doc-link: #93c5fd;
--doc-code-bg: #0f0f12;
--doc-code-text: #a8b4c8;
--doc-blockquote: rgba(255, 255, 255, 0.05);
/* state accent colors — severity gradient */
--anchored: #34d399;
--anchored-dim: rgba(52, 211, 153, 0.10);
--shifted: #60a5fa;
--shifted-dim: rgba(96, 165, 250, 0.12);
--outdated: #f59e0b;
--outdated-dim: rgba(245, 158, 11, 0.12);
--orphaned: #dc2626;
--orphaned-dim: rgba(220, 38, 38, 0.12);
/* text highlight in anchored state */
--highlight-bg: rgba(250, 204, 21, 0.14);
--highlight-border: rgba(250, 204, 21, 0.35);
--radius-sm: 4px;
--radius: 6px;
--radius-lg: 10px;
--mono: 'JetBrains Mono', 'Cascadia Code', 'Fira Code', ui-monospace, 'Courier New', monospace;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
line-height: 1.5;
}
/* ── Layout ── */
.app {
max-width: 960px;
margin: 0 auto;
padding: 32px 24px 64px;
}
/* ── Header ── */
.header {
margin-bottom: 32px;
}
.header-eyebrow {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
color: var(--text-muted);
margin-bottom: 10px;
}
.header h1 {
font-size: 26px;
font-weight: 700;
color: var(--text);
letter-spacing: -0.02em;
margin-bottom: 6px;
}
.header p {
font-size: 14px;
color: var(--text-muted);
max-width: 680px;
line-height: 1.7;
}
/* ── Tab Navigation ── */
.tabs {
display: flex;
gap: 4px;
margin-bottom: 28px;
background: var(--panel-bg);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 5px;
}
.tab {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 9px 16px;
border-radius: var(--radius);
border: none;
background: none;
cursor: pointer;
font-size: 13px;
font-weight: 500;
color: var(--text-muted);
transition: background 0.18s ease, color 0.18s ease;
white-space: nowrap;
}
.tab:hover {
background: var(--surface-hover);
color: var(--text);
}
.tab.active {
background: var(--surface);
color: var(--text);
}
.tab.active.tab-anchored { background: var(--anchored-dim); color: var(--anchored); }
.tab.active.tab-shifted { background: var(--shifted-dim); color: var(--shifted); }
.tab.active.tab-outdated { background: var(--outdated-dim); color: var(--outdated); }
.tab.active.tab-orphaned { background: var(--orphaned-dim); color: var(--orphaned); }
/* ── State Panels ── */
.state-panel { display: none; }
.state-panel.active { display: block; }
/* ── State Info Caption ── */
.state-info {
padding: 6px 0;
margin-bottom: 16px;
font-size: 12px;
color: var(--text-muted);
line-height: 1.5;
}
/* ── Review Layout: document + margin ── */
.review-layout {
display: grid;
grid-template-columns: 1fr 300px;
gap: 0;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
overflow: hidden;
background: var(--panel-bg);
align-items: start;
}
/* ── Document Area ── */
.document-area {
padding: 40px 48px 48px;
border-right: 1px solid var(--border);
min-height: 600px;
overflow: hidden;
}
/* ── Document Typography ── */
.doc-title {
font-size: 28px;
font-weight: 700;
color: var(--doc-heading);
letter-spacing: -0.02em;
line-height: 1.25;
margin-bottom: 8px;
}
.doc-byline {
font-size: 13px;
color: var(--text-dim);
margin-bottom: 36px;
padding-bottom: 24px;
border-bottom: 1px solid var(--border-subtle);
}
.doc-h2 {
font-size: 20px;
font-weight: 700;
color: var(--doc-heading);
letter-spacing: -0.01em;
line-height: 1.3;
margin-top: 36px;
margin-bottom: 12px;
}
.doc-h3 {
font-size: 15px;
font-weight: 600;
color: var(--doc-heading);
letter-spacing: 0;
line-height: 1.4;
margin-top: 24px;
margin-bottom: 8px;
}
.doc-p {
font-size: 15px;
color: var(--doc-text);
line-height: 1.7;
margin-bottom: 16px;
}
.doc-p:last-child { margin-bottom: 0; }
.doc-a {
color: var(--doc-link);
text-decoration: none;
}
.doc-a:hover { text-decoration: underline; }
.doc-code {
font-family: var(--mono);
font-size: 12.5px;
background: rgba(255, 255, 255, 0.07);
color: var(--doc-code-text);
padding: 1px 5px;
border-radius: 3px;
}
.doc-pre {
background: var(--doc-code-bg);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 16px 20px;
margin: 16px 0;
overflow-x: auto;
}
.doc-pre code {
font-family: var(--mono);
font-size: 12.5px;
line-height: 1.65;
color: #a8b4c8;
}
.doc-pre .kw { color: #c792ea; }
.doc-pre .fn { color: #82aaff; }
.doc-pre .str { color: #c3e88d; }
.doc-pre .cm { color: #546e7a; font-style: italic; }
.doc-pre .num { color: #f78c6c; }
.doc-pre .ty { color: #ffcb6b; }
.doc-ul {
padding-left: 24px;
margin-bottom: 16px;
}
.doc-ul li {
font-size: 15px;
color: var(--doc-text);
line-height: 1.7;
margin-bottom: 4px;
}
.doc-ul li::marker { color: var(--text-dim); }
.doc-ol {
padding-left: 24px;
margin-bottom: 16px;
}
.doc-ol li {
font-size: 15px;
color: var(--doc-text);
line-height: 1.7;
margin-bottom: 4px;
}
.doc-ol li::marker { color: var(--text-dim); }
.doc-blockquote {
border-left: 3px solid var(--border);
background: var(--doc-blockquote);
margin: 16px 0;
padding: 12px 20px;
border-radius: 0 var(--radius) var(--radius) 0;
}
.doc-blockquote p {
font-size: 14px;
color: var(--text-muted);
line-height: 1.65;
font-style: italic;
margin: 0;
}
/* ── Text Highlight (annotation) ── */
.text-highlight {
background: var(--highlight-bg);
border-bottom: 1.5px solid var(--highlight-border);
border-radius: 2px;
cursor: pointer;
transition: background 0.15s ease;
padding: 0 1px;
}
.text-highlight:hover {
background: rgba(250, 204, 21, 0.22);
}
.text-highlight.active-highlight {
background: rgba(250, 204, 21, 0.24);
border-bottom-color: rgba(250, 204, 21, 0.6);
}
/* Shifted highlight */
.text-highlight-shifted {
background: rgba(96, 165, 250, 0.12);
border-bottom: 1.5px solid rgba(96, 165, 250, 0.35);
border-radius: 2px;
cursor: pointer;
transition: background 0.15s ease;
padding: 0 1px;
}
.text-highlight-shifted:hover {
background: rgba(96, 165, 250, 0.2);
}
/* Ghost passage (where comment used to be in Shifted) */
/* Displayed as a block band between heading and paragraph */
.ghost-passage {
display: block;
border-left: 2px dashed rgba(96, 165, 250, 0.35);
padding: 5px 10px;
margin-bottom: 10px;
color: rgba(96, 165, 250, 0.45);
font-size: 11.5px;
font-style: italic;
cursor: default;
pointer-events: none;
}
/* Outdated — thin amber underline as click target only */
.text-highlight-outdated {
border-bottom: 1.5px solid rgba(245, 158, 11, 0.45);
border-radius: 1px;
cursor: pointer;
padding: 0 1px;
transition: background 0.15s ease;
}
.text-highlight-outdated:hover {
background: rgba(245, 158, 11, 0.08);
}
/* ── Margin (sidebar) ── */
.margin-area {
padding: 40px 0 48px;
display: flex;
flex-direction: column;
align-items: stretch;
}
/* ── Margin Comment ── */
.margin-comment {
padding: 14px 16px;
border-bottom: 1px solid var(--border-subtle);
position: relative;
}
.margin-comment:first-child { border-top: none; }
.margin-comment:last-child { border-bottom: none; }
/* Connector line — visual association from comment to highlight */
.margin-comment::before {
content: '';
position: absolute;
left: 0;
top: 20px;
width: 3px;
height: 28px;
background: rgba(250, 204, 21, 0.3);
border-radius: 0 2px 2px 0;
}
.margin-comment.state-shifted::before { background: rgba(96, 165, 250, 0.4); }
.margin-comment.state-outdated::before { background: rgba(245, 158, 11, 0.4); }
.margin-comment.state-orphaned::before { background: rgba(220, 38, 38, 0.4); }
.margin-comment-header {
display: flex;
align-items: flex-start;
gap: 8px;
margin-bottom: 8px;
}
.margin-avatar {
width: 24px;
height: 24px;
border-radius: 50%;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: 700;
color: #fff;
background: #3a3a4a;
margin-top: 1px;
}
.margin-meta {
flex: 1;
min-width: 0;
}
.margin-author {
font-size: 12px;
font-weight: 600;
color: var(--text);
}
.margin-time {
font-size: 11px;
color: var(--text-muted);
margin-left: 6px;
}
.margin-body {
font-size: 12.5px;
color: var(--doc-text);
line-height: 1.6;
padding-left: 32px;
}
.margin-body code {
font-family: var(--mono);
font-size: 11px;
background: rgba(255, 255, 255, 0.07);
color: var(--doc-code-text);
padding: 1px 4px;
border-radius: 3px;
}
/* Quoted text in margin */
.margin-quote {
font-size: 11px;
color: var(--text-dim);
font-style: italic;
border-left: 2px solid var(--border);
padding: 2px 8px 2px 10px;
margin-bottom: 8px;
margin-left: 32px;
line-height: 1.5;
}
.margin-quote mark {
background: none;
color: var(--text-muted);
font-style: normal;
}
/* ── Anchored check microtext ── */
.anchored-check {
font-size: 10px;
color: var(--anchored);
font-weight: 500;
margin-left: 4px;
}
/* ── Badges ── */
.badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 7px;
border-radius: 20px;
font-size: 10.5px;
font-weight: 600;
line-height: 1.5;
white-space: nowrap;
}
.badge-shifted { background: rgba(96, 165, 250, 0.14); color: var(--shifted); border: 1px solid rgba(96, 165, 250, 0.25); }
.badge-outdated { background: rgba(245, 158, 11, 0.14); color: var(--outdated); border: 1px solid rgba(245, 158, 11, 0.25); }
.badge-orphaned { background: rgba(220, 38, 38, 0.14); color: var(--orphaned); border: 1px solid rgba(220, 38, 38, 0.25); }
/* ── Breadcrumb trail ── */
.breadcrumb {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 4px;
font-size: 10.5px;
color: var(--text-dim);
margin-top: 4px;
padding-left: 32px;
line-height: 1.6;
}
.breadcrumb-sep {
color: var(--text-dim);
opacity: 0.5;
}
.breadcrumb-section {
font-weight: 500;
color: var(--text-muted);
}
.breadcrumb-arrow {
color: var(--text-dim);
opacity: 0.6;
margin: 0 1px;
}
/* ── Collapsible original text block ── */
.collapsible-toggle {
display: flex;
align-items: center;
gap: 5px;
font-size: 11px;
font-weight: 500;
color: var(--text-muted);
cursor: pointer;
padding-left: 32px;
margin-top: 8px;
user-select: none;
background: none;
border: none;
text-align: left;
width: 100%;
transition: color 0.15s ease;
}
.collapsible-toggle:hover { color: var(--text); }
.collapsible-toggle .chev {
transition: transform 0.2s ease;
color: var(--text-dim);
flex-shrink: 0;
}
.collapsible-toggle .chev.open { transform: rotate(180deg); }
.collapsible-body {
overflow: hidden;
max-height: 0;
transition: max-height 0.3s ease;
}
.collapsible-body.expanded { max-height: 300px; }
.collapsible-inner {
padding: 8px 14px 8px 32px;
margin-top: 6px;
}
.original-text-block {
background: rgba(245, 158, 11, 0.04);
border: 1px solid rgba(245, 158, 11, 0.18);
border-radius: var(--radius);
padding: 10px 12px;
font-size: 11.5px;
font-style: italic;
color: #c4a84f;
line-height: 1.6;
font-family: inherit;
}
.original-text-block.orphaned-text {
background: rgba(220, 38, 38, 0.04);
border-color: rgba(220, 38, 38, 0.18);
color: #b07070;
}
.original-text-label {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
color: var(--outdated);
opacity: 0.7;
margin-bottom: 6px;
}
.original-text-label.orphaned-label { color: var(--orphaned); }
/* Outdated diff display */
.outdated-diff {
margin-top: 10px;
padding-left: 32px;
}
.outdated-diff-row {
display: flex;
align-items: baseline;
gap: 6px;
font-size: 11.5px;
line-height: 1.65;
padding: 1px 0;
}
.diff-sign-del {
color: #f87171;
font-weight: 700;
flex-shrink: 0;
width: 10px;
font-family: var(--mono);
}
.diff-sign-add {
color: #4ade80;
font-weight: 700;
flex-shrink: 0;
width: 10px;
font-family: var(--mono);
}
.diff-text-del {
color: #c4a0a0;
text-decoration: line-through;
text-decoration-color: rgba(248, 113, 113, 0.6);
font-size: 11px;
font-style: italic;
flex: 1;
}
.diff-text-add {
color: #a3d9a5;
flex: 1;
font-style: italic;
font-size: 11px;
}
/* ── Orphaned banner ── */
.orphaned-banner {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 16px;
background: rgba(220, 38, 38, 0.05);
border-bottom: 1px solid rgba(220, 38, 38, 0.15);
font-size: 12px;
color: var(--text-muted);
}
.orphaned-banner-icon { color: var(--orphaned); flex-shrink: 0; }
.orphaned-banner strong { color: var(--orphaned); font-weight: 600; }
/* ── Orphaned panel layout — full width ── */
.orphaned-layout {
border: 1px solid var(--border);
border-radius: var(--radius-lg);
overflow: hidden;
background: var(--panel-bg);
}
.orphaned-comments-panel {
border-bottom: 1px solid rgba(220, 38, 38, 0.15);
}
.orphaned-comments-header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 20px;
background: rgba(220, 38, 38, 0.04);
border-bottom: 1px solid rgba(220, 38, 38, 0.12);
}
.orphaned-comments-title {
font-size: 12px;
font-weight: 600;
color: var(--orphaned);
}
.orphaned-comments-desc {
font-size: 11.5px;
color: var(--text-muted);
margin-left: auto;
}
.orphaned-comment-card {
padding: 16px 20px;
border-bottom: 1px solid var(--border-subtle);
}
.orphaned-comment-card:last-child { border-bottom: none; }
.orphaned-card-meta {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.orphaned-card-breadcrumb {
font-size: 11px;
color: var(--text-dim);
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 4px;
flex-wrap: wrap;
padding-left: 32px;
}
.orphaned-card-body {
font-size: 13px;
color: var(--doc-text);
line-height: 1.65;
padding-left: 32px;
margin-bottom: 10px;
}
/* Document for orphaned tab shows no trace of the deleted section */
.orphaned-doc-layout {
display: grid;
grid-template-columns: 1fr 300px;
gap: 0;
border-top: 1px solid var(--border);
align-items: start;
}
.orphaned-doc-area {
padding: 40px 48px 48px;
border-right: 1px solid var(--border);
}
.orphaned-sidebar-placeholder {
padding: 24px 16px;
border-left: 1px solid var(--border-subtle);
}
.orphaned-sidebar-placeholder p {
font-size: 11.5px;
color: var(--text-dim);
font-style: italic;
line-height: 1.6;
}
/* ── Keyboard hint ── */
.keyboard-hint {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
margin-top: 28px;
font-size: 11.5px;
color: var(--text-dim);
}
kbd {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 22px;
height: 22px;
padding: 0 6px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 4px;
font-size: 11px;
color: var(--text-muted);
box-shadow: 0 1px 0 var(--border);
}
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(180,180,190,0.18); border-radius: 3px; }
</style>
</head>
<body>
<div class="app">
<!-- ── Header ── -->
<div class="header">
<div class="header-eyebrow">
Loupe &mdash; Prototype
</div>
<h1>Rendered-Mode Re-Anchoring States</h1>
<p>In rendered-mode, comments are text annotations anchored to highlighted passages using a TextQuoteSelector — not line numbers. As the document evolves, each comment lands in one of four states depending on whether its passage is still present, moved, changed, or gone.</p>
</div>
<!-- ── Tabs ── -->
<div class="tabs" role="tablist" aria-label="Anchor states">
<button class="tab tab-anchored active" role="tab" aria-selected="true" data-state="anchored" onclick="switchTab('anchored')">Anchored</button>
<button class="tab tab-shifted" role="tab" aria-selected="false" data-state="shifted" onclick="switchTab('shifted')">Shifted</button>
<button class="tab tab-outdated" role="tab" aria-selected="false" data-state="outdated" onclick="switchTab('outdated')">Outdated</button>
<button class="tab tab-orphaned" role="tab" aria-selected="false" data-state="orphaned" onclick="switchTab('orphaned')">Orphaned</button>
</div>
<!-- ════════════════════════════════════════════
STATE 1 — ANCHORED
════════════════════════════════════════════ -->
<div class="state-panel active" id="panel-anchored">
<div class="state-info">Passage found at its original location — no action needed.</div>
<div class="review-layout">
<!-- Document -->
<div class="document-area" id="doc-anchored">
<div class="doc-title">Contributing to Meridian</div>
<div class="doc-byline">Last updated March 2026 &mdash; Meridian Core Team</div>
<div class="doc-h2">Getting Started</div>
<p class="doc-p">Meridian is an open-source data pipeline toolkit written in Go and TypeScript. Before submitting your first pull request, take a few minutes to read through this guide — it will save you and the reviewers significant back-and-forth.</p>
<div class="doc-h3">Prerequisites</div>
<p class="doc-p">You need Go 1.22 or later, Node 20+, and <span class="doc-code">pnpm</span> installed. The test suite requires a running PostgreSQL 15 instance; the easiest path is Docker Compose:</p>
<div class="doc-pre"><code><span class="cm"># Start services</span>
<span class="fn">docker</span> compose up -d postgres redis
<span class="fn">go</span> test ./...</code></div>
<p class="doc-p">If you encounter connection errors during tests, verify that <span class="doc-code">DATABASE_URL</span> is exported in your shell and matches the credentials in <span class="doc-code">docker-compose.yml</span>.</p>
<div class="doc-h2">Workflow</div>
<div class="doc-h3">Branching</div>
<p class="doc-p">Create a feature branch from <span class="doc-code">main</span>. Use the naming pattern <span class="doc-code">type/short-description</span> where type is one of <span class="doc-code">feat</span>, <span class="doc-code">fix</span>, <span class="doc-code">docs</span>, or <span class="doc-code">refactor</span>. Branches prefixed with <span class="doc-code">wip/</span> are ignored by CI.</p>
<div class="doc-h3">Commit Messages</div>
<p class="doc-p"><span class="text-highlight" id="hl-anchored" onclick="focusComment('mc-anchored')" title="Click to focus comment">We follow the Conventional Commits specification. Each commit message must have a type, an optional scope in parentheses, and a short imperative-mood description — no trailing period. The body, separated by a blank line, explains the <em>why</em> rather than the <em>what</em>.</span></p>
<div class="doc-blockquote"><p>Good: <strong>feat(pipeline): add retry logic for transient errors</strong><br>Bad: <strong>fixed things</strong></p></div>
<p class="doc-p">Breaking changes must include a <span class="doc-code">BREAKING CHANGE:</span> footer in the commit body. This triggers a major version bump in the automated release workflow.</p>
<div class="doc-h2">Code Review</div>
<p class="doc-p">All pull requests require at least one approval from a core team member. Reviews are expected to be thorough — plan for a 2–3 day turnaround on most submissions. If your PR has been open for more than five days without feedback, ping the <span class="doc-code">#pr-review</span> Slack channel.</p>
<div class="doc-h3">Keeping Your PR Small</div>
<p class="doc-p">PRs under 400 lines of diff get reviewed faster. If your change is larger, consider whether it can be split along logical seams — for example, data model changes in one PR and the feature that uses them in a follow-up.</p>
</div>
<!-- Margin -->
<div class="margin-area">
<div class="margin-comment" id="mc-anchored">
<div class="margin-comment-header">
<div class="margin-avatar">SK</div>
<div class="margin-meta">
<span class="margin-author">sasha.k</span>
<span class="margin-time">3 days ago</span>
<span class="anchored-check">&#10003; Anchored</span>
</div>
</div>
<div class="margin-quote">&ldquo;Each commit message must have a type, an optional scope&hellip;&rdquo;</div>
<div class="margin-body">
Worth mentioning the 72-character limit on the subject line here — most teams discover it the hard way when GitHub truncates the title in notifications. Maybe add a concrete example of a good body too, since &ldquo;explains the why&rdquo; is advice people find vague until they see it in practice.
</div>
</div>
</div>
</div>
</div>
<!-- ════════════════════════════════════════════
STATE 2 — SHIFTED
════════════════════════════════════════════ -->
<div class="state-panel" id="panel-shifted">
<div class="state-info">Passage found at a new location — the section was reorganized. Comment follows automatically.</div>
<div class="review-layout">
<!-- Document -->
<div class="document-area" id="doc-shifted">
<div class="doc-title">Contributing to Meridian</div>
<div class="doc-byline">Last updated March 2026 &mdash; Meridian Core Team</div>
<div class="doc-h2">Getting Started</div>
<p class="doc-p">Meridian is an open-source data pipeline toolkit written in Go and TypeScript. Before submitting your first pull request, take a few minutes to read through this guide.</p>
<div class="doc-h3">Prerequisites</div>
<p class="doc-p">You need Go 1.22 or later, Node 20+, and <span class="doc-code">pnpm</span> installed. Start services with Docker Compose and run <span class="doc-code">go test ./...</span>.</p>
<!-- Old section with ghost placeholder -->
<div class="doc-h2">Workflow</div>
<div class="doc-h3">Branching</div>
<p class="doc-p">Create a feature branch from <span class="doc-code">main</span>. Use the naming pattern <span class="doc-code">type/short-description</span> where type is one of <span class="doc-code">feat</span>, <span class="doc-code">fix</span>, <span class="doc-code">docs</span>, or <span class="doc-code">refactor</span>.</p>
<!-- Ghost: comment was originally here under Workflow > Commit Messages -->
<div class="doc-h3">Commit Messages</div>
<span class="ghost-passage">Comment shifted to §Setup &rsaquo; Commit Standards</span>
<p class="doc-p">We follow the Conventional Commits specification. Each commit message must have a type, an optional scope, and a short description.</p>
<div class="doc-h2">Code Review</div>
<p class="doc-p">All pull requests require at least one approval from a core team member. Reviews are expected to be thorough — plan for a 2–3 day turnaround on most submissions.</p>
<!-- Reorganized: the passage now lives under Setup > Commit Standards -->
<div class="doc-h2">Setup</div>
<div class="doc-h3">Environment</div>
<p class="doc-p">Export <span class="doc-code">DATABASE_URL</span> before running tests. Verify it matches the credentials in <span class="doc-code">docker-compose.yml</span>.</p>
<div class="doc-h3">Commit Standards</div>
<p class="doc-p"><span class="text-highlight-shifted" id="hl-shifted" onclick="focusComment('mc-shifted')" title="Click to focus comment">We follow the Conventional Commits specification. Each commit message must have a type, an optional scope in parentheses, and a short imperative-mood description — no trailing period. The body, separated by a blank line, explains the <em>why</em> rather than the <em>what</em>.</span></p>
<div class="doc-blockquote"><p>Good: <strong>feat(pipeline): add retry logic for transient errors</strong></p></div>
<p class="doc-p">Breaking changes must include a <span class="doc-code">BREAKING CHANGE:</span> footer. This triggers a major version bump in the automated release workflow.</p>
</div>
<!-- Margin -->
<div class="margin-area">
<div class="margin-comment state-shifted" id="mc-shifted">
<div class="margin-comment-header">
<div class="margin-avatar">SK</div>
<div class="margin-meta">
<span class="margin-author">sasha.k</span>
<span class="margin-time">3 days ago</span>
</div>
</div>
<div style="padding-left: 32px; margin-bottom: 4px;">
<span class="badge badge-shifted">&#8597; Shifted</span>
</div>
<div class="breadcrumb">
<span class="breadcrumb-section">Workflow</span>
<span class="breadcrumb-sep">&rsaquo;</span>
<span class="breadcrumb-section">Commit Messages</span>
<span class="breadcrumb-arrow">&rarr;</span>
<span class="breadcrumb-section">Setup</span>
<span class="breadcrumb-sep">&rsaquo;</span>
<span class="breadcrumb-section">Commit Standards</span>
</div>
<div class="margin-quote" style="margin-top: 8px;">&ldquo;Each commit message must have a type, an optional scope&hellip;&rdquo;</div>
<div class="margin-body">
Worth mentioning the 72-character limit on the subject line here — most teams discover it the hard way when GitHub truncates the title in notifications.
</div>
</div>
</div>
</div>
</div>
<!-- ════════════════════════════════════════════
STATE 3 — OUTDATED
════════════════════════════════════════════ -->
<div class="state-panel" id="panel-outdated">
<div class="state-info">The commented passage was rewritten — the comment remains visible but refers to old content.</div>
<div class="review-layout">
<!-- Document -->
<div class="document-area" id="doc-outdated">
<div class="doc-title">Contributing to Meridian</div>
<div class="doc-byline">Last updated March 2026 &mdash; Meridian Core Team</div>
<div class="doc-h2">Getting Started</div>
<p class="doc-p">Meridian is an open-source data pipeline toolkit written in Go and TypeScript. Before submitting your first pull request, take a few minutes to read through this guide.</p>
<div class="doc-h3">Prerequisites</div>
<p class="doc-p">You need Go 1.22 or later, Node 20+, and <span class="doc-code">pnpm</span> installed. Start services with Docker Compose and run <span class="doc-code">go test ./...</span>.</p>
<div class="doc-h2">Workflow</div>
<div class="doc-h3">Branching</div>
<p class="doc-p">Create a feature branch from <span class="doc-code">main</span>. Use the naming pattern <span class="doc-code">type/short-description</span>.</p>
<div class="doc-h3">Commit Messages</div>
<!-- The passage has been rewritten — current text only, no old text kept in document -->
<p class="doc-p"><span class="text-highlight-outdated" id="hl-outdated" onclick="focusComment('mc-outdated')" title="Click to view outdated comment">Commit messages follow the Conventional Commits format. The subject line is limited to 72 characters. Use the imperative mood: &ldquo;add retry logic&rdquo; not &ldquo;added retry logic.&rdquo; Include a blank-line-separated body when the change warrants explanation. Breaking changes require a <span class="doc-code">BREAKING CHANGE:</span> footer.</span></p>
<div class="doc-h2">Code Review</div>
<p class="doc-p">All pull requests require at least one approval from a core team member. Reviews are expected to be thorough — plan for a 2–3 day turnaround.</p>
<div class="doc-h3">Keeping Your PR Small</div>
<p class="doc-p">PRs under 400 lines of diff get reviewed faster. If your change is larger, consider whether it can be split along logical seams.</p>
</div>
<!-- Margin -->
<div class="margin-area">
<div class="margin-comment state-outdated" id="mc-outdated">
<div class="margin-comment-header">
<div class="margin-avatar">SK</div>
<div class="margin-meta">
<span class="margin-author">sasha.k</span>
<span class="margin-time">3 days ago</span>
</div>
</div>
<div style="padding-left: 32px; margin-bottom: 8px;">
<span class="badge badge-outdated">&#9888; Outdated</span>
</div>
<div class="margin-body">
Worth mentioning the 72-character limit on the subject line — most teams discover it the hard way. Add a concrete example of a good commit body too.
</div>
<!-- Visual diff between old and new text -->
<div class="outdated-diff">
<div class="outdated-diff-row">
<span class="diff-sign-del">&minus;</span>
<span class="diff-text-del">Each commit message must have a type, an optional scope in parentheses, and a short imperative-mood description&hellip;</span>
</div>
<div class="outdated-diff-row">
<span class="diff-sign-add">+</span>
<span class="diff-text-add">Commit messages follow the Conventional Commits format. The subject line is limited to 72 characters&hellip;</span>
</div>
</div>
<button class="collapsible-toggle" onclick="toggleCollapsible('orig-outdated', this, 'Show original quoted text', 'Hide original quoted text')" aria-expanded="false">
<svg class="chev" width="12" height="12" viewBox="0 0 16 16" fill="none">
<path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Show original quoted text
</button>
<div class="collapsible-body" id="orig-outdated">
<div class="collapsible-inner">
<div class="original-text-label">Original passage</div>
<div class="original-text-block">&ldquo;We follow the Conventional Commits specification. Each commit message must have a type, an optional scope in parentheses, and a short imperative-mood description — no trailing period. The body, separated by a blank line, explains the <em>why</em> rather than the <em>what</em>.&rdquo;</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ════════════════════════════════════════════
STATE 4 — ORPHANED
════════════════════════════════════════════ -->
<div class="state-panel" id="panel-orphaned">
<div class="state-info">The entire section this comment was in was deleted — surfaced at the document level so nothing is silently lost.</div>
<div class="orphaned-layout">
<!-- Orphaned comments panel at document level -->
<div class="orphaned-comments-panel">
<div class="orphaned-comments-header">
<svg class="orphaned-banner-icon" width="14" height="14" viewBox="0 0 16 16" fill="none">
<path d="M8 5v4M8 11v.5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
<circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.2"/>
</svg>
<span class="orphaned-comments-title">1 orphaned comment</span>
<span class="orphaned-comments-desc">Section was removed from this document</span>
</div>
<div class="orphaned-comment-card">
<div class="orphaned-card-meta">
<div class="margin-avatar">SK</div>
<span class="margin-author">sasha.k</span>
<span class="margin-time">3 days ago</span>
<span class="badge badge-orphaned" style="margin-left: auto;">&#10007; Orphaned</span>
</div>
<div class="orphaned-card-breadcrumb">
<span style="font-style: italic; color: var(--text-dim);">Was in:</span>
<span style="font-weight: 500; color: var(--text-muted);">Code Review</span>
<span style="color: var(--text-dim); opacity: 0.5;">&rsaquo;</span>
<span style="font-weight: 500; color: var(--text-muted);">Review Etiquette</span>
</div>
<div class="orphaned-card-body">
This framing puts all the responsibility on the reviewer, but the author has a role here too — keeping the PR description up to date as the implementation changes, flagging when an earlier comment has been addressed. Worth a sentence or two about author obligations in review.
</div>
<button class="collapsible-toggle" onclick="toggleCollapsible('orig-orphaned', this, 'Show deleted passage', 'Hide deleted passage')" aria-expanded="false" style="padding-left: 0; margin-left: 32px; margin-top: 0;">
<svg class="chev" width="12" height="12" viewBox="0 0 16 16" fill="none">
<path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Show deleted passage
</button>
<div class="collapsible-body" id="orig-orphaned">
<div class="collapsible-inner" style="padding-left: 0; margin-left: 32px;">
<div class="original-text-label orphaned-label">Deleted passage</div>
<div class="original-text-block orphaned-text">&ldquo;Review comments should be precise and actionable. Avoid drive-by approvals — if you approve, you are vouching for the correctness of the change. If you request changes, explain why and suggest a concrete alternative where possible.&rdquo;</div>
</div>
</div>
</div>
</div>
<!-- The document itself: Code Review > Review Etiquette section is gone -->
<div class="orphaned-doc-layout">
<div class="orphaned-doc-area">
<div class="doc-title">Contributing to Meridian</div>
<div class="doc-byline">Last updated March 2026 &mdash; Meridian Core Team</div>
<div class="doc-h2">Getting Started</div>
<p class="doc-p">Meridian is an open-source data pipeline toolkit written in Go and TypeScript. Before submitting your first pull request, take a few minutes to read through this guide.</p>
<div class="doc-h3">Prerequisites</div>
<p class="doc-p">You need Go 1.22 or later, Node 20+, and <span class="doc-code">pnpm</span> installed. Start services with Docker Compose and run <span class="doc-code">go test ./...</span>.</p>
<div class="doc-h2">Workflow</div>
<div class="doc-h3">Branching</div>
<p class="doc-p">Create a feature branch from <span class="doc-code">main</span>. Use the naming pattern <span class="doc-code">type/short-description</span>.</p>
<div class="doc-h3">Commit Messages</div>
<p class="doc-p">We follow the Conventional Commits specification. Each commit message must have a type, an optional scope in parentheses, and a short imperative-mood description. Breaking changes require a <span class="doc-code">BREAKING CHANGE:</span> footer.</p>
<div class="doc-h2">Code Review</div>
<!-- Note: Review Etiquette subsection was deleted — document jumps straight to PR size -->
<p class="doc-p">All pull requests require at least one approval from a core team member. Plan for a 2–3 day turnaround on most submissions. Ping <span class="doc-code">#pr-review</span> on Slack after five days without feedback.</p>
<div class="doc-h3">Keeping Your PR Small</div>
<p class="doc-p">PRs under 400 lines of diff get reviewed faster. If your change is larger, consider whether it can be split along logical seams — data model changes in one PR, the feature in a follow-up.</p>
<div class="doc-h2">Releases</div>
<p class="doc-p">Releases are automated via semantic-release. Merging to <span class="doc-code">main</span> with a <span class="doc-code">feat:</span> commit bumps the minor version; <span class="doc-code">fix:</span> bumps the patch. Breaking changes trigger a major bump.</p>
</div>
<div class="orphaned-sidebar-placeholder">
<p>No active annotations in this document. The orphaned comment above refers to a section that no longer exists.</p>
</div>
</div>
</div>
</div>
<!-- ── Keyboard hint ── -->
<div class="keyboard-hint">
<kbd>1</kbd><kbd>2</kbd><kbd>3</kbd><kbd>4</kbd> to switch states &nbsp;&middot;&nbsp;
<kbd>&larr;</kbd><kbd>&rarr;</kbd> to navigate
</div>
</div><!-- /app -->
<script>
const STATES = ['anchored', 'shifted', 'outdated', 'orphaned'];
let current = 0;
function switchTab(state) {
const idx = STATES.indexOf(state);
if (idx === -1) return;
current = idx;
document.querySelectorAll('.tab').forEach(t => {
t.classList.toggle('active', t.dataset.state === state);
t.setAttribute('aria-selected', t.dataset.state === state ? 'true' : 'false');
});
document.querySelectorAll('.state-panel').forEach(p => {
p.classList.toggle('active', p.id === 'panel-' + state);
});
// Re-align after the panel becomes visible (layout is now computed)
requestAnimationFrame(() => alignCommentToHighlight('panel-' + state));
}
function focusComment(commentId) {
const comment = document.getElementById(commentId);
if (!comment) return;
comment.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
comment.style.transition = 'background 0.2s ease';
comment.style.background = 'rgba(250, 204, 21, 0.07)';
setTimeout(() => { comment.style.background = ''; }, 1200);
}
function toggleCollapsible(bodyId, btn, labelClosed, labelOpen) {
const body = document.getElementById(bodyId);
const chev = btn.querySelector('.chev');
const isOpen = body.classList.contains('expanded');
body.classList.toggle('expanded', !isOpen);
chev.classList.toggle('open', !isOpen);
btn.setAttribute('aria-expanded', String(!isOpen));
const label = btn.childNodes[btn.childNodes.length - 1];
if (label && label.nodeType === 3) {
label.textContent = isOpen ? ' ' + labelClosed : ' ' + labelOpen;
}
}
// Align a margin comment's top edge to the top edge of its highlight
function alignCommentToHighlight(panelId) {
const panel = document.getElementById(panelId);
if (!panel) return;
const highlight = panel.querySelector('.text-highlight, .text-highlight-shifted, .text-highlight-outdated');
const comment = panel.querySelector('.margin-comment');
if (!highlight || !comment) return;
const marginArea = comment.parentElement;
marginArea.style.position = 'relative';
comment.style.position = 'absolute';
comment.style.width = '100%';
comment.style.top = (highlight.getBoundingClientRect().top - marginArea.getBoundingClientRect().top + marginArea.scrollTop) + 'px';
// Ensure the margin area is tall enough for the absolutely-positioned comment
const docArea = panel.querySelector('.document-area');
if (docArea) {
marginArea.style.minHeight = docArea.offsetHeight + 'px';
}
}
const ALIGNED_PANELS = ['panel-anchored', 'panel-shifted', 'panel-outdated'];
function alignAll() {
ALIGNED_PANELS.forEach(id => alignCommentToHighlight(id));
}
// Keyboard navigation
document.addEventListener('keydown', e => {
if (e.key >= '1' && e.key <= '4' && !e.ctrlKey && !e.metaKey && !e.altKey) {
switchTab(STATES[parseInt(e.key, 10) - 1]);
return;
}
if ((e.key === 'ArrowRight' || e.key === 'ArrowDown') && !e.ctrlKey && !e.metaKey) {
e.preventDefault();
switchTab(STATES[(current + 1) % STATES.length]);
} else if ((e.key === 'ArrowLeft' || e.key === 'ArrowUp') && !e.ctrlKey && !e.metaKey) {
e.preventDefault();
switchTab(STATES[(current + STATES.length - 1) % STATES.length]);
}
});
// Initial alignment for the default tab (anchored)
requestAnimationFrame(alignAll);
// Re-align on window resize
window.addEventListener('resize', alignAll);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment