fix: handle recording discard and write-failure in cursor telemetry buffer
Address two issues raised during review: P1 – When a recording is cancelled or restarted, setRecordingState(false) enqueues its cursor batch but store-recorded-session is never called, leaving a stale batch that contaminates the next recording's telemetry. Add discardLatestPending() to the buffer and a discard-cursor-telemetry IPC handler; the renderer now calls it on the discard path. P2 – takeNextBatch() dequeued the batch before fs.writeFile, so a write failure would permanently lose the telemetry. Wrap the write in try/catch and re-insert the batch via prependBatch() on failure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -225,6 +225,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
try {
|
||||
const screenBlob = await activeScreenRecorder.recordedBlobPromise;
|
||||
if (discardRecordingId.current === activeRecordingId) {
|
||||
window.electronAPI?.discardCursorTelemetry();
|
||||
return;
|
||||
}
|
||||
if (screenBlob.size === 0) {
|
||||
|
||||
@@ -96,6 +96,50 @@ describe("createCursorTelemetryBuffer", () => {
|
||||
expect(batch.map((s) => s.timeMs)).toEqual([1]);
|
||||
});
|
||||
|
||||
it("discardLatestPending() drops the most recently enqueued batch", () => {
|
||||
const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 });
|
||||
|
||||
buf.startSession();
|
||||
buf.push(sample(1));
|
||||
buf.endSession();
|
||||
|
||||
buf.startSession();
|
||||
buf.push(sample(2));
|
||||
buf.endSession();
|
||||
|
||||
expect(buf.pendingCount).toBe(2);
|
||||
buf.discardLatestPending();
|
||||
expect(buf.pendingCount).toBe(1);
|
||||
expect(buf.takeNextBatch().map((s) => s.timeMs)).toEqual([1]);
|
||||
});
|
||||
|
||||
it("discardLatestPending() is safe to call on an empty queue", () => {
|
||||
const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 });
|
||||
buf.discardLatestPending();
|
||||
expect(buf.pendingCount).toBe(0);
|
||||
});
|
||||
|
||||
it("prependBatch() re-inserts a batch at the front of the queue", () => {
|
||||
const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 });
|
||||
|
||||
buf.startSession();
|
||||
buf.push(sample(1));
|
||||
buf.endSession();
|
||||
|
||||
const batch = buf.takeNextBatch();
|
||||
expect(buf.pendingCount).toBe(0);
|
||||
|
||||
buf.prependBatch(batch);
|
||||
expect(buf.pendingCount).toBe(1);
|
||||
expect(buf.takeNextBatch().map((s) => s.timeMs)).toEqual([1]);
|
||||
});
|
||||
|
||||
it("prependBatch() ignores empty batches", () => {
|
||||
const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 });
|
||||
buf.prependBatch([]);
|
||||
expect(buf.pendingCount).toBe(0);
|
||||
});
|
||||
|
||||
it("reset() clears both active and pending state", () => {
|
||||
const buf = createCursorTelemetryBuffer({ maxActiveSamples: 10 });
|
||||
buf.startSession();
|
||||
|
||||
@@ -9,6 +9,8 @@ export interface CursorTelemetryBuffer {
|
||||
push(point: CursorTelemetryPoint): void;
|
||||
endSession(): void;
|
||||
takeNextBatch(): CursorTelemetryPoint[];
|
||||
prependBatch(batch: CursorTelemetryPoint[]): void;
|
||||
discardLatestPending(): void;
|
||||
reset(): void;
|
||||
readonly activeCount: number;
|
||||
readonly pendingCount: number;
|
||||
@@ -52,6 +54,14 @@ export function createCursorTelemetryBuffer(
|
||||
takeNextBatch() {
|
||||
return pending.shift() ?? [];
|
||||
},
|
||||
prependBatch(batch) {
|
||||
if (batch.length > 0) {
|
||||
pending.unshift(batch);
|
||||
}
|
||||
},
|
||||
discardLatestPending() {
|
||||
pending.pop();
|
||||
},
|
||||
reset() {
|
||||
active = [];
|
||||
pending = [];
|
||||
|
||||
Reference in New Issue
Block a user