ModsCodeMERGEBLE LLC Search Claude Code Mods

ModsCode / Gallery / Designs / Git graph

Git graph

Commit history on main, fix and feature lanes with curved branch and merge links, short shas, branch tags and HEAD on the tip.

Git graphGit graph
Code and CI · 380×312 · sample data
hooks/designs/git-graph.mjs
// git-graph: fourteen commits of a mod repo on three lanes, newest at the top as in git log --graph.
// The lanes run main, fix, feature from the left: with fix in the middle, no branch or merge curve
// crosses a lane that is live in those rows. A commit's first parent keeps its lane and is a straight
// line; a branch bends out just above the commit it starts from and a merge bends in just below the
// merge commit, each a one-row S-curve. Lanes are told apart by ink steps (main 0.9, feature 0.65, fix
// 0.42) on the dots and by the ladder's 0.55, 0.35 and 0.25 on the lines; merge commits are hollow
// rings. Rows are 20px, the sha is in the mono face, branch tags are kanban's outlined tag pill in a
// column before the age, and HEAD on the main tip is the one Claude orange item. Static sample history, so
// every render is the same. Sample data only.
const W = 380
const H = 312
const STAGE = '#FAF9F5'
const INK = '#141413'
const MUTED = '#73726C'
const CLAUDE = '#D77757'
const FONT = "'Inter Tight','Segoe UI',system-ui,sans-serif"
const MONO = "ui-monospace,'Cascadia Mono',Consolas,monospace"
const f = n => n.toFixed(1)

const ROW0 = 22 // centre of the newest row
const ROW = 20
const LANES = [
  { name: 'main', x: 16, op: 0.9, line: 0.55 },
  { name: 'fix', x: 28, op: 0.42, line: 0.25 },
  { name: 'feature', x: 40, op: 0.65, line: 0.35 },
]
const SHA_X = 56
const SUBJECT_X = 106
const TAG_R = 340 // right edge of the tag column; the age sits right-aligned at W - 12

// Oldest first. lane indexes LANES; parents index this list, the first parent first.
const COMMITS = [
  { sha: '1f3c0a2', lane: 0, parents: [], subject: 'init look mod', age: '9d' },
  { sha: '7b21e94', lane: 0, parents: [0], subject: 'feat: candle design', age: '8d' },
  { sha: 'c40d8a1', lane: 2, parents: [1], subject: 'feat: gallery grid', age: '7d' },
  { sha: '5e9a3f0', lane: 0, parents: [1], subject: 'chore: pin d3 bundle', age: '6d' },
  { sha: '9a07b6c', lane: 2, parents: [2], subject: 'feat: draw designs lazily', age: '6d' },
  { sha: '2d8e41f', lane: 1, parents: [3], subject: 'fix: round token counts', age: '5d' },
  { sha: 'e61f0b3', lane: 0, parents: [3], subject: 'test: check.mjs contract', age: '4d' },
  { sha: '84c2d17', lane: 1, parents: [5], subject: 'fix: guard empty window', age: '4d', tag: 'fix/usage-round' },
  { sha: 'b3a95e0', lane: 0, parents: [6, 7], subject: 'Merge fix/usage-round', age: '3d' },
  { sha: '0f6d2c8', lane: 2, parents: [4, 8], subject: 'Merge main into feat/gallery', age: '2d' },
  { sha: '6ac81e5', lane: 2, parents: [9], subject: 'feat: sheet export at 2x', age: '1d', tag: 'feat/gallery' },
  { sha: 'd27b4f9', lane: 0, parents: [8], subject: 'chore: bump 0.4.1', age: '20h' },
  { sha: '49e0c7a', lane: 0, parents: [11, 10], subject: 'Merge feat/gallery', age: '6h' },
  { sha: 'a3f9c21', lane: 0, parents: [12], subject: 'release: 0.5.0', age: '2h', tag: 'main', head: true },
]

const text = (x, y, s, { size = 9, fill = MUTED, anchor = 'start', font = FONT } = {}) =>
  `<text x="${f(x)}" y="${f(y)}"${anchor === 'start' ? '' : ` text-anchor="${anchor}"`} font-size="${size}" font-family="${font}" fill="${fill}">${s}</text>`

