Skip to main content
Compendium / Pillars & Roles / Loops & Cycles / RED-GREEN-REFACTOR Ring
#381

RED-GREEN-REFACTOR Ring

A 3-segment ring tracking the CO pillar TDD loop — RED · GREEN · REFACTOR — with the active step highlighted.

STABLE CO
#svg #real-content #dark-mode-tested #responsive
Live
// co · tdd RING
STEP 2/3 GREEN
REDGREENREFACTOR
Spec

Intent

Make the CO pillar's test-driven loop visible at a glance: which third of the cycle is @author currently in.

Acceptance Criteria

  • [ ] Three equal arcs — red (fail), green (pass), refactor
  • [ ] Active segment uses its band token; idle segments are ink-tinted
  • [ ] Centre label names the active step
  • [ ] Negative: does NOT require JS

Constraints

  • Pure SVG
  • Theme-aware via tokens
  • ≤ 150 lines body
// red-green-refactor-ring.astro
---
import type { CompendiumMeta } from '../../../data/types';

export const meta: CompendiumMeta = {
  id: 381,
  slug: 'red-green-refactor-ring',
  name: 'RED-GREEN-REFACTOR Ring',
  category: 'pillars-roles',
  subcategory: 'loops-cycles',
  pillar: 'co',
  description: 'A 3-segment ring tracking the CO pillar TDD loop — RED · GREEN · REFACTOR — with the active step highlighted.',
  spec: `## Intent
Make the CO pillar's test-driven loop visible at a glance: which third of the cycle is @author currently in.

## Acceptance Criteria
- [ ] Three equal arcs — red (fail), green (pass), refactor
- [ ] Active segment uses its band token; idle segments are ink-tinted
- [ ] Centre label names the active step
- [ ] Negative: does NOT require JS

## Constraints
- Pure SVG
- Theme-aware via tokens
- ≤ 150 lines body`,
  tags: ['svg', 'real-content', 'dark-mode-tested', 'responsive'],
  status: 'stable',
  health: { ics: 92, a11y: 'aa', themed: true, animated: false },
};

const steps = [
  { key: 'RED',      tone: 'danger'     },
  { key: 'GREEN',    tone: 'success'    },
  { key: 'REFACTOR', tone: 'attention'  },
];
const activeIdx = 1;
const cx = 60, cy = 60, r = 44;
// Arc helper: each arc is 120deg, with a 6deg gap on each side.
function arcPath(startDeg: number, endDeg: number) {
  const s = ((startDeg - 90) * Math.PI) / 180;
  const e = ((endDeg - 90) * Math.PI) / 180;
  const x1 = cx + r * Math.cos(s), y1 = cy + r * Math.sin(s);
  const x2 = cx + r * Math.cos(e), y2 = cy + r * Math.sin(e);
  const large = endDeg - startDeg > 180 ? 1 : 0;
  return `M ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2}`;
}
const arcs = steps.map((s, i) => ({
  ...s,
  d: arcPath(i * 120 + 6, (i + 1) * 120 - 6),
  active: i === activeIdx,
}));
---

<div data-this-component class="w-full bg-paper text-black border-4 border-black p-3 overflow-hidden">
  <div class="flex items-baseline justify-between mb-2 gap-2">
    <span class="font-mono text-[10px] uppercase tracking-widest opacity-60">// co · tdd</span>
    <span class="font-mono text-[10px] uppercase tracking-widest">RING</span>
  </div>
  <div class="flex items-center justify-center">
    <svg viewBox="0 0 120 120" class="w-full max-w-[180px] block" aria-label={`TDD ring, active step ${steps[activeIdx].key}`} role="img">
      {arcs.map((a) => (
        <path
          d={a.d}
          fill="none"
          stroke={a.active ? `var(--color-${a.tone})` : 'var(--color-ink)'}
          stroke-opacity={a.active ? 1 : 0.18}
          stroke-width="10"
          stroke-linecap="butt"
          vector-effect="non-scaling-stroke"
        />
      ))}
      <text x="60" y="58" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="8" letter-spacing="2" fill="var(--color-ink)" opacity="0.55">STEP {activeIdx + 1}/3</text>
      <text x="60" y="74" text-anchor="middle" font-family="Inter, sans-serif" font-weight="900" font-size="15" fill="var(--color-ink)">{steps[activeIdx].key}</text>
    </svg>
  </div>
  <div class="mt-2 flex items-baseline justify-between font-mono text-[9px] uppercase tracking-widest">
    {steps.map((s, i) => (
      <span class={i === activeIdx ? 'font-black text-black' : 'opacity-50'}>{s.key}</span>
    ))}
  </div>
</div>