@roj-ai/debug 0.1.12 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,20 +5,14 @@ interface EventPollingProviderProps {
5
5
  /**
6
6
  * Provider component that manages event polling for a session.
7
7
  *
8
- * When mounted, it loads the session's events and starts polling for new events.
9
- * When unmounted (e.g., navigating away), it stops polling and resets the store.
10
- *
11
- * Usage:
12
- * ```tsx
13
- * <EventPollingProvider sessionId={sessionId}>
14
- * <DebugViews />
15
- * </EventPollingProvider>
16
- * ```
8
+ * Polling is paused when the tab is hidden or the user has been idle for
9
+ * IDLE_TIMEOUT_MS. It resumes (and immediately catches up) on activity or
10
+ * tab refocus. Unmounting clears polling and resets the store.
17
11
  */
18
12
  export declare function EventPollingProvider({ sessionId, children }: EventPollingProviderProps): import("react/jsx-runtime").JSX.Element;
19
13
  /**
20
- * Hook to initialize event polling for a session.
21
- * Use this instead of EventPollingProvider if you want more control.
14
+ * Hook variant of EventPollingProvider for callers that need access to
15
+ * isLoading / error.
22
16
  */
23
17
  export declare function useEventPolling(sessionId: string | undefined): {
24
18
  isLoading: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"EventPollingProvider.d.ts","sourceRoot":"","sources":["../../src/providers/EventPollingProvider.tsx"],"names":[],"mappings":"AAGA,UAAU,yBAAyB;IAClC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CACzB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,yBAAyB,2CActF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS;;;EAkB5D"}
1
+ {"version":3,"file":"EventPollingProvider.d.ts","sourceRoot":"","sources":["../../src/providers/EventPollingProvider.tsx"],"names":[],"mappings":"AAMA,UAAU,yBAAyB;IAClC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,yBAAyB,2CAGtF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS;;;EAwE5D"}
@@ -1,49 +1,92 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect } from 'react';
3
3
  import { useEventStore } from '../stores/event-store.js';
4
+ const IDLE_TIMEOUT_MS = 2 * 60 * 1000;
5
+ const ACTIVITY_EVENTS = ['mousedown', 'mousemove', 'keydown', 'touchstart', 'scroll', 'wheel'];
4
6
  /**
5
7
  * Provider component that manages event polling for a session.
6
8
  *
7
- * When mounted, it loads the session's events and starts polling for new events.
8
- * When unmounted (e.g., navigating away), it stops polling and resets the store.
9
- *
10
- * Usage:
11
- * ```tsx
12
- * <EventPollingProvider sessionId={sessionId}>
13
- * <DebugViews />
14
- * </EventPollingProvider>
15
- * ```
9
+ * Polling is paused when the tab is hidden or the user has been idle for
10
+ * IDLE_TIMEOUT_MS. It resumes (and immediately catches up) on activity or
11
+ * tab refocus. Unmounting clears polling and resets the store.
16
12
  */
17
13
  export function EventPollingProvider({ sessionId, children }) {
18
- const loadSession = useEventStore((s) => s.loadSession);
19
- const reset = useEventStore((s) => s.reset);
20
- useEffect(() => {
21
- loadSession(sessionId);
22
- // Cleanup on unmount or sessionId change
23
- return () => {
24
- reset();
25
- };
26
- }, [sessionId, loadSession, reset]);
14
+ useEventPolling(sessionId);
27
15
  return _jsx(_Fragment, { children: children });
28
16
  }
29
17
  /**
30
- * Hook to initialize event polling for a session.
31
- * Use this instead of EventPollingProvider if you want more control.
18
+ * Hook variant of EventPollingProvider for callers that need access to
19
+ * isLoading / error.
32
20
  */
33
21
  export function useEventPolling(sessionId) {
34
22
  const loadSession = useEventStore((s) => s.loadSession);
35
23
  const reset = useEventStore((s) => s.reset);
24
+ const startPolling = useEventStore((s) => s.startPolling);
25
+ const stopPolling = useEventStore((s) => s.stopPolling);
26
+ const fetchNewEvents = useEventStore((s) => s.fetchNewEvents);
36
27
  const isLoading = useEventStore((s) => s.isLoading);
37
28
  const error = useEventStore((s) => s.error);
38
29
  useEffect(() => {
39
30
  if (!sessionId)
40
31
  return;
32
+ let idleTimer = null;
33
+ let isIdle = false;
34
+ let cancelled = false;
35
+ const reconcile = () => {
36
+ if (cancelled)
37
+ return;
38
+ const state = useEventStore.getState();
39
+ if (state.sessionId !== sessionId || state.isLoading)
40
+ return;
41
+ const shouldPoll = document.visibilityState === 'visible' && !isIdle;
42
+ if (shouldPoll && !state.isPolling) {
43
+ fetchNewEvents();
44
+ startPolling();
45
+ }
46
+ else if (!shouldPoll && state.isPolling) {
47
+ stopPolling();
48
+ }
49
+ };
50
+ const onActivity = () => {
51
+ if (idleTimer)
52
+ clearTimeout(idleTimer);
53
+ if (isIdle) {
54
+ isIdle = false;
55
+ reconcile();
56
+ }
57
+ idleTimer = setTimeout(() => {
58
+ isIdle = true;
59
+ reconcile();
60
+ }, IDLE_TIMEOUT_MS);
61
+ };
62
+ const onVisibility = () => reconcile();
63
+ for (const ev of ACTIVITY_EVENTS) {
64
+ window.addEventListener(ev, onActivity, { passive: true });
65
+ }
66
+ document.addEventListener('visibilitychange', onVisibility);
67
+ // Reconcile after the initial load finishes — loadSession auto-starts
68
+ // polling on success, so we need to immediately stop it if the tab is
69
+ // hidden / user is idle at that moment.
70
+ const unsubscribe = useEventStore.subscribe((state, prev) => {
71
+ if (state.sessionId !== sessionId)
72
+ return;
73
+ if (state.isLoading !== prev.isLoading)
74
+ reconcile();
75
+ });
76
+ onActivity(); // arm the idle timer
41
77
  loadSession(sessionId);
42
- // Cleanup on unmount or sessionId change
43
78
  return () => {
79
+ cancelled = true;
80
+ if (idleTimer)
81
+ clearTimeout(idleTimer);
82
+ for (const ev of ACTIVITY_EVENTS) {
83
+ window.removeEventListener(ev, onActivity);
84
+ }
85
+ document.removeEventListener('visibilitychange', onVisibility);
86
+ unsubscribe();
44
87
  reset();
45
88
  };
46
- }, [sessionId, loadSession, reset]);
89
+ }, [sessionId, loadSession, reset, startPolling, stopPolling, fetchNewEvents]);
47
90
  return { isLoading, error };
48
91
  }
49
92
  //# sourceMappingURL=EventPollingProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EventPollingProvider.js","sourceRoot":"","sources":["../../src/providers/EventPollingProvider.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAOxD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,oBAAoB,CAAC,EAAE,SAAS,EAAE,QAAQ,EAA6B;IACtF,MAAM,WAAW,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;IACvD,MAAM,KAAK,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;IAE3C,SAAS,CAAC,GAAG,EAAE;QACd,WAAW,CAAC,SAAS,CAAC,CAAA;QAEtB,yCAAyC;QACzC,OAAO,GAAG,EAAE;YACX,KAAK,EAAE,CAAA;QACR,CAAC,CAAA;IACF,CAAC,EAAE,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC,CAAA;IAEnC,OAAO,4BAAG,QAAQ,GAAI,CAAA;AACvB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,SAA6B;IAC5D,MAAM,WAAW,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;IACvD,MAAM,KAAK,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;IAC3C,MAAM,SAAS,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;IACnD,MAAM,KAAK,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;IAE3C,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,CAAC,SAAS;YAAE,OAAM;QAEtB,WAAW,CAAC,SAAS,CAAC,CAAA;QAEtB,yCAAyC;QACzC,OAAO,GAAG,EAAE;YACX,KAAK,EAAE,CAAA;QACR,CAAC,CAAA;IACF,CAAC,EAAE,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC,CAAA;IAEnC,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;AAC5B,CAAC"}
