Skip to content

fix(dashboard): catch up GitHub signal sync with cached query age#161

Merged
stylessh merged 4 commits intomainfrom
stylessh/c56dccdc
Apr 18, 2026
Merged

fix(dashboard): catch up GitHub signal sync with cached query age#161
stylessh merged 4 commits intomainfrom
stylessh/c56dccdc

Conversation

@stylessh
Copy link
Copy Markdown
Owner

@stylessh stylessh commented Apr 18, 2026

Summary

Fixes stale "My" / overview PR and issue lists after webhooks ran while the user was away. The signal poll previously seeded server timestamps on first sync without comparing them to React Query cache age, so durable D1 invalidations never triggered a refetch. WebSocket reconnect now runs the same catch-up immediately.

Changes

  • Compare server github_revalidation_signal timestamps to dataUpdatedAt on first sync per key
  • Run that sync after WebSocket connect (in addition to the 90s poll fallback)
  • Share lastSeen map between poll and WS; reset when subscribed signal keys change

Test Plan

  • Open dashboard overview with persisted React Query cache; confirm merged PRs drop from open lists after load when server signals are newer than cache
  • Reconnect WebSocket (tab background / network) and confirm lists refresh without waiting for poll interval

Screenshots

N/A (behavioral fix; no visual design change)

Before After
Lists could stay stale until manual refresh Lists refetch when server invalidation is newer than cached data

Summary by CodeRabbit

  • Bug Fixes

    • More reliable real-time signal synchronization with an initial catch-up on reconnect so missed updates are applied promptly.
  • Refactor

    • Unified timestamp tracking across streaming and polling; a single shared tracker resets when the set of monitored targets changes to ensure consistent invalidation.

Compare server revalidation timestamps to React Query dataUpdatedAt on first
sync and on WebSocket reconnect so offline webhook invalidations refetch lists.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 18, 2026

Warning

Rate limit exceeded

@stylessh has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 57 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 7 minutes and 57 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 21dfe730-e58b-4647-82bb-8549a6613442

📥 Commits

Reviewing files that changed from the base of the PR and between 62bc5cf and 1819771.

📒 Files selected for processing (1)
  • apps/dashboard/src/lib/use-github-signal-stream.ts
📝 Walkthrough

Walkthrough

Adds a shared lastSeenTimestampsRef, introduces signalStreamCompositeKey(...) and collectKeysToInvalidateAfterServerSync(...) to compute stale/missed signal keys, adds a WebSocket open-time catch-up that fetches server timestamps and invalidates computed keys, and updates polling to use the shared ref and helper; parent hook resets ref when targets identity changes. (≤50 words)

Changes

Cohort / File(s) Summary
Core hook & helpers
apps/dashboard/src/lib/use-github-signal-stream.ts
Added signalStreamCompositeKey(...) and collectKeysToInvalidateAfterServerSync(...); created/managed a shared lastSeenTimestampsRef in the parent hook and introduced mergedTargetsIdentity to reset it when target identity changes.
WebSocket flow
apps/dashboard/src/lib/use-github-signal-stream.ts
WebSocket effect now accepts lastSeenTimestampsRef; removed per-effect Map; performs async catch-up on open by calling getRevalidationSignalTimestamps(...), uses the helper to compute updatedKeys, and invalidates matching targets.
Polling flow
apps/dashboard/src/lib/use-github-signal-stream.ts
Polling effect refactored to use the shared lastSeenTimestampsRef and collectKeysToInvalidateAfterServerSync(...) instead of a local Map, then invalidates computed keys each poll.
Exports / API
apps/dashboard/src/lib/use-github-signal-stream.ts
No exported/public function or type signatures changed.

Sequence Diagram(s)

sequenceDiagram
    participant Hook as useGitHubSignalStream
    participant WS as WebSocket
    participant API as Server (getRevalidationSignalTimestamps)
    participant Poll as PollingTimer
    participant Invalidator as invalidateTargets

    Hook->>WS: open connection / subscribe
    WS->>API: request revalidation timestamps (on open)
    API-->>WS: [{signalKey, updatedAt}, ...]
    WS->>Hook: deliver server timestamps
    Hook->>Hook: collectKeysToInvalidateAfterServerSync(lastSeenTimestampsRef, serverTs, localDataUpdatedAt)
    Hook->>Invalidator: invalidate computed signalKeys
    Poll->>Hook: periodic poll reads lastSeenTimestampsRef
    Hook->>Hook: collectKeysToInvalidateAfterServerSync(...)
    Hook->>Invalidator: invalidate computed signalKeys
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: implementing catch-up logic for GitHub signal sync against cached query age.
Description check ✅ Passed The description follows the template structure with Summary, Changes, and Test Plan sections all completed with relevant technical details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch stylessh/c56dccdc

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/dashboard/src/lib/use-github-signal-stream.ts`:
- Around line 95-115: The current lastSeenTimestamps map keyed only by
signal.signalKey causes per-target first-sync checks to be skipped once any
target sets a lastSeen; update the logic to track first-sync state per target by
using a composite key (e.g. `${target.queryKey}:${signal.signalKey}`) or reset
entries when the targets/query set changes; modify usages around
lastSeenTimestamps, the loop over targets, and the branch that sets
lastSeenTimestamps (and replicate the same fix at the other occurrence around
lines 381-383) so the comparison against
queryClient.getQueryState(target.queryKey)?.dataUpdatedAt is performed per
target rather than globally per signalKey.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 22e6b674-c07d-48bb-81ed-3c01671ed3df

📥 Commits

Reviewing files that changed from the base of the PR and between d43e2d8 and 9847c5d.

📒 Files selected for processing (1)
  • apps/dashboard/src/lib/use-github-signal-stream.ts

Comment thread apps/dashboard/src/lib/use-github-signal-stream.ts Outdated
Resolve use-github-signal-stream: keep mergedTargets for subscriptions and
wire lastSeenTimestampsRef through poll/WebSocket catch-up.
Key lastSeen by queryKey+signalKey so new targets sharing pullsMine still get
catch-up vs dataUpdatedAt; reset baseline when merged target set changes.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/dashboard/src/lib/use-github-signal-stream.ts (1)

145-167: ⚠️ Potential issue | 🟠 Major

Track first-sync state per target, not just per signal key.

Line 146 keys lastSeenTimestamps only by signalKey, and Lines 438-440 only clear that map when the subscribed key set changes. If mergedTargets changes but reuses the same signal key, the new target skips the dataUpdatedAt catch-up path and can keep stale persisted data until the next GitHub update. Key this state by queryKey + signalKey, or reset it when the target set changes too.

Also applies to: 438-440

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/lib/use-github-signal-stream.ts` around lines 145 - 167,
The bug is that lastSeenTimestamps is keyed only by signal.signalKey so when
mergedTargets (or targets) changes but reuses the same signalKey a new target
won’t run the catch-up logic; update the usage of lastSeenTimestamps to key by a
composite key (e.g., `${queryKey}:${signalKey}`) wherever you call
get/set/has/clear in this file (including inside the signals loop and the place
that currently clears the map when subscribed keys change), or alternatively
clear/reset lastSeenTimestamps whenever mergedTargets/targets change so each
target will trigger the dataUpdatedAt catch-up; adjust references to
lastSeenTimestamps.get, .set and the clearing logic accordingly (search for
lastSeenTimestamps and mergedTargets to find all update points).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/dashboard/src/lib/use-github-signal-stream.ts`:
- Line 2: Replace the deprecated MutableRefObject import with RefObject in the
module import statement and update any type annotations that currently use
MutableRefObject<Map<string, number>> to RefObject<Map<string, number>> (notably
the ref declarations used inside the useGithubSignalStream hook); ensure the
hook and any functions that mutate the ref continue to access and assign via
.current so the unified RefObject type works correctly with React 19.

---

Duplicate comments:
In `@apps/dashboard/src/lib/use-github-signal-stream.ts`:
- Around line 145-167: The bug is that lastSeenTimestamps is keyed only by
signal.signalKey so when mergedTargets (or targets) changes but reuses the same
signalKey a new target won’t run the catch-up logic; update the usage of
lastSeenTimestamps to key by a composite key (e.g., `${queryKey}:${signalKey}`)
wherever you call get/set/has/clear in this file (including inside the signals
loop and the place that currently clears the map when subscribed keys change),
or alternatively clear/reset lastSeenTimestamps whenever mergedTargets/targets
change so each target will trigger the dataUpdatedAt catch-up; adjust references
to lastSeenTimestamps.get, .set and the clearing logic accordingly (search for
lastSeenTimestamps and mergedTargets to find all update points).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0ea8bdc9-619c-418f-9983-594a98395bac

📥 Commits

Reviewing files that changed from the base of the PR and between 9847c5d and 55648fb.

📒 Files selected for processing (1)
  • apps/dashboard/src/lib/use-github-signal-stream.ts

@@ -1,5 +1,5 @@
import { type QueryKey, useQueryClient } from "@tanstack/react-query";
import { useEffect, useMemo, useRef } from "react";
import { type MutableRefObject, useEffect, useMemo, useRef } from "react";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Declared React versions in package manifests:"
python - <<'PY'
import json
from pathlib import Path

for path in Path(".").rglob("package.json"):
    try:
        data = json.loads(path.read_text())
    except Exception:
        continue

    deps = {}
    for bucket in ("dependencies", "devDependencies", "peerDependencies"):
        deps.update(data.get(bucket, {}))

    react = deps.get("react")
    types = deps.get("@types/react")
    if react or types:
        print(f"{path}: react={react or 'n/a'}, `@types/react`={types or 'n/a'}")
PY

echo
echo "Current MutableRefObject usages:"
rg -n --type=ts --type=tsx '\bMutableRefObject\b'

Repository: stylessh/diffkit

Length of output: 376


🏁 Script executed:

cat -n apps/dashboard/src/lib/use-github-signal-stream.ts | head -200

Repository: stylessh/diffkit

Length of output: 6750


🏁 Script executed:

rg -n 'MutableRefObject' --type ts apps/dashboard/src/lib/

Repository: stylessh/diffkit

Length of output: 423


🏁 Script executed:

cat -n apps/dashboard/src/lib/use-github-signal-stream.ts | sed -n '170,200p; 330,360p'

Repository: stylessh/diffkit

Length of output: 1909


🌐 Web query:

React 19 MutableRefObject RefObject ref type changes

💡 Result:

In React 19, significant changes were made to ref types in TypeScript via @types/react updates to align with runtime improvements, particularly making refs mutable by default and simplifying useRef. Key changes: - useRef now requires an initial argument (e.g., useRef(null) or useRef(undefined)). The zero-argument form is removed. - RefObject now has mutable current: T (not readonly T | null). Previously, current was readonly T | null. - MutableRefObject is deprecated in favor of the unified RefObject, which useRef always returns. - For ref as a prop (new React 19 feature, reducing need for forwardRef), use React.Ref for the prop type (union of RefObject | ((instance: T | null) => void) | null | undefined). These changes improve type safety and reflect common usage patterns. Codemods like types-react-codemod refobject-defaults and useref-required-initial help migration. Examples: const ref = useRef(null); // RefObject, current: HTMLDivElement | null (but mutable) ref.current = someElement; // Now allowed, previously readonly error // For component props in React 19: interface Props { ref?: React.Ref; } function MyComponent({ ref, ...props }: Props) { return <div ref={ref} {...props} />; } Official React docs do not detail TypeScript types (runtime-focused), but upgrade guide mentions ref cleanups and useRef requiring argument. Changes implemented in @types/react for React 19 compatibility.

Citations:


Replace deprecated MutableRefObject with RefObject for React 19 compatibility.

MutableRefObject is deprecated in React 19 as of @types/react@^19.2.0. Refactor line 2's import and the type annotations on lines 187 and 349 to use RefObject<Map<string, number>> instead, which is the unified ref type in React 19 with mutable .current.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/lib/use-github-signal-stream.ts` at line 2, Replace the
deprecated MutableRefObject import with RefObject in the module import statement
and update any type annotations that currently use MutableRefObject<Map<string,
number>> to RefObject<Map<string, number>> (notably the ref declarations used
inside the useGithubSignalStream hook); ensure the hook and any functions that
mutate the ref continue to access and assign via .current so the unified
RefObject type works correctly with React 19.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
apps/dashboard/src/lib/use-github-signal-stream.ts (1)

2-2: ⚠️ Potential issue | 🟠 Major

Replace MutableRefObject with RefObject for React 19 typings.

Line 2, Line 187, and Line 349 still use MutableRefObject. With React 19 typings, this should be migrated to RefObject to avoid deprecation/compatibility drift.

Proposed diff
-import { type MutableRefObject, useEffect, useMemo, useRef } from "react";
+import { type RefObject, useEffect, useMemo, useRef } from "react";
...
 function useGitHubSignalStreamWebSocket(
 	targets: readonly GitHubSignalStreamTarget[],
 	signalKeysKey: string,
-	lastSeenTimestampsRef: MutableRefObject<Map<string, number>>,
+	lastSeenTimestampsRef: RefObject<Map<string, number>>,
 ) {
...
 function useGitHubSignalPoll(
 	targets: readonly GitHubSignalStreamTarget[],
 	signalKeysKey: string,
-	lastSeenTimestampsRef: MutableRefObject<Map<string, number>>,
+	lastSeenTimestampsRef: RefObject<Map<string, number>>,
 ) {

Use this read-only check to verify package typings + remaining usages:

#!/bin/bash
set -euo pipefail

python - <<'PY'
import json
from pathlib import Path

for path in Path(".").rglob("package.json"):
    try:
        data = json.loads(path.read_text())
    except Exception:
        continue
    deps = {}
    for bucket in ("dependencies", "devDependencies", "peerDependencies"):
        deps.update(data.get(bucket, {}))
    if "react" in deps or "@types/react" in deps:
        print(f"{path}: react={deps.get('react')}, `@types/react`={deps.get('@types/react')}")
PY

echo
echo "MutableRefObject usages:"
rg -n --type=ts --type=tsx '\bMutableRefObject\b'

Also applies to: 187-187, 349-349

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/lib/use-github-signal-stream.ts` at line 2, The code
imports and uses MutableRefObject which is deprecated in React 19 typings;
replace MutableRefObject with RefObject in the import from react and in all type
annotations inside this module (e.g., the import line and any
variables/parameters typed as MutableRefObject<T> around the
useGitHubSignalStream hook and related refs), changing signatures like
MutableRefObject<...> to RefObject<...> so the types align with React 19; ensure
you import RefObject from "react" and update any places that assume mutability
to use a currentRef access pattern (ref.current) if mutation is needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/dashboard/src/lib/use-github-signal-stream.ts`:
- Line 2: The code imports and uses MutableRefObject which is deprecated in
React 19 typings; replace MutableRefObject with RefObject in the import from
react and in all type annotations inside this module (e.g., the import line and
any variables/parameters typed as MutableRefObject<T> around the
useGitHubSignalStream hook and related refs), changing signatures like
MutableRefObject<...> to RefObject<...> so the types align with React 19; ensure
you import RefObject from "react" and update any places that assume mutability
to use a currentRef access pattern (ref.current) if mutation is needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1fa9e9da-b7cf-452d-a72e-866cced0a51e

📥 Commits

Reviewing files that changed from the base of the PR and between 55648fb and 62bc5cf.

📒 Files selected for processing (1)
  • apps/dashboard/src/lib/use-github-signal-stream.ts

@stylessh stylessh merged commit 13f9ff6 into main Apr 18, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant