Files
open-design/apps/web/tests/analytics/turn-index.test.ts
0ac3f04851 feat(analytics): add per-project project_turn_index run counter (#5215)
* feat(analytics): add per-project project_turn_index run counter

turn_index counts runs session-wide across all projects (sessionStorage,
resets each browser session). Add a project-scoped sibling
project_turn_index (localStorage keyed by project id, project-lifetime per
device) so we can analyse "within THIS project, which prompt / follow-up is
this run?" without the session-global counter conflating separate projects.

- identity.ts: claimProjectTurnIndex(projectId)
- contracts: projectTurnIndex hint + project_turn_index run prop
- ProjectView: claim alongside the session turn at the create-run dispatch
- daemon: stamp project_turn_index (mirrors the turn_index passthrough)

Existing turn_index is unchanged. New unit test pins per-project isolation,
cross-session persistence, and independence from the session counter.

* feat(analytics): stamp session/project turn index on the BYOK-OpenCode run path

Per review of the tracking-acceptance doc: the BYOK-OpenCode send path
previously carried neither turn_index nor project_turn_index. Claim both
(plus is_first_run / has_existing_artifact) there too, so run_created /
run_finished report the session-global and project-scoped run sequence
uniformly across every runtime (local cli / amr / byok).

* chore: retrigger ci

---------

Co-authored-by: free666799 <293857035+free666799@users.noreply.github.com>
Co-authored-by: Amy <1184569493@qq.com>
2026-07-07 07:17:50 +00:00

59 lines
2.4 KiB
TypeScript

// @vitest-environment jsdom
import { beforeEach, describe, expect, it } from 'vitest';
import {
claimProjectTurnIndex,
claimRunTurnIndex,
} from '../../src/analytics/identity';
// `claimRunTurnIndex` is the session-wide run counter (sessionStorage, spans
// all projects, resets each browser session). `claimProjectTurnIndex` is the
// per-project counter (localStorage, project-lifetime per device). These tests
// pin the semantic difference between the two.
describe('run turn index: session-wide vs per-project', () => {
beforeEach(() => {
window.localStorage.clear();
window.sessionStorage.clear();
});
it('per-project index starts at 0 and increments within a project', () => {
expect(claimProjectTurnIndex('proj-a')).toEqual({ projectTurnIndex: 0 });
expect(claimProjectTurnIndex('proj-a')).toEqual({ projectTurnIndex: 1 });
expect(claimProjectTurnIndex('proj-a')).toEqual({ projectTurnIndex: 2 });
});
it('keeps an independent counter per project id', () => {
claimProjectTurnIndex('proj-a'); // 0
claimProjectTurnIndex('proj-a'); // 1
expect(claimProjectTurnIndex('proj-b')).toEqual({ projectTurnIndex: 0 });
expect(claimProjectTurnIndex('proj-a')).toEqual({ projectTurnIndex: 2 });
});
it('persists across browser sessions (survives a sessionStorage reset)', () => {
claimProjectTurnIndex('proj-a'); // 0
claimProjectTurnIndex('proj-a'); // 1
// A new browser session clears sessionStorage but NOT localStorage.
window.sessionStorage.clear();
expect(claimProjectTurnIndex('proj-a')).toEqual({ projectTurnIndex: 2 });
});
it('returns null for an empty project id', () => {
expect(claimProjectTurnIndex('')).toBeNull();
});
it('session counter resets on new session while project counters persist', () => {
expect(claimRunTurnIndex()).toEqual({ turnIndex: 0, isFirstRun: true });
claimRunTurnIndex(); // turnIndex 1
claimProjectTurnIndex('proj-a'); // 0
claimProjectTurnIndex('proj-b'); // 0
window.sessionStorage.clear(); // simulate a new browser session
// Session-wide counter starts over…
expect(claimRunTurnIndex()).toEqual({ turnIndex: 0, isFirstRun: true });
// …but each project keeps its own place.
expect(claimProjectTurnIndex('proj-a')).toEqual({ projectTurnIndex: 1 });
expect(claimProjectTurnIndex('proj-b')).toEqual({ projectTurnIndex: 1 });
});
});