ModsCodeMERGEBLE LLC Search Claude Code Mods

ModsCode / Gallery / Designs / Session timeline

Session timeline

Turns of one session on a clock track: tick height is the context sent, dots mark prompts, red ticks are failed calls and the compaction is marked in Claude orange.

Session timelineSession timeline
Work and agents · 380×130 · sample data
hooks/designs/session-timeline.mjs
// session-timeline: one Claude Code session of 40 turns on a single clock track, 14:02 to 15:08. Each
// turn is a 2px tick at its real time, so tool loops bunch into combs and the person's reading time shows
// as gaps; its height is the context it sent (input plus cache read), at candle's 55% ink. A dot above
// the track (2px radius) marks a turn the person started with a prompt. The context starts at 21K for
// the system prompt and tools and grows 3K to 8K a turn (prompts add 4K to 10K) until /compact before
// turn 28 resets it to 26K: the compaction is the one Claude orange mark, and the drop in the ticks after it is
// the reason to show it. Two turns whose tool call failed are red. The peak and the last turn carry
// their values; time labels sit under 4px stubs at the quarter hours, and the
// key names the tick CONTEXT so the K values have a unit. Turn times and sizes come from a
// seeded walk scaled to 66 minutes; the turn after /compact always follows a pause, as it is typed. Sample data only.
const W = 380
const H = 130
const STAGE = '#FAF9F5'
const INK = '#141413'
const MUTED = '#73726C'
const DOWN = '#AB2B3F'
const CLAUDE = '#D77757'
const FONT = "'Inter Tight','Segoe UI',system-ui,sans-serif"
const f = n => n.toFixed(1)

const N = 40
const START = 14 * 60 + 2 // 14:02, in minutes after midnight
const SPAN = 66 // minutes
const COMPACT = 27 // the first turn after /compact (0-based)
const ERRORS = [12, 33] // turns whose tool call failed
const PLOT = { left: 14, right: 366 }
const BASE = 92 // the track; ticks rise from it
const TALL = 42 // the height of the largest context
const DOTS = 34 // the prompt row
const Y_MAX = 170000

function rng(seed) {
  return () => (seed = (Math.imul(seed, 1103515245) + 12345) & 0x7fffffff) / 0x7fffffff
}

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 clock = m => `${Math.floor(m / 60)}:${String(m % 60).padStart(2, '0')}`
const short = v => `${Math.round(v / 1000)}K`

// A seeded session: a prompt follows a 2.5 to 6 minute pause, a tool-loop turn follows 0.6 to 1.4
// minutes after the last one. The times are then scaled so the last turn lands at 66 minutes.
function sample() {
  const rnd = rng(20261026)
  const turns = []
  let t = 0
  let ctx = 21000
  for (let i = 0; i < N; i++) {
    const prompt = i === 0 || i === COMPACT || rnd() < 0.22 // /compact is typed, so a prompt follows it
    if (i > 0) t += prompt ? 2.5 + rnd() * 3.5 : 0.6 + rnd() * 0.8
    if (i === COMPACT) ctx = 26000
    else if (i > 0) ctx += prompt ? 4000 + rnd() * 6000 : 3000 + rnd() * 5000
    turns.push({ t, prompt, ctx: Math.round(ctx), error: ERRORS.includes(i) })
  }
  const k = SPAN / t
  turns.forEach(d => (d.t *= k))
  return turns
}

function draw() {
  const turns = sample()
  const x = m => PLOT.left + ((PLOT.right - PLOT.left) * m) / SPAN
  const h = v => (TALL * v) / Y_MAX
  const parts = [`<rect width="${W}" height="${H}" rx="4" fill="${STAGE}"/>`]

  // Header: the length of the session and its turn count; a key for the tick (its height is the
  // context, which gives the K labels their unit), the prompt dot and the red tick
  parts.push(text(12, 20, `${Math.floor(SPAN / 60)}h ${String(SPAN % 60).padStart(2, '0')}m`, { size: 13, fill: INK }))
  parts.push(text(63, 20, `${N} TURNS`))
  parts.push(`<rect x="232" y="12" width="2" height="8" fill="${INK}" fill-opacity="0.55"/>`)
  parts.push(text(239, 20, 'CONTEXT'))
  parts.push(`<circle cx="286" cy="17" r="2" fill="${INK}" fill-opacity="0.55"/>`)
  parts.push(text(292, 20, 'PROMPT'))
  parts.push(`<rect x="334" y="12" width="2" height="8" fill="${DOWN}" fill-opacity="0.55"/>`)
  parts.push(text(W - 12, 20, 'ERROR', { anchor: 'end' }))

  // The turns stand on an unseen baseline: a drawn one reads as an axis line, and the stubs below imply it
  for (const d of turns) {
    const th = Math.max(2, h(d.ctx))
    parts.push(`<rect x="${f(x(d.t) - 1)}" y="${f(BASE - th)}" width="2" height="${f(th)}" fill="${d.error ? DOWN : INK}" fill-opacity="0.55"/>`)
    if (d.prompt) parts.push(`<circle cx="${f(x(d.t))}" cy="${DOTS}" r="2" fill="${INK}" fill-opacity="0.55"/>`)
  }

  // The compaction: a line between the last full turn and the first short one, labelled on the right
  const cx = (x(turns[COMPACT - 1].t) + x(turns[COMPACT].t)) / 2
  parts.push(`<line x1="${f(cx)}" x2="${f(cx)}" y1="${DOTS + 6}" y2="${BASE}" stroke="${CLAUDE}" stroke-width="1" stroke-dasharray="3 3"/>`)
  parts.push(text(cx + 4, DOTS + 14, 'COMPACT', { fill: CLAUDE }))

  // The peak and the last turn carry their context
  const peak = turns.reduce((a, d) => (d.ctx > a.ctx ? d : a))
  // Above its tick and ending on it: beside the tick it sat on the near-peak ticks to its left
  parts.push(text(x(peak.t) + 1, BASE - h(peak.ctx) - 4, short(peak.ctx), { fill: INK, anchor: 'end' }))
  const last = turns[N - 1]
  parts.push(text(x(last.t), BASE - h(last.ctx) - 4, short(last.ctx), { fill: INK, anchor: 'end' }))

  // Quarter-hour stubs under the track and the start at the left edge; the header gives the length, so
  // the end needs no label (at 15:08 it would touch 15:00)
  for (let m = 15 - (START % 15); m < SPAN; m += 15) {
    parts.push(`<line x1="${f(x(m))}" x2="${f(x(m))}" y1="${BASE + 4}" y2="${BASE + 8}" stroke="${MUTED}" stroke-width="1"/>`)
    parts.push(text(x(m), BASE + 18, clock(START + m), { anchor: 'middle' }))
  }
  parts.push(text(PLOT.left - 2, BASE + 18, clock(START)))
  parts.push(text(12, H - 8, 'SAMPLE SESSION'))
  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: 'session-timeline',
  title: 'Session timeline',
  category: 'work',
  description: 'Turns of one session on a clock track: tick height is the context sent, dots mark prompts, red ticks are failed calls and the compaction is marked in Claude orange.',
  element: ui => ui.Svg({
    source: (source ??= draw()),
    alt: `Timeline of a ${SPAN}-minute session with ${N} turns, tick height the context per turn, prompt dots, ${ERRORS.length} failed turns and a compaction that resets the context, 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).