You are the engineer on call. A support ticket has come in about a small interval-timer page we ship. The user has listed four complaints. Some of them are bugs. Some of them may not be.
Your job is triage and a minimal fix — not a rewrite.
Subject: timer page — several problems
A. The progress ring fills from the wrong place. It starts at the right-hand side, at three o'clock, instead of at the top like every other timer I've used.
B. My rounds are set to 20 seconds but they take 21. The clock sits on 00:00 for a whole second before it moves on. Over eight rounds I'm losing nearly ten seconds.
C. Sometimes the timer runs at double speed and the numbers fly past. I haven't worked out exactly when. It's happened three times this week.
D. The beep at the end of a rest period is noticeably louder than the beep at the end of a work period. Please make them the same volume.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Interval Timer</title>
<style>
:root { color-scheme: dark; }
body { margin:0; font:16px/1.4 system-ui, sans-serif; background:#18181b; color:#e4e4e7;
display:flex; min-height:100vh; align-items:center; justify-content:center; }
.card { width:320px; text-align:center; }
.ring { display:block; margin:0 auto 12px; }
.ring circle { fill:none; stroke-width:10; }
.ring .track { stroke:#27272a; }
.ring .bar { stroke:#3b82f6; stroke-linecap:round; transition:stroke-dashoffset .2s linear; }
.phase { text-transform:uppercase; letter-spacing:.15em; font-size:12px; color:#a1a1aa; }
.clock { font-size:44px; font-variant-numeric:tabular-nums; margin:4px 0 2px; }
.rounds { font-size:13px; color:#a1a1aa; }
button { background:#27272a; color:#e4e4e7; border:1px solid #3f3f46; border-radius:8px;
padding:8px 14px; font-size:14px; cursor:pointer; margin:12px 4px 0; }
ul { list-style:none; padding:0; margin:16px 0 0; text-align:left; font-size:13px; }
li { border-bottom:1px solid #27272a; padding:6px 2px; display:flex; justify-content:space-between; }
.empty { color:#71717a; font-size:13px; margin-top:16px; }
</style>
</head>
<body>
<div class="card">
<svg class="ring" width="160" height="160" viewBox="0 0 160 160">
<circle class="track" cx="80" cy="80" r="70"></circle>
<circle class="bar" cx="80" cy="80" r="70" id="bar"></circle>
</svg>
<div class="phase" id="phase">Work</div>
<div class="clock" id="clock">00:20</div>
<div class="rounds" id="rounds">Round 1 of 4</div>
<button id="start">Start</button>
<button id="reset">Reset</button>
<ul id="laps"></ul>
<div class="empty" id="empty">No rounds completed yet.</div>
</div>
<script>
var WORK = 20, REST = 10, ROUNDS = 4;
var C = 2 * Math.PI * 70;
var bar = document.getElementById('bar');
bar.style.strokeDasharray = C;
bar.style.strokeDashoffset = 0;
var timer = null, phase = 'work', left = WORK, round = 1, ac = null;
function fmt(s) {
return String(Math.floor(s / 60)).padStart(2, '0') + ':' + String(s % 60).padStart(2, '0');
}
function paint() {
document.getElementById('clock').textContent = fmt(left);
document.getElementById('phase').textContent = phase === 'work' ? 'Work' : 'Rest';
document.getElementById('rounds').textContent = 'Round ' + round + ' of ' + ROUNDS;
var total = phase === 'work' ? WORK : REST;
bar.style.strokeDashoffset = C * (1 - left / total);
}
function beep() {
if (!ac) ac = new (window.AudioContext || window.webkitAudioContext)();
var o = ac.createOscillator(), g = ac.createGain();
o.connect(g);
g.connect(ac.destination);
o.frequency.value = phase === 'work' ? 880 : 440;
g.gain.setValueAtTime(0.15, ac.currentTime);
g.gain.exponentialRampToValueAtTime(0.001, ac.currentTime + 0.25);
o.start();
o.stop(ac.currentTime + 0.25);
}
function addLap() {
var li = document.createElement('li');
li.innerHTML = '<span>Round ' + round + '</span><span>' + WORK + 's work</span>';
document.getElementById('laps').appendChild(li);
document.getElementById('empty').style.display = 'none';
}
function stop() {
clearInterval(timer);
timer = null;
}
function tick() {
left--;
if (left < 0) {
beep();
if (phase === 'work') {
addLap();
phase = 'rest';
left = REST;
} else {
round++;
if (round <= ROUNDS) {
phase = 'work';
left = WORK;
} else {
stop();
document.getElementById('phase').textContent = 'Done';
return;
}
}
}
paint();
}
document.getElementById('start').onclick = function () {
timer = setInterval(tick, 1000);
};
document.getElementById('reset').onclick = function () {
stop();
phase = 'work';
left = WORK;
round = 1;
document.getElementById('laps').innerHTML = '';
document.getElementById('empty').style.display = '';
paint();
};
paint();
</script>
</body>
</html>
A table with one row per complaint, in order A to D:
| # | Verdict | Cause |
|---|
Verdict is exactly one of REAL or NOT A BUG. Cause is one line — for a real bug, what is actually wrong; for a non-bug, what the user is really experiencing and why the code does not have the defect they describe.
For each complaint you marked REAL, and only those:
Do not give me the whole file back. I want the smallest change that fixes each defect. A rewrite is not an answer to a support ticket — I have to review this and I have to be able to see what changed.
At most two further defects the user has not reported, each one you can quote a line for. Only things that are actually wrong.
If there is nothing else worth raising, write exactly Nothing else.
Every claim you make should be checkable against the code above. A confident diagnosis of a bug that is not in this file costs you more than missing a bug that is — I can find a missed bug myself, but I will act on a wrong one.