From df0f2bf7c674c6008e1cef2bbefb8ad216fa912c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 20 Apr 2026 08:11:37 +0000 Subject: [PATCH 1/2] fix(settings): reliably show session replay id Co-authored-by: Jan Six --- .../src/app/components/Settings/Settings.tsx | 21 ++---- .../getReplaySessionIdWithRetry.test.ts | 74 +++++++++++++++++++ .../Settings/getReplaySessionIdWithRetry.ts | 51 +++++++++++++ 3 files changed, 133 insertions(+), 13 deletions(-) create mode 100644 packages/tokens-studio-for-figma/src/app/components/Settings/getReplaySessionIdWithRetry.test.ts create mode 100644 packages/tokens-studio-for-figma/src/app/components/Settings/getReplaySessionIdWithRetry.ts diff --git a/packages/tokens-studio-for-figma/src/app/components/Settings/Settings.tsx b/packages/tokens-studio-for-figma/src/app/components/Settings/Settings.tsx index 50a62b54b..5702f6fc1 100644 --- a/packages/tokens-studio-for-figma/src/app/components/Settings/Settings.tsx +++ b/packages/tokens-studio-for-figma/src/app/components/Settings/Settings.tsx @@ -17,6 +17,7 @@ import { replay } from '@/app/sentry'; import { sessionRecordingSelector } from '@/selectors/sessionRecordingSelector'; import { ExplainerModal } from '../ExplainerModal'; import { Tabs } from '@/constants/Tabs'; +import { getReplaySessionIdWithRetry } from './getReplaySessionIdWithRetry'; // TODO: expose types from @tokens-studio/ui/checkbox type CheckedState = boolean | 'indeterminate'; @@ -42,15 +43,9 @@ function Settings() { dispatch.settings.setSessionRecording(!!checked); if (checked) { - // Display the info to the user try { - let id = await replay.getReplayId(); - if (!id) { - // Force start the replay functionality - replay.start(); - id = await replay.getReplayId(); - } - setDebugSession(id || ''); + const replaySessionId = await getReplaySessionIdWithRetry(replay); + setDebugSession(replaySessionId); } catch (err) { console.warn('Replay is likely in progress already', err); } @@ -65,16 +60,16 @@ function Settings() { // Load once on mount. useEffect(() => { - async function getSessionId() { + async function loadSessionId() { try { - const id = replay.getReplayId(); - setDebugSession(id || ''); + const replaySessionId = await getReplaySessionIdWithRetry(replay, { startIfMissing: false }); + setDebugSession(replaySessionId); } catch (err) { // Silently fail } } - getSessionId(); - }); + loadSessionId(); + }, []); const closeOnboarding = React.useCallback(() => { dispatch.uiState.setOnboardingExplainerSyncProviders(false); diff --git a/packages/tokens-studio-for-figma/src/app/components/Settings/getReplaySessionIdWithRetry.test.ts b/packages/tokens-studio-for-figma/src/app/components/Settings/getReplaySessionIdWithRetry.test.ts new file mode 100644 index 000000000..00fd662f2 --- /dev/null +++ b/packages/tokens-studio-for-figma/src/app/components/Settings/getReplaySessionIdWithRetry.test.ts @@ -0,0 +1,74 @@ +import { getReplaySessionIdWithRetry } from './getReplaySessionIdWithRetry'; + +describe('getReplaySessionIdWithRetry', () => { + it('returns the current replay id when already available', async () => { + const replayController = { + getReplayId: jest.fn().mockReturnValue('replay-id-1'), + start: jest.fn(), + }; + + const replayId = await getReplaySessionIdWithRetry(replayController); + + expect(replayId).toBe('replay-id-1'); + expect(replayController.start).not.toHaveBeenCalled(); + expect(replayController.getReplayId).toHaveBeenCalledTimes(1); + }); + + it('starts replay and retries until an id becomes available', async () => { + const replayController = { + getReplayId: jest + .fn() + .mockReturnValueOnce('') + .mockReturnValueOnce('') + .mockReturnValueOnce('replay-id-2'), + start: jest.fn(), + }; + + const replayId = await getReplaySessionIdWithRetry(replayController, { + attempts: 3, + retryDelayMs: 0, + }); + + expect(replayId).toBe('replay-id-2'); + expect(replayController.start).toHaveBeenCalledTimes(1); + expect(replayController.getReplayId).toHaveBeenCalledTimes(3); + }); + + it('returns an empty string when no replay id is available', async () => { + const replayController = { + getReplayId: jest.fn().mockReturnValue(''), + start: jest.fn(), + }; + + const replayId = await getReplaySessionIdWithRetry(replayController, { + attempts: 2, + retryDelayMs: 0, + }); + + expect(replayId).toBe(''); + expect(replayController.start).toHaveBeenCalledTimes(1); + expect(replayController.getReplayId).toHaveBeenCalledTimes(3); + }); + + it('continues retrying when start throws', async () => { + const replayController = { + getReplayId: jest + .fn() + .mockReturnValueOnce('') + .mockReturnValueOnce('') + .mockReturnValueOnce('replay-id-3'), + start: jest.fn().mockImplementation(() => { + throw new Error('start failed'); + }), + }; + + const replayId = await getReplaySessionIdWithRetry(replayController, { + attempts: 3, + retryDelayMs: 0, + }); + + expect(replayId).toBe('replay-id-3'); + expect(replayController.start).toHaveBeenCalledTimes(1); + expect(replayController.getReplayId).toHaveBeenCalledTimes(3); + }); +}); diff --git a/packages/tokens-studio-for-figma/src/app/components/Settings/getReplaySessionIdWithRetry.ts b/packages/tokens-studio-for-figma/src/app/components/Settings/getReplaySessionIdWithRetry.ts new file mode 100644 index 000000000..5ebd562ef --- /dev/null +++ b/packages/tokens-studio-for-figma/src/app/components/Settings/getReplaySessionIdWithRetry.ts @@ -0,0 +1,51 @@ +type ReplayController = { + getReplayId: () => string | undefined | Promise; + start: () => void | Promise; +}; + +type GetReplaySessionIdWithRetryOptions = { + attempts?: number; + retryDelayMs?: number; + startIfMissing?: boolean; +}; + +const DEFAULT_ATTEMPTS = 6; +const DEFAULT_RETRY_DELAY_MS = 300; + +function wait(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function getReplayId(replayController: ReplayController) { + return (await Promise.resolve(replayController.getReplayId())) || ''; +} + +export async function getReplaySessionIdWithRetry( + replayController: ReplayController, + options: GetReplaySessionIdWithRetryOptions = {}, +) { + const { + attempts = DEFAULT_ATTEMPTS, + retryDelayMs = DEFAULT_RETRY_DELAY_MS, + startIfMissing = true, + } = options; + + let replayId = await getReplayId(replayController); + if (replayId) return replayId; + + if (startIfMissing) { + try { + await Promise.resolve(replayController.start()); + } catch (error) { + // If replay start fails, keep trying to read any existing replay id. + } + } + + for (let attempt = 0; attempt < attempts; attempt += 1) { + await wait(retryDelayMs); + replayId = await getReplayId(replayController); + if (replayId) return replayId; + } + + return ''; +} From e66f6768e0396bc1a932bfd36ac2873365927271 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 20 Apr 2026 08:14:19 +0000 Subject: [PATCH 2/2] chore(settings): satisfy lint in replay retry helper Co-authored-by: Jan Six --- .../app/components/Settings/getReplaySessionIdWithRetry.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/tokens-studio-for-figma/src/app/components/Settings/getReplaySessionIdWithRetry.ts b/packages/tokens-studio-for-figma/src/app/components/Settings/getReplaySessionIdWithRetry.ts index 5ebd562ef..8baa68aa4 100644 --- a/packages/tokens-studio-for-figma/src/app/components/Settings/getReplaySessionIdWithRetry.ts +++ b/packages/tokens-studio-for-figma/src/app/components/Settings/getReplaySessionIdWithRetry.ts @@ -13,7 +13,9 @@ const DEFAULT_ATTEMPTS = 6; const DEFAULT_RETRY_DELAY_MS = 300; function wait(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); } async function getReplayId(replayController: ReplayController) {