ModsCodeMERGEBLE LLC Search Claude Code Mods

ModsCode / Gallery / Designs / Incident board

Incident board

Incidents by severity with owner, age and a 24-hour status strip per row; the mean time to resolve sits top right, worked out from the resolved rows.

Incident boardIncident board
Work and agents · 380×226 · sample data
hooks/designs/incident-board.mjs
// incident-board: the last 24 hours of incidents for a small team, one row each: a severity chip, the
// title, the owner, how long it has been open (or how long it took to resolve), and a strip of 24
// hourly cells for the affected service. A quiet hour is a track at 14% ink; an hour inside the
// incident is candle's 55% body, red for the SEV1 outage and ink for degraded service, so the red reads
// as the one outage and the ink as the rest. Chips: SEV1 filled red, SEV2 an Claude orange outline (the one
// Claude orange item, so there is exactly one SEV2), SEV3 an ink outline. Open rows come first by severity,
// then resolved ones, newest first, with their titles muted and a green check by the time they took. The MTTR top right is the mean of the
// three resolved durations (1h 12m, 54m, 2h 40m, so 1h 35m), worked out from the rows so it cannot
// disagree with them. Sample data only.
const W = 380
const H = 226
const STAGE = '#FAF9F5'
const INK = '#141413'
const MUTED = '#73726C'
const UP = '#2C7A39'
const DOWN = '#AB2B3F'
const CLAUDE = '#D77757'
const FONT = "'Inter Tight','Segoe UI',system-ui,sans-serif"
const f = n => n.toFixed(1)

const ROW0 = 50
const PITCH = 26
const STRIP = { x: 224, pitch: 6, cell: 5, h: 10 } // 24 cells end at x = 368
const COL_END = 216 // the right edge of the title column

// Times in minutes. opened: how long ago it started; took: how long it ran before it was resolved.
const INCIDENTS = [
  { sev: 1, title: 'API 5xx on /v1/messages', owner: 'haru', opened: 85 },
  { sev: 2, title: 'Checkout p95 over 2s', owner: 'kenji', opened: 190 },
  { sev: 3, title: 'Flaky e2e on main', owner: 'agent-2', opened: 365 },
  { sev: 3, title: 'Webhook retries piling up', owner: 'mika', opened: 560, took: 72 },
  { sev: 3, title: 'Docs site serving stale cache', owner: 'sam', opened: 880, took: 54 },
  { sev: 3, title: 'CI runners out of disk', owner: 'lee', opened: 1260, took: 160 },
]

const text = (x, y, s, { size = 9, fill = MUTED, anchor = 'start' } = {}) =>
  `<text x="${f(x)}" y="${f(y)}"${anchor === 'start' ? '' : ` text-anchor="${anchor}"`} font-size="${size}" font-family="${FONT}" fill="${fill}">${s}</text>`
const dur = m => (m >= 60 ? `${Math.floor(m / 60)}h ${String(Math.round(m % 60)).padStart(2, '0')}m` : `${Math.round(m)}m`)

function chip(sev, x, y) {
  const box = `x="${f(x + 0.5)}" y="${f(y + 0.5)}" width="31" height="12" rx="2"`
  const label = text(x + 16, y + 9.5, `SEV${sev}`, { fill: sev === 1 ? INK : sev === 2 ? CLAUDE : MUTED, anchor: 'middle' })
  if (sev === 1) return `<rect ${box} fill="${DOWN}" fill-opacity="0.55"/>${label}`
  if (sev === 2) return `<rect ${box} fill="none" stroke="${CLAUDE}"/>${label}`
  return `<rect ${box} fill="none" stroke="${INK}" stroke-opacity="0.35"/>${label}`
}

// Cell c covers the hour from (24 - c) hours ago to (23 - c) hours ago; it is hit when that hour
// overlaps the incident, from `opened` minutes ago until it was resolved (or until now)
function strip(inc, y) {
  const from = inc.opened
  const to = inc.took === undefined ? 0 : inc.opened - inc.took
  let out = ''
  for (let c = 0; c < 24; c++) {
    const a = (24 - c) * 60
    const b = (23 - c) * 60
    const hit = a > to && b < from
    const fill = hit ? `fill="${inc.sev === 1 ? DOWN : INK}" fill-opacity="0.55"` : `fill="${INK}" fill-opacity="0.14"`
    out += `<rect x="${f(STRIP.x + c * STRIP.pitch)}" y="${f(y)}" width="${STRIP.cell}" height="${STRIP.h}" ${fill}/>`
  }
  return out
}

function draw() {
  const parts = [`<rect width="${W}" height="${H}" rx="4" fill="${STAGE}"/>`]
  const open = INCIDENTS.filter(i => i.took === undefined)
  const done = INCIDENTS.filter(i => i.took !== undefined)
  const mttr = done.reduce((s, i) => s + i.took, 0) / done.length

  // Header: open count on the left, MTTR on the right as the hero figure
  parts.push(text(12, 24, String(open.length), { size: 14, fill: INK }))
  parts.push(text(12 + String(open.length).length * 9 + 3, 24, `OPEN, ${INCIDENTS.length} IN 24H`))
  parts.push(text(W - 12, 24, dur(mttr), { size: 14, fill: INK, anchor: 'end' }))
  parts.push(text(W - 66, 24, 'MTTR', { anchor: 'end' }))

  // The strip's scale, once, over the column
  const sx1 = STRIP.x + 23 * STRIP.pitch + STRIP.cell
  parts.push(text(STRIP.x, ROW0 - 6, '24H AGO'))
  parts.push(text((STRIP.x + sx1) / 2, ROW0 - 6, '12H', { anchor: 'middle' }))
  parts.push(text(sx1, ROW0 - 6, 'NOW', { anchor: 'end' }))

  INCIDENTS.forEach((inc, i) => {
    const top = ROW0 + i * PITCH
    const resolved = inc.took !== undefined
    parts.push(chip(inc.sev, 12, top + 2))
    parts.push(text(52, top + 12, inc.title, { size: 10, fill: resolved ? MUTED : INK }))
    parts.push(text(52, top + 23, inc.owner))
    const state = resolved ? `RESOLVED IN ${dur(inc.took)}` : `OPEN ${dur(inc.opened)}`
    parts.push(text(COL_END, top + 23, state, { anchor: 'end' }))
    // A resolved row gets ci-pipeline's check, drawn as a path, left of its words: three green texts
    // outweighed the open SEV1. The words are placed at 4.8px a character (measured 4.7), so a wider fallback font still clears it.
    if (resolved) {
      const gx = COL_END - state.length * 4.8 - 8
      const gy = top + 20
      parts.push(`<path d="M${f(gx - 2.6)} ${f(gy + 0.2)}L${f(gx - 0.8)} ${f(gy + 2)}L${f(gx + 2.7)} ${f(gy - 1.9)}" fill="none" stroke="${UP}"/>`)
    }
    parts.push(strip(inc, top + 7))
  })
  parts.push(text(12, H - 8, 'SAMPLE INCIDENTS'))
  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: 'incident-board',
  title: 'Incident board',
  category: 'work',
  description: 'Incidents by severity with owner, age and a 24-hour status strip per row; the mean time to resolve sits top right, worked out from the resolved rows.',
  element: ui => ui.Svg({
    source: (source ??= draw()),
    alt: `Incident board of ${INCIDENTS.length} incidents with severity chips, owners, ages and 24-hour status strips, and the mean time to resolve, 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).