Skip to content

Commit 05b026f

Browse files
committed
Use watchingEnabled for tutorial WATCHING detection
1 parent 722c2ce commit 05b026f

3 files changed

Lines changed: 53 additions & 12 deletions

File tree

docs/specs/tutorial.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,18 @@ Note: `-` produces a `direction: 'vertical'` split (panes stack top/bottom = hor
5555

5656
### Section 2 — Alert and TODO (6 items)
5757

58-
The detector subscribes to `subscribeToActivity()` and tracks per-id `(status, todo)` transitions.
58+
The detector subscribes to `subscribeToActivity()` and tracks per-id `(status, watchingEnabled, todo)` transitions.
5959

6060
| ID | Title | Detection |
6161
|---|---|---|
62-
| `al-enable` | Enable WATCHING on a pane (click bell or `a`) | status transitions away from `WATCHING_DISABLED` |
62+
| `al-enable` | Enable WATCHING on a pane (click bell or `a`) | `watchingEnabled` transitions `false → true` |
6363
| `al-busy` | Watch the bell tilt while a task runs | status enters `BUSY`, `MIGHT_BE_BUSY`, or `OSC_NOTIF_BUSY` |
6464
| `al-ring` | Bell rings on completion | status enters `ALERT_RINGING` |
6565
| `al-todo-auto` | TODO appears when you dismiss the ringing alert | `todo` transitions `false → true` while previous status was `ALERT_RINGING` |
6666
| `al-todo-clear` | Press passthrough Enter to clear the TODO | `todo` transitions `true → false` |
6767
| `al-todo-manual` | Manually add a TODO (`t` or right-click) | `todo` transitions `false → true` while previous status was NOT `ALERT_RINGING` |
6868

69-
The detector remembers the most recent pane whose WATCHING track was enabled. The Alert section view shows a runner-local instruction: "Press `s` here to start a fake busy task." `s` is **not** a real MouseTerm shortcut; it is intercepted by `TutRunner` only while the Alert section is open. When pressed, the runner does two things:
69+
The detector remembers the most recent pane whose `watchingEnabled` flag is true, even when projected `status` is currently owned by protocol or command-exit alert tracks. The Alert section view shows a runner-local instruction: "Press `s` here to start a fake busy task." `s` is **not** a real MouseTerm shortcut; it is intercepted by `TutRunner` only while the Alert section is open. When pressed, the runner does two things:
7070

7171
1. Resolves that pane to its current PTY session id, then calls `adapter.pumpActivity(sessionId, BUSY_DEMO_DURATION_MS, 800)` — drives the alert-manager's activity monitor on the same WATCHING-enabled session with **no text output**, so the bell tilts to BUSY without scrolling any scenario text. The session id is resolved at trigger time so `Cmd/Ctrl+Arrow` swaps do not leave the tutorial pumping an old pane id. If no WATCHING-enabled pane is known, the runner falls back to `PANE_BOXED` (the changelog pane). `BUSY_DEMO_DURATION_MS` is `cfg.alert.userAttention + 250` so silence begins after the attention idle window has expired, with a small scheduler-jitter guard; otherwise the "user is looking at this pane" check inside `ActivityMonitor.startNeedsAttentionConfirmTimer` would suppress the ring rather than let it fire.
7272
2. Animates a countdown in-place where the "Press s…" hint was: `⠋ Fake task will finish in N seconds.` ticking down to 1, then a static `✓ Fake task finished. Press s to start another one.` once the activity stops. Detection is purely timing-based via the existing `ActivityMonitor`, so no shell integration is required.

website/src/lib/tut-detector.test.ts

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@ import type { ActivityState } from "mouseterm-lib/lib/terminal-registry";
44
import { TutDetector } from "./tut-detector";
55
import { TutorialState } from "./tutorial-state";
66

7-
function activity(status: ActivityState["status"], todo = false): ActivityState {
8-
return { status, watchingEnabled: status !== "WATCHING_DISABLED", todo, notification: null };
7+
function activity(
8+
status: ActivityState["status"],
9+
todo = false,
10+
watchingEnabled = status !== "WATCHING_DISABLED",
11+
): ActivityState {
12+
return { status, watchingEnabled, todo, notification: null };
913
}
1014

11-
function makeDetectorHarness() {
15+
function makeDetectorHarness(initialActivitySnapshot = new Map<string, ActivityState>()) {
1216
let activePanelListener: ((panel: { id?: string } | undefined) => void) | null = null;
1317
let activityListener: (() => void) | null = null;
1418
let mouseListener: (() => void) | null = null;
15-
let activitySnapshot = new Map<string, ActivityState>();
19+
let activitySnapshot = initialActivitySnapshot;
1620
let mouseSnapshot = new Map<string, MouseSelectionState>();
1721
const onWatchingDemoPaneChange = vi.fn();
1822

@@ -117,8 +121,8 @@ describe("TutDetector", () => {
117121
const { state, setActivitySnapshot } = makeDetectorHarness();
118122

119123
setActivitySnapshot(new Map([
120-
["pane-a", { status: "BUSY", todo: false }],
121-
["pane-b", { status: "ALERT_RINGING", todo: false }],
124+
["pane-a", activity("BUSY")],
125+
["pane-b", activity("ALERT_RINGING")],
122126
]));
123127

124128
expect(state.isComplete("al-busy")).toBe(false);
@@ -155,6 +159,43 @@ describe("TutDetector", () => {
155159
expect(state.isComplete("al-enable")).toBe(true);
156160
});
157161

162+
it("does not credit al-enable for projected command-exit status while WATCHING is off", () => {
163+
const { state, onWatchingDemoPaneChange, setActivitySnapshot } = makeDetectorHarness();
164+
165+
onWatchingDemoPaneChange.mockClear();
166+
setActivitySnapshot(new Map([
167+
["pane-a", activity("WATCHING_DISABLED", false, false)],
168+
]));
169+
setActivitySnapshot(new Map([
170+
["pane-a", activity("COMMAND_EXIT_ARMED", false, false)],
171+
]));
172+
173+
expect(state.isComplete("al-enable")).toBe(false);
174+
expect(onWatchingDemoPaneChange).not.toHaveBeenCalled();
175+
});
176+
177+
it("credits al-enable when WATCHING turns on under an existing projected status", () => {
178+
const { state, onWatchingDemoPaneChange, setActivitySnapshot } = makeDetectorHarness();
179+
180+
setActivitySnapshot(new Map([
181+
["pane-a", activity("COMMAND_EXIT_ARMED", false, false)],
182+
]));
183+
setActivitySnapshot(new Map([
184+
["pane-a", activity("COMMAND_EXIT_ARMED", false, true)],
185+
]));
186+
187+
expect(state.isComplete("al-enable")).toBe(true);
188+
expect(onWatchingDemoPaneChange).toHaveBeenLastCalledWith("pane-a");
189+
});
190+
191+
it("does not seed the WATCHING demo pane from projected alert status", () => {
192+
const { onWatchingDemoPaneChange } = makeDetectorHarness(new Map([
193+
["pane-a", activity("ALERT_RINGING", false, false)],
194+
]));
195+
196+
expect(onWatchingDemoPaneChange).toHaveBeenLastCalledWith(null);
197+
});
198+
158199
it("tracks the pane whose WATCHING was enabled for the busy demo", () => {
159200
const { onWatchingDemoPaneChange, setActivitySnapshot } = makeDetectorHarness();
160201

website/src/lib/tut-detector.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export class TutDetector {
6363
// mis-read as a transition from "nothing".
6464
for (const [id, s] of this.activityStore.getActivitySnapshot()) {
6565
this.prevActivity.set(id, { ...s });
66-
if (s.status !== "WATCHING_DISABLED") {
66+
if (s.watchingEnabled) {
6767
this.watchingEnabledPaneIds.add(id);
6868
this.preferredWatchingPaneId ??= id;
6969
}
@@ -157,12 +157,12 @@ export class TutDetector {
157157
continue;
158158
}
159159

160-
if (prev.status === "WATCHING_DISABLED" && current.status !== "WATCHING_DISABLED") {
160+
if (!prev.watchingEnabled && current.watchingEnabled) {
161161
this.state.markComplete("al-enable");
162162
this.watchingEnabledPaneIds.add(id);
163163
this.preferredWatchingPaneId = id;
164164
this.emitWatchingDemoPaneChange();
165-
} else if (prev.status !== "WATCHING_DISABLED" && current.status === "WATCHING_DISABLED") {
165+
} else if (prev.watchingEnabled && !current.watchingEnabled) {
166166
this.watchingEnabledPaneIds.delete(id);
167167
if (this.preferredWatchingPaneId === id) {
168168
this.preferredWatchingPaneId = this.watchingEnabledPaneIds.values().next().value ?? null;

0 commit comments

Comments
 (0)