1
+ {"version":3,"file":"EventPollingProvider.js","sourceRoot":"","sources":["../../src/providers/EventPollingProvider.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roj-ai/debug",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {
@@ -30,13 +30,13 @@
30
30
  "type-check": "tsc --noEmit"
31
31
  },
32
32
  "dependencies": {
33
- "@roj-ai/client": "^0.1.12",
34
- "@roj-ai/shared": "^0.1.12",
33
+ "@roj-ai/client": "^0.1.14",
34
+ "@roj-ai/shared": "^0.1.14",
35
35
  "tokenx": "1.3.0",
36
36
  "zustand": "5.0.11"
37
37
  },
38
38
  "devDependencies": {
39
- "@roj-ai/sdk": "^0.1.12",
39
+ "@roj-ai/sdk": "^0.1.14",
40
40
  "@types/react": "19.2.14",
41
41
  "@types/react-dom": "19.2.3",
42
42
  "react": "19.2.4",
@@ -1,6 +1,9 @@
1
1
  import { useEffect } from 'react'
2
2
  import { useEventStore } from '../stores/event-store.js'
3
3
 
4
+ const IDLE_TIMEOUT_MS = 2 * 60 * 1000
5
+ const ACTIVITY_EVENTS = ['mousedown', 'mousemove', 'keydown', 'touchstart', 'scroll', 'wheel'] as const
6
+
4
7
  interface EventPollingProviderProps {
5
8
  sessionId: string
6
9
  children: React.ReactNode
@@ -9,52 +12,89 @@ interface EventPollingProviderProps {
9
12
  /**
10
13
  * Provider component that manages event polling for a session.
11
14
  *
12
- * When mounted, it loads the session's events and starts polling for new events.
13
- * When unmounted (e.g., navigating away), it stops polling and resets the store.
14
- *
15
- * Usage:
16
- * ```tsx
17
- * <EventPollingProvider sessionId={sessionId}>
18
- * <DebugViews />
19
- * </EventPollingProvider>
20
- * ```
15
+ * Polling is paused when the tab is hidden or the user has been idle for
16
+ * IDLE_TIMEOUT_MS. It resumes (and immediately catches up) on activity or
17
+ * tab refocus. Unmounting clears polling and resets the store.
21
18
  */
22
19
  export function EventPollingProvider({ sessionId, children }: EventPollingProviderProps) {
23
- const loadSession = useEventStore((s) => s.loadSession)
24
- const reset = useEventStore((s) => s.reset)
25
-
26
- useEffect(() => {
27
- loadSession(sessionId)
28
-
29
- // Cleanup on unmount or sessionId change
30
- return () => {
31
- reset()
32
- }
33
- }, [sessionId, loadSession, reset])
34
-
20
+ useEventPolling(sessionId)
35
21
  return <>{children}</>
36
22
  }
37
23
 
38
24
  /**
39
- * Hook to initialize event polling for a session.
40
- * Use this instead of EventPollingProvider if you want more control.
25
+ * Hook variant of EventPollingProvider for callers that need access to
26
+ * isLoading / error.
41
27
  */
42
28
  export function useEventPolling(sessionId: string | undefined) {
43
29
  const loadSession = useEventStore((s) => s.loadSession)
44
30
  const reset = useEventStore((s) => s.reset)
31
+ const startPolling = useEventStore((s) => s.startPolling)
32
+ const stopPolling = useEventStore((s) => s.stopPolling)
33
+ const fetchNewEvents = useEventStore((s) => s.fetchNewEvents)
45
34
  const isLoading = useEventStore((s) => s.isLoading)
46
35
  const error = useEventStore((s) => s.error)
47
36
 
48
37
  useEffect(() => {
49
38
  if (!sessionId) return
50
39
 
40
+ let idleTimer: ReturnType<typeof setTimeout> | null = null
41
+ let isIdle = false
42
+ let cancelled = false
43
+
44
+ const reconcile = () => {
45
+ if (cancelled) return
46
+ const state = useEventStore.getState()
47
+ if (state.sessionId !== sessionId || state.isLoading) return
48
+ const shouldPoll = document.visibilityState === 'visible' && !isIdle
49
+ if (shouldPoll && !state.isPolling) {
50
+ fetchNewEvents()
51
+ startPolling()
52
+ } else if (!shouldPoll && state.isPolling) {
53
+ stopPolling()
54
+ }
55
+ }
56
+
57
+ const onActivity = () => {
58
+ if (idleTimer) clearTimeout(idleTimer)
59
+ if (isIdle) {
60
+ isIdle = false
61
+ reconcile()
62
+ }
63
+ idleTimer = setTimeout(() => {
64
+ isIdle = true
65
+ reconcile()
66
+ }, IDLE_TIMEOUT_MS)
67
+ }
68
+
69
+ const onVisibility = () => reconcile()
70
+
71
+ for (const ev of ACTIVITY_EVENTS) {
72
+ window.addEventListener(ev, onActivity, { passive: true })
73
+ }
74
+ document.addEventListener('visibilitychange', onVisibility)
75
+
76
+ // Reconcile after the initial load finishes — loadSession auto-starts
77
+ // polling on success, so we need to immediately stop it if the tab is
78
+ // hidden / user is idle at that moment.
79
+ const unsubscribe = useEventStore.subscribe((state, prev) => {
80
+ if (state.sessionId !== sessionId) return
81
+ if (state.isLoading !== prev.isLoading) reconcile()
82
+ })
83
+
84
+ onActivity() // arm the idle timer
51
85
  loadSession(sessionId)
52
86
 
53
- // Cleanup on unmount or sessionId change
54
87
  return () => {
88
+ cancelled = true
89
+ if (idleTimer) clearTimeout(idleTimer)
90
+ for (const ev of ACTIVITY_EVENTS) {
91
+ window.removeEventListener(ev, onActivity)
92
+ }
93
+ document.removeEventListener('visibilitychange', onVisibility)
94
+ unsubscribe()
55
95
  reset()
56
96
  }
57
- }, [sessionId, loadSession, reset])
97
+ }, [sessionId, loadSession, reset, startPolling, stopPolling, fetchNewEvents])
58
98
 
59
99
  return { isLoading, error }
60
100
  }