// kanban's tag pill, right-aligned at x. The font is not measured, so widths at 9px come from three
// classes read off a Segoe UI render: a capital 5.9px, m or w 7.4px, the narrow f i j l r t and / - . 2.6px,
// any other character 4.9px. One flat width per character left feat/gallery with 14px of air on its right
// and 4px on its left; the classes keep both sides near the 4px of padding
const pillW = label => 8 + [...label].reduce((w, ch) => w + (/[A-Z]/.test(ch) ? 5.9 : /[mw]/.test(ch) ? 7.4 : /[fijlrt/.-]/.test(ch) ? 2.6 : 4.9), 0)
function tagPill(right, cy, label, colour, op) {
  const w = Math.round(pillW(label))
  const x = right - w
  return {
    w,
    svg: `<rect x="${f(x + 0.5)}" y="${f(cy - 6.5)}" width="${w}" height="13" rx="2" fill="none" stroke="${colour}" stroke-opacity="${op}"/>` +
      text(x + 4.5, cy + 3, label, { fill: colour }),
  }
}

function draw() {
  const n = COMMITS.length
  const rowY = i => ROW0 + (n - 1 - i) * ROW // newest at the top
  const parts = [`<rect width="${W}" height="${H}" rx="4" fill="${STAGE}"/>`]

  // Links, child to parent, before the dots
  COMMITS.forEach((c, i) => {
    c.parents.forEach((p, k) => {
      const a = LANES[c.lane]
      const b = LANES[COMMITS[p].lane]
      const yc = rowY(i)
      const yp = rowY(p)
      let d
      let lane
      if (a === b) {
        d = `M${a.x} ${f(yc)}V${f(yp)}`
        lane = a
      } else if (k === 0) {
        // A branch: run down the child's lane, then bend into the parent in the last row
        const ys = yp - ROW
        d = `M${a.x} ${f(yc)}V${f(ys)}C${a.x} ${f(ys + ROW / 2)} ${b.x} ${f(ys + ROW / 2)} ${b.x} ${f(yp)}`
        lane = a
      } else {
        // A merge: bend out of the merge commit in the first row, then run down the parent's lane
        const ye = yc + ROW
        d = `M${a.x} ${f(yc)}C${a.x} ${f(yc + ROW / 2)} ${b.x} ${f(yc + ROW / 2)} ${b.x} ${f(ye)}V${f(yp)}`
        lane = b
      }
      parts.push(`<path d="${d}" fill="none" stroke="${INK}" stroke-opacity="${lane.line}"/>`)
    })
  })

  COMMITS.forEach((c, i) => {
    const y = rowY(i)
    const lane = LANES[c.lane]
    if (c.parents.length > 1) {
      parts.push(`<circle cx="${lane.x}" cy="${f(y)}" r="2.5" fill="${STAGE}" stroke="${INK}" stroke-opacity="${lane.op}"/>`)
    } else {
      parts.push(`<circle cx="${lane.x}" cy="${f(y)}" r="2" fill="${INK}" fill-opacity="${lane.op}"/>`)
    }
    parts.push(text(SHA_X, y + 3.5, c.sha, { size: 10, font: MONO }))
    parts.push(text(SUBJECT_X, y + 3.5, c.subject, { size: 10, fill: INK }))
    // Tags line up in a column that ends before the age, so their place does not hang on an estimate
    // of the subject's width; HEAD stands to the left of the branch it points at
    let tx = TAG_R
    if (c.tag) {
      const p = tagPill(tx, y, c.tag, MUTED, 0.5)
      parts.push(p.svg)
      tx -= p.w + 4
    }
    if (c.head) parts.push(tagPill(tx, y, 'HEAD', CLAUDE, 0.9).svg)
    parts.push(text(W - 12, y + 3, c.age, { anchor: 'end' }))
  })

  parts.push(text(12, H - 8, `SAMPLE ${COMMITS.length} COMMITS`))
  return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}">${parts.join('')}</svg>`
}

let source // drawn on first use and kept: the pane loads every design, so none pays at load time
export default {
  id: 'git-graph',
  title: 'Git graph',
  category: 'code',
  description: 'Commit history on main, fix and feature lanes with curved branch and merge links, short shas, branch tags and HEAD on the tip.',
  element: ui => ui.Svg({ source: (source ??= draw()), alt: `Git graph of ${COMMITS.length} commits on ${LANES.length} lanes with branch and merge curves and HEAD on main, sample data`, width: W, height: H }),
}

To use it in a mod: copy the file to your mod's hooks/designs/ and return its element(ui) from a pane's ui.render. How a pane is built.

Code: PolyForm Noncommercial License 1.0.0: noncommercial use only. Required Notice: Copyright 2026 Haruki Nakada (https://modscode.com).