Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/app/TerminalApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8694,6 +8694,9 @@ export class TerminalApp {
if (block && watch.status === 'stopped' && !block.finalized) {
block.finalized = true;
this.output.addFrontendBlock(`/track ${watch.command}`, finalResultRows(watch));
// The anchored transcript row can render its stopped state directly from the task.
// The mutable per-tracker height bookkeeping is no longer needed after finalization.
this.trackBlocks.delete(watch.id);
}
this.render();
});
Expand Down
3 changes: 3 additions & 0 deletions src/tasks/WatchTasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ export class WatchTasks {
return true;
}
this.children.set(id, child);
// Notify the presenter when a refresh begins, not only when it completes:
// a slow sample must be shown as refreshing while its previous result stays visible.
this.emit(watch);
const take = (decoder: StringDecoder, chunk: Buffer) => {
const kept = Math.min(chunk.length, Math.max(0, MAX_OUTPUT - receivedBytes));
if (kept) { receivedBytes += kept; output += decoder.write(chunk.subarray(0, kept)); }
Expand Down
21 changes: 21 additions & 0 deletions tests/track.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,27 @@ test('/track reuses one current and previous snapshot, does not overlap manual r
}
});

test('/track emits a live refresh-start event before the sample completes', async () => {
const cwd = mkdtempSync(join(tmpdir(), 'nmsh-track-refresh-'));
const engine = new WatchTasks('/bin/sh');
try {
const task = engine.start('sleep 0.2; printf "ready\\n"', cwd, 5000,
{kind: 'allowed', network: false, remote: false});
await waitFor(() => task.runs === 1);
let notifiedWhileSampling = false;
const unsubscribe = engine.onChange(changed => {
if (changed.id === task.id && engine.sampling(task.id)) notifiedWhileSampling = true;
});
assert.equal(engine.runNow(task.id), true);
assert.equal(notifiedWhileSampling, true, 'presenter is notified during refresh');
await waitFor(() => task.runs === 2);
unsubscribe();
} finally {
engine.dispose();
rmSync(cwd, {recursive: true, force: true});
}
});

test('/track strips terminal control sequences from the displayed snapshot', async () => {
const cwd = mkdtempSync(join(tmpdir(), 'nmsh-track-controls-'));
const engine = new WatchTasks('/bin/sh');
Expand Down
1 change: 1 addition & 0 deletions tests/trackApp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ test('/track in the app: one block whose live result refreshes in place; /track
const text = stored(app).join('\n');
assert.match(text, /Stopped · last result \d\d:\d\d:\d\d · exit 0 · \d+ runs?\n\s*two/u);
assert.equal(text.split('Stopped · last result').length, 2, 'written once');
assert.equal(app['trackBlocks'].size, 0, 'a stopped tracker releases its live height bookkeeping');
assert.match(live(app)[0] ?? '', /^■ stopped/u, 'the live rows end with the stopped state');

app['handleWatch']('/watch ls', 'start', 'ls', false);
Expand Down
Loading