Skip to content

Commit 3622d44

Browse files
committed
Codex review R1: reset soft-TODO bucket to full on re-attend, add tests
1 parent 65f0bef commit 3622d44

3 files changed

Lines changed: 50 additions & 2 deletions

File tree

docs/specs/alarm.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ The Session leaves `ALARM_RINGING` and returns to `NOTHING_TO_SHOW` when any of
206206
- the user marks the Session as hard TODO (`t` key or context menu)
207207
- new output arrives while the Session has attention (starts a new `MIGHT_BE_BUSY` cycle; without attention the alarm stays ringing — see latch in transition rules)
208208

209-
All attention-based dismissals (the first three above) create a soft TODO if `todo` is currently `TODO_OFF`. This prevents phantom dismissals where the alarm vanishes without a trace. Printable keypresses drain the soft TODO's leaky bucket, and if the bucket empties completely the soft TODO clears — so users who engage with the output don't accumulate breadcrumbs. If the user stops typing, the bucket refills over `cfg.todoBucket.timeToFullSeconds` (default 3 s). Synthetic terminal reports (focus events, cursor-position responses) do not drain the bucket.
209+
All attention-based dismissals (the first three above) create a soft TODO if `todo` is not already `TODO_HARD`. If a partially-drained soft TODO already exists, the bucket resets to full — a fresh alarm ring deserves a full drain cycle. This prevents phantom dismissals where the alarm vanishes without a trace. Printable keypresses drain the soft TODO's leaky bucket, and if the bucket empties completely the soft TODO clears — so users who engage with the output don't accumulate breadcrumbs. If the user stops typing, the bucket refills over `cfg.todoBucket.timeToFullSeconds` (default 3 s). Synthetic terminal reports (focus events, cursor-position responses) do not drain the bucket.
210210

211211
The Session leaves `ALARM_RINGING` and returns to `ALARM_DISABLED` when:
212212

lib/src/lib/alarm-manager.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,54 @@ describe('AlarmManager in isolation', () => {
254254
expect(manager.getState(id).todo).toBe(TODO_HARD);
255255
});
256256

257+
it('re-attending a ringing alarm resets a partially-drained soft-TODO bucket to full', () => {
258+
const id = 'bucket-reset-on-reattend';
259+
createSoftTodo(id);
260+
261+
// Drain the bucket partially (3 out of 5 keypresses)
262+
manager.drainTodoBucket(id);
263+
manager.drainTodoBucket(id);
264+
manager.drainTodoBucket(id);
265+
expect(manager.getState(id).todo).toBeCloseTo(0.4);
266+
267+
// Drive to ALARM_RINGING again
268+
manager.clearAttention(id);
269+
manager.onData(id);
270+
vi.advanceTimersByTime(1_600);
271+
manager.onData(id);
272+
manager.onData(id);
273+
vi.advanceTimersByTime(2_000);
274+
vi.advanceTimersByTime(3_000);
275+
expect(manager.getState(id).status).toBe('ALARM_RINGING');
276+
277+
// Re-attend should reset the bucket to full
278+
manager.attend(id);
279+
expect(manager.getState(id).todo).toBe(TODO_SOFT_FULL);
280+
});
281+
282+
it('re-attending a ringing alarm does NOT override a hard TODO', () => {
283+
const id = 'bucket-no-reset-hard';
284+
createSoftTodo(id);
285+
286+
// Promote to hard
287+
manager.promoteTodo(id);
288+
expect(manager.getState(id).todo).toBe(TODO_HARD);
289+
290+
// Drive to ALARM_RINGING again
291+
manager.clearAttention(id);
292+
manager.onData(id);
293+
vi.advanceTimersByTime(1_600);
294+
manager.onData(id);
295+
manager.onData(id);
296+
vi.advanceTimersByTime(2_000);
297+
vi.advanceTimersByTime(3_000);
298+
expect(manager.getState(id).status).toBe('ALARM_RINGING');
299+
300+
// Re-attend should NOT change hard TODO
301+
manager.attend(id);
302+
expect(manager.getState(id).todo).toBe(TODO_HARD);
303+
});
304+
257305
it('drainTodoBucket is a no-op for hard TODOs', () => {
258306
const id = 'bucket-hard-noop';
259307
manager.toggleTodo(id);

lib/src/lib/alarm-manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export class AlarmManager {
129129

130130
if (previousStatus === 'ALARM_RINGING') {
131131
entry.attentionDismissedRing = true;
132-
if (entry.todo === TODO_OFF) {
132+
if (!isHardTodo(entry.todo)) {
133133
entry.todo = TODO_SOFT_FULL;
134134
}
135135
}

0 commit comments

Comments
 (0)