/* CTO peer-review widgets — interactive controls specific to the engineering track.
Skill matrix, sortable priorities, code-diff rating, slider scales, tag picker,
stack tags, "would you ship this" yes/no with explanation, etc. */
const { useState: ctoUseState, useRef: ctoUseRef, useEffect: ctoUseEffect } = React;
// ─── Skill matrix: rate me on 5 axes (slider 1–5) ─────────────────────
function SkillMatrix({ skills, value = {}, onChange }) {
const labels = ['—', '1', '2', '3', '4', '5'];
const captions = (n) => n === 0 ? '· · ·' : n === 1 ? 'junior' : n === 2 ? 'developing' : n === 3 ? 'solid' : n === 4 ? 'strong' : 'staff-level';
return (
{skills.map(s => {
const v = value[s.key] ?? 0;
return (
{[1,2,3,4,5].map(n => (
);
})}
);
}
// ─── Sortable priorities ──────────────────────────────────────────────
function SortablePriorities({ items, value, onChange }) {
const ordered = value && value.length === items.length ? value : items.slice();
const [drag, setDrag] = ctoUseState(null);
const move = (from, to) => {
if (from === to) return;
const arr = ordered.slice();
const [m] = arr.splice(from, 1);
arr.splice(to, 0, m);
onChange(arr);
};
return (
// drag to reorder · top = matters most
{ordered.map((item, idx) => (
setDrag(idx)}
onDragEnd={() => setDrag(null)}
onDragOver={(e) => { e.preventDefault(); }}
onDrop={() => { if (drag != null) move(drag, idx); setDrag(null); }}
>
{String(idx + 1).padStart(2, '0')}
⋮⋮
{item}
))}
);
}
// ─── Slider scale (1–10) with named ends ──────────────────────────────
function SliderScale({ value, onChange, lowLabel, highLabel, leftWord, rightWord }) {
const v = value ?? 5;
return (
{leftWord}{rightWord}
onChange(Number(e.target.value))} />
{v}
1 — {lowLabel}
{highLabel} — 10
);
}
// ─── Code diff: which version is better? ─────────────────────────────
function CodeDiffPick({ samples, value, onChange, t }) {
return (
{samples.map((s, i) => (
))}
);
}
// ─── Yes / No with required reason ───────────────────────────────────
function YesNoWithReason({ value = {}, onChange, yesLabel, noLabel, reasonPlaceholder }) {
return (
{value.choice && (
);
}
// ─── Stack tag cloud (multi-select with weight) ─────────────────────
function StackPicker({ tags, value = [], onChange }) {
const toggle = (tag) => {
onChange(value.includes(tag) ? value.filter(t => t !== tag) : [...value, tag]);
};
return (
{tags.map(tag => (
))}
);
}
// ─── Distribute 100 points across categories ─────────────────────────
function PointsAllocator({ buckets, value = {}, onChange, total = 100 }) {
const sum = Object.values(value).reduce((a, b) => a + (Number(b) || 0), 0);
const remaining = total - sum;
const set = (k, raw) => {
const n = Math.max(0, Math.min(total, Number(raw) || 0));
const others = Object.entries(value).filter(([key]) => key !== k);
const otherSum = others.reduce((a, [, b]) => a + (Number(b) || 0), 0);
const cap = Math.max(0, total - otherSum);
onChange({ ...value, [k]: Math.min(n, cap) });
};
return (
{remaining === 0 ? '✓ allocated' : remaining > 0 ? `${remaining} left to spend` : `over by ${-remaining}`}
{buckets.map(b => {
const v = value[b.key] || 0;
return (
);
})}
);
}
// ─── Time pulse: how was the rhythm of work? ─────────────────────────
function RhythmGraph({ value = [], onChange, weeks = 8 }) {
const arr = value.length === weeks ? value : Array.from({ length: weeks }, () => 3);
const set = (idx, val) => {
const next = arr.slice();
next[idx] = val;
onChange(next);
};
return (
{arr.map((v, i) => (
{[5,4,3,2,1].map(level => (
w{i + 1}
))}
· low energypeak ·
);
}
Object.assign(window, {
SkillMatrix, SortablePriorities, SliderScale, CodeDiffPick,
YesNoWithReason, StackPicker, PointsAllocator, RhythmGraph,
});
// ─── Trust meter: how much would you trust me with X? ──────────────
function TrustMeter({ items, value = {}, onChange }) {
const levels = [
{ v: 0, label: 'no way' },
{ v: 1, label: 'with hand-holding' },
{ v: 2, label: 'with light review' },
{ v: 3, label: 'fully' },
];
return (
{items.map(item => {
const v = value[item.key];
return (
{levels.map(l => (
))}
);
})}
);
}
// ─── Word pair: pick one of two adjectives across N axes ───────────
function WordPair({ pairs, value = {}, onChange }) {
return (
{pairs.map((p, i) => {
const picked = value[p.key];
return (
vs
);
})}
);
}
// ─── Process flow rating: stages of work, each gets a glyph ────────
const FLOW_GLYPHS = [
{ v: 'broken', icon: '✗', label: 'broken' },
{ v: 'rough', icon: '~', label: 'rough' },
{ v: 'ok', icon: '·', label: 'ok' },
{ v: 'good', icon: '✓', label: 'good' },
{ v: 'great', icon: '★', label: 'great' },
];
function ProcessFlow({ stages, value = {}, onChange, captions = {} }) {
return (
{stages.map((s, i) => {
const v = value[s.key];
return (
{String(i + 1).padStart(2, '0')}
{s.label}
{s.hint}
{FLOW_GLYPHS.map(g => (
))}
);
})}
);
}
// ─── "What would you steal from my code" — pick & weight tags ──────
function StealPicker({ tags, value = [], onChange, max = 3 }) {
const toggle = (tag) => {
if (value.includes(tag)) {
onChange(value.filter(t => t !== tag));
} else if (value.length < max) {
onChange([...value, tag]);
}
};
return (
{tags.map(tag => {
const idx = value.indexOf(tag);
return (
);
})}
{value.length} / {max} picked
);
}
// ─── Risk flag: text + severity slider ─────────────────────────────
function RiskFlag({ value = {}, onChange, placeholder, severityLow, severityHigh }) {
const sev = value.severity ?? 3;
return (
);
}
Object.assign(window, {
TrustMeter, WordPair, ProcessFlow, StealPicker, RiskFlag, FLOW_GLYPHS,
});