feat: 添加 Fork 复刻管理页面#132
Conversation
- Add Fork tab between Release and Trending tabs in the header - Create ForkTimeline component with search, refresh, and pagination - Create ForkCard component with workflow dropdown, sync upstream, and GitHub link - Add GitHub API methods: getUserForks, syncFork (merge upstream), getRepositoryWorkflows, triggerWorkflowRun - Add fork state and actions to Zustand store (forks, readForks, forkViewMode, forkSearchQuery, etc.) - Fork cards show upstream source info, sorted by upstream updated_at desc - Unread badges shown when upstream repo has new updates since last refresh - Mark forks as read on any user interaction (click, expand workflows, sync) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a Forks feature: new ForkTimeline and ForkCard UI, GitHub API methods for listing/syncing/running workflows on forks, store/types/state and persistence for fork timeline, and header/App wiring to surface the new 'forks' view. (49 words) ChangesFork Management Feature
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as ForkTimeline / ForkCard
participant Store as AppStore
participant API as GitHubApiService
participant Toasts
User->>UI: Click "Refresh"
UI->>Store: setForkIsRefreshing(true)
UI->>API: getUserForks()
API-->>UI: Fork list
UI->>Store: setForks(merged preserving has_unread/upstream)
UI->>Store: mutate readForks for upstream changes
UI->>Store: setForkIsRefreshing(false)
UI->>Toasts: show success or error
User->>UI: Click "Sync Upstream" on a ForkCard
UI->>Store: set per-fork syncing state
UI->>API: syncFork(owner,repo,branch)
API-->>UI: { hasUpdates, sourceUpdatedAt, mergeType? }
UI->>Store: updateFork(upstream_updated_at), markForkAsRead
UI->>Store: clear per-fork syncing state
UI->>Toasts: show sync result
User->>UI: Expand Workflows -> Click "Run" on workflow
UI->>API: triggerWorkflowRun(owner,repo,workflowPath,branch)
API-->>UI: success
UI->>API: getRepositoryWorkflows(owner,repo) (reload)
UI->>Store: (local) update workflowsMap for fork
UI->>Toasts: show run result
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
src/components/ForkTimeline.tsx (1)
172-176: 💤 Low valueUsing
useAppStore.setStatedirectly is unconventional but functional.While this works, consider using the
markForkAsReadaction you already destructured (line 19) for consistency. The direct setState pattern makes it harder to track state changes and test the component.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ForkTimeline.tsx` around lines 172 - 176, Replace the direct use of useAppStore.setState that mutates readForks with the existing action you destructured (markForkAsRead) to keep state changes centralized; instead of creating a new Set and deleting fork.id, call markForkAsRead(fork.id) (or markForkAsRead(fork.id, false) if the action expects an explicit read/unread flag) so the store's action handles updating readForks and keeps behavior consistent and testable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/ForkCard.tsx`:
- Around line 211-215: The button handler is passing workflow.id (a WorkflowRun
run ID) to onRunWorkflow which must receive the workflow definition ID or
filename; update the data shape and call to use the definition identifier
instead of the run ID: extend the WorkflowRun type to include the workflow
definition id or filename (e.g., workflow_definition_id or path/filename) when
you fetch workflow runs, populate that field in the fetch logic, and change the
button handler to call onRunWorkflow with that new property (e.g.,
onRunWorkflow(String(workflow.workflow_definition_id) or workflow.path)) and
still pass workflow.name and workflow.head_branch; alternatively, if you cannot
include it in the fetch, make the handler perform a lightweight query to
retrieve the workflow definition id/filename for the given run before invoking
onRunWorkflow.
In `@src/components/ForkTimeline.tsx`:
- Around line 244-246: The code uses a non-null assertion githubToken! inside
loadWorkflows (called from toggleWorkflows), which can throw if loadWorkflows is
invoked when githubToken is null; fix by removing the non-null assertion and
making loadWorkflows either accept githubToken as a required parameter or
explicitly guard for null at the top of loadWorkflows (e.g., if (!githubToken)
return/throw a clear error), then update callers (handleRefresh and
toggleWorkflows) to pass the token in or rely on the guard; reference symbols:
githubToken, loadWorkflows, toggleWorkflows, handleRefresh, and
GitHubApiService.
In `@src/services/githubApi.ts`:
- Around line 967-986: The syncFork function currently hardcodes ref: 'main'
which will fail for repos with other default branches; update syncFork to accept
a branch parameter (e.g., syncFork(owner: string, repo: string, branch?:
string)) or fetch the fork's default branch before calling makeRequest; use the
provided/queried branch value for the POST body instead of 'main' and ensure
callers pass fork.default_branch (add it to the ForkRepo type if needed) or that
syncFork queries the repo endpoint to derive default_branch prior to calling
makeRequest(`/repos/${owner}/${repo}/merge_upstream`, ...).
- Around line 1000-1008: The triggerWorkflowRun method declares Promise<{ id:
number }> but the workflow_dispatch endpoint returns 204 No Content; change the
triggerWorkflowRun signature in githubApi.ts to return Promise<void> and update
its call to this.makeRequest so it doesn't expect a response body (or adjust
makeRequest generics accordingly); optionally, if you need run details, add an
optional parameter (e.g., returnRunDetails?: boolean) that when true requests
the new return_run_details behavior and returns the appropriate typed response
object instead of void—also ensure callers like ForkTimeline.tsx are not
expecting an { id } return value.
In `@src/store/useAppStore.ts`:
- Around line 699-700: Remove the duplicate property declarations for readForks
and forks in the useAppStore object by deleting the earlier entries readForks:
new Set<number>() and forks: [], leaving the later correct declarations (the
intended readForks and forks properties) intact so the object has only one
definition of each key.
---
Nitpick comments:
In `@src/components/ForkTimeline.tsx`:
- Around line 172-176: Replace the direct use of useAppStore.setState that
mutates readForks with the existing action you destructured (markForkAsRead) to
keep state changes centralized; instead of creating a new Set and deleting
fork.id, call markForkAsRead(fork.id) (or markForkAsRead(fork.id, false) if the
action expects an explicit read/unread flag) so the store's action handles
updating readForks and keeps behavior consistent and testable.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1f97e28c-0fbd-4694-b031-cf75fb745f0f
📒 Files selected for processing (7)
src/App.tsxsrc/components/ForkCard.tsxsrc/components/ForkTimeline.tsxsrc/components/Header.tsxsrc/services/githubApi.tssrc/store/useAppStore.tssrc/types/index.ts
1. Fix API: /user/forks endpoint doesn't exist, switch to GitHub search API
with user:{login}+fork:true query to find all forks for the authenticated user
2. Fix WorkflowRun interface: add path (workflow file path) and workflow_id
(definition ID) fields returned by the GitHub API's expand=run parameter
3. Fix ForkCard: pass workflow.path (file path like ".github/workflows/ci.yml")
instead of run ID to onRunWorkflow, since dispatch API accepts file path
4. Fix handleRunWorkflow in ForkTimeline: accept workflowPath instead of
workflowId, pass fork.default_branch to syncFork instead of hardcoded 'main'
5. Fix loadWorkflows: add !githubToken guard to prevent runtime error from
non-null assertion
6. Fix syncFork: accept branch parameter instead of hardcoding 'main',
update caller to pass fork.default_branch || 'main'
7. Fix triggerWorkflowRun: change return type to void (API returns 204)
8. Fix useAppStore duplicate properties: remove duplicate readForks/forks
declarations at lines 699-700 that were shadowed by later declarations
9. Add default_branch to ForkRepo interface
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/store/useAppStore.ts (1)
764-777:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReset the fork UI state on logout.
Only
forksandreadForksare cleared here.forkViewMode,forkSelectedFilters,forkSearchQuery,forkExpandedRepositories, andforkIsRefreshingare still persisted, so the next login can inherit the previous user's fork page state and even land on an empty filtered view.🔧 Proposed fix
logout: () => set({ user: null, githubToken: null, isAuthenticated: false, repositories: [], releases: [], releaseSubscriptions: new Set(), readReleases: new Set(), forks: [], readForks: new Set(), + forkViewMode: 'timeline', + forkSelectedFilters: [], + forkSearchQuery: '', + forkExpandedRepositories: new Set(), + forkIsRefreshing: false, analyzingRepositoryIds: new Set(), searchResults: [], lastSync: null, }),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/store/useAppStore.ts` around lines 764 - 777, The logout handler (logout in useAppStore.ts) resets many user fields but omits fork UI state, causing state leakage between users; update the logout setter to also reset forkViewMode, forkSelectedFilters, forkSearchQuery, forkExpandedRepositories, and forkIsRefreshing to their default values (same defaults used when store initializes) so the fork page UI is cleared on logout.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/services/githubApi.ts`:
- Around line 1020-1023: The triggerWorkflowRun method builds the workflow
dispatch URL using the raw workflowId which breaks paths containing slashes;
update triggerWorkflowRun to URL-encode the workflowId before inserting it into
the path (use a URL-encoding utility such as encodeURIComponent) so both numeric
IDs and file-path identifiers like .github/workflows/ci.yml are safely supported
when calling /repos/{owner}/{repo}/actions/workflows/{workflowId}/dispatches.
- Around line 986-1004: In syncFork, don't assume success means updates; capture
the response from makeRequest (the object that includes merge_type), inspect
merge_type and set hasUpdates = true only when merge_type is "merge" or
"fast-forward" (hasUpdates = false when "none"), and return sourceUpdatedAt as
new Date().toISOString() only when hasUpdates is true (otherwise null). Also
update the error handling/comment: do not treat HTTP 409 as "already up to
date"—leave 409 to be thrown as an error. Use the existing syncFork function and
the makeRequest call to obtain and inspect merge_type.
---
Outside diff comments:
In `@src/store/useAppStore.ts`:
- Around line 764-777: The logout handler (logout in useAppStore.ts) resets many
user fields but omits fork UI state, causing state leakage between users; update
the logout setter to also reset forkViewMode, forkSelectedFilters,
forkSearchQuery, forkExpandedRepositories, and forkIsRefreshing to their default
values (same defaults used when store initializes) so the fork page UI is
cleared on logout.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 49924e29-c698-4b66-852b-538d338845fc
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (5)
src/components/ForkCard.tsxsrc/components/ForkTimeline.tsxsrc/services/githubApi.tssrc/store/useAppStore.tssrc/types/index.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- src/types/index.ts
- src/components/ForkCard.tsx
- src/components/ForkTimeline.tsx
1. Fork API: switch from search API to GET /user/repos?type=forks
— only returns actual forks, excludes repos user created themselves
2. syncFork: distinguish NOT_A_FORK (404) and MERGE_CONFLICT (409) errors
with appropriate user-facing messages; merge_type=none is handled as
"already up to date"
3. getRepositoryWorkflows: now calls GET /repos/{owner}/{repo}/actions/workflows
(lists workflow *definitions/files*) instead of the runs endpoint
4. triggerWorkflowRun: URL-encode the workflow file path (e.g.
.github/workflows/ci.yml) since it contains slashes
5. WorkflowDefinition type replaces WorkflowRun: fields are now
path, name, state, id, created_at, updated_at, url, html_url, badge_url
6. ForkCard: sync button disabled (grayed, non-clickable) when repo is not
a fork; shows "非Fork" badge; confirm dialog before sync
7. ForkCard workflow list: shows workflow files with state badge (active/
disabled), file path, and Run button; disabled workflows cannot be triggered
8. handleRunWorkflow: dispatches on fork.default_branch, no longer
requires branch parameter in prop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/components/ForkTimeline.tsx (1)
162-189: ⚡ Quick winSide effects inside
mapcallback is a code smell.The
mapat line 162 triggersuseAppStore.setState(lines 172-176) as a side effect during the transformation. While this works due to React 18's automatic batching, placing state mutations inside pure transformation functions is generally discouraged—it can cause unexpected behavior in React's Strict Mode (double-invocation) and makes the code harder to reason about.Consider separating the update detection from the state mutation:
♻️ Proposed refactor
- // Check for upstream updates on existing forks - mark as unread if source has newer commits - const updatedForks = mergedForks.map(fork => { + // Check for upstream updates on existing forks + const forksToMarkUnread: number[] = []; + const updatedForks = mergedForks.map(fork => { const existing = existingForkMap.get(fork.id); if (existing) { - // Compare: if source updated since last check, mark as unread const prevUpstreamTime = existing.upstream_updated_at; const currentUpstreamTime = fork.source?.updated_at; if (prevUpstreamTime && currentUpstreamTime) { const hasNewUpdates = new Date(currentUpstreamTime) > new Date(prevUpstreamTime); if (hasNewUpdates) { - // Mark as unread by removing from readForks - useAppStore.setState(state => { - const newReadForks = new Set(state.readForks); - newReadForks.delete(fork.id); - return { readForks: newReadForks }; - }); + forksToMarkUnread.push(fork.id); return { ...fork, upstream_updated_at: currentUpstreamTime, }; } } return { ...fork, upstream_updated_at: existing.upstream_updated_at || fork.source?.updated_at, }; } return fork; }); + + // Batch mark as unread after the transformation + if (forksToMarkUnread.length > 0) { + useAppStore.setState(state => { + const newReadForks = new Set(state.readForks); + forksToMarkUnread.forEach(id => newReadForks.delete(id)); + return { readForks: newReadForks }; + }); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ForkTimeline.tsx` around lines 162 - 189, The map callback for mergedForks (creating updatedForks) contains a side effect calling useAppStore.setState when detecting hasNewUpdates; extract detection into a pure pass to collect ids to mark unread, then after the map call perform a single useAppStore.setState update to remove those ids from state.readForks. Specifically: in the mergedForks.map/updatedForks logic (references: mergedForks.map, updatedForks, existing.upstream_updated_at, fork.source?.updated_at), only compute and return the updated fork objects there (no state mutation), accumulate fork.id values where new Date(fork.source?.updated_at) > new Date(existing.upstream_updated_at), and after building updatedForks call useAppStore.setState once to update readForks (create new Set, delete each collected id, return { readForks: newSet }).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/ForkTimeline.tsx`:
- Line 567: The Tailwind class `border-black/[0.06]-alt` in the div inside
ForkTimeline.tsx is invalid; update the className on that div (the one with
"text-center py-12 bg-light-bg ...") to remove the `-alt` suffix and use a valid
Tailwind opacity class such as `border-black/[0.06]` (keep the existing
`dark:border-white/[0.04]`), so the border renders correctly.
---
Nitpick comments:
In `@src/components/ForkTimeline.tsx`:
- Around line 162-189: The map callback for mergedForks (creating updatedForks)
contains a side effect calling useAppStore.setState when detecting
hasNewUpdates; extract detection into a pure pass to collect ids to mark unread,
then after the map call perform a single useAppStore.setState update to remove
those ids from state.readForks. Specifically: in the
mergedForks.map/updatedForks logic (references: mergedForks.map, updatedForks,
existing.upstream_updated_at, fork.source?.updated_at), only compute and return
the updated fork objects there (no state mutation), accumulate fork.id values
where new Date(fork.source?.updated_at) > new
Date(existing.upstream_updated_at), and after building updatedForks call
useAppStore.setState once to update readForks (create new Set, delete each
collected id, return { readForks: newSet }).
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 798323de-a93a-42d3-82fc-00cb2e217b1f
📒 Files selected for processing (4)
src/components/ForkCard.tsxsrc/components/ForkTimeline.tsxsrc/services/githubApi.tssrc/types/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/types/index.ts
- Change title from "复刻时间线"/"Fork Timeline" to "复刻"/"Fork" - Fix isFork check: also check fork.fork boolean (always present in GitHub API) - Add fork: boolean field to ForkRepo type - Add isRunningWorkflow state with Loader2 spinner on run workflow button - Add setRunningWorkflows state to ForkTimeline with finally block Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/components/ForkCard.tsx (1)
111-114: ⚡ Quick winMake unread-clearing explicit on all fork operations.
At Line 111, Line 131, and Line 224, these actions stop propagation and don’t call
onMarkAsRead. This leaves unread-clearing behavior dependent on parent callback internals. CallingonMarkAsReadhere makes the requirement (“any operation clears unread”) explicit and resilient.Suggested fix
onClick={(e) => { e.stopPropagation(); + onMarkAsRead(); onToggleWorkflows(); }} @@ onClick={(e) => { e.stopPropagation(); + onMarkAsRead(); onSyncUpstream(); }} @@ onClick={(e) => { e.stopPropagation(); + onMarkAsRead(); onRunWorkflow(workflow.path, workflow.name); }}Also applies to: 131-134, 224-227
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ForkCard.tsx` around lines 111 - 114, The onClick handlers that call e.stopPropagation() and invoke actions like onToggleWorkflows (and similar handlers at the other two occurrences) must also explicitly call onMarkAsRead so unread state is cleared regardless of parent behavior; update the three handlers (the one invoking onToggleWorkflows and the other handlers at the two noted locations) to call onMarkAsRead() in each onClick after e.stopPropagation() and alongside the existing action invocation to make unread-clearing explicit and resilient.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/ForkCard.tsx`:
- Around line 223-234: The run-workflow icon button currently relies only on
title for accessibility; update the button rendered in the ForkCard component
(the element using onRunWorkflow with workflow.path and workflow.name) to
include an explicit aria-label that matches the localized title logic (use
language to choose between 'Run workflow' / '运行工作流' or 'Workflow disabled' /
'工作流已禁用' when workflow.state === 'disabled'), so screen readers get a clear
action name in addition to the title and disabled handling.
---
Nitpick comments:
In `@src/components/ForkCard.tsx`:
- Around line 111-114: The onClick handlers that call e.stopPropagation() and
invoke actions like onToggleWorkflows (and similar handlers at the other two
occurrences) must also explicitly call onMarkAsRead so unread state is cleared
regardless of parent behavior; update the three handlers (the one invoking
onToggleWorkflows and the other handlers at the two noted locations) to call
onMarkAsRead() in each onClick after e.stopPropagation() and alongside the
existing action invocation to make unread-clearing explicit and resilient.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 328e6832-2146-4dd1-8f8e-a311caf1dddf
📒 Files selected for processing (3)
src/components/ForkCard.tsxsrc/components/ForkTimeline.tsxsrc/types/index.ts
✅ Files skipped from review due to trivial changes (2)
- src/types/index.ts
- src/components/ForkTimeline.tsx
- ForkCard: remove "非Fork" label entirely - ForkCard: show "Forked from Owner/Repo" with clickable link to upstream - ForkCard: add aria-label for run workflow button - ForkTimeline: use fork.fork instead of parent/source for sync guard - ForkTimeline: fix invalid Tailwind class border-black/[0.06]-alt - githubApi: handle HTTP 422 as "already up to date" (none merge type) GitHub returns 422 when branch is already up-to-date, not 409 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/services/githubApi.ts (1)
992-995:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDon't stamp
sourceUpdatedAton a no-op sync.
sourceUpdatedAtis always set tonow, even whenmerge_type === 'none'. That makes an already up-to-date fork look freshly synced and can skew any downstream “updated” ordering or badge logic. ReturnnullunlesshasUpdatesis true.🔧 Suggested fix
- return { - hasUpdates: result.merge_type !== 'none', - sourceUpdatedAt: new Date().toISOString(), - mergeType: result.merge_type, - }; + const hasUpdates = result.merge_type !== 'none'; + return { + hasUpdates, + sourceUpdatedAt: hasUpdates ? new Date().toISOString() : null, + mergeType: result.merge_type, + };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/services/githubApi.ts` around lines 992 - 995, The returned object always stamps sourceUpdatedAt to now even when no merge occurred; update the code that constructs the return (the object containing hasUpdates, sourceUpdatedAt, mergeType) to set sourceUpdatedAt to null when result.merge_type === 'none' (i.e. when hasUpdates is false) and only set it to new Date().toISOString() when result.merge_type !== 'none'; keep hasUpdates: result.merge_type !== 'none' and mergeType: result.merge_type unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/ForkCard.tsx`:
- Around line 125-142: The unread badge isn't cleared by interaction handlers
besides the outer card and GitHub link; update the action handlers (e.g., the
onClick handler that calls onToggleWorkflows, the sync upstream handler, and the
run-workflow handler) to also clear the unread state by invoking onMarkAsRead()
(or the same internal routine that sets isUnread=false) immediately after
calling e.stopPropagation(), ensuring the existing stopPropagation behavior
remains; apply the same change to the other similar blocks mentioned (the button
blocks around the other ranges) so any user interaction that opens workflows,
syncs upstream, or runs a workflow clears the unread badge.
---
Duplicate comments:
In `@src/services/githubApi.ts`:
- Around line 992-995: The returned object always stamps sourceUpdatedAt to now
even when no merge occurred; update the code that constructs the return (the
object containing hasUpdates, sourceUpdatedAt, mergeType) to set sourceUpdatedAt
to null when result.merge_type === 'none' (i.e. when hasUpdates is false) and
only set it to new Date().toISOString() when result.merge_type !== 'none'; keep
hasUpdates: result.merge_type !== 'none' and mergeType: result.merge_type
unchanged.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: ac9eecf2-bd29-4103-8b8d-f43a8c233f29
📒 Files selected for processing (3)
src/components/ForkCard.tsxsrc/components/ForkTimeline.tsxsrc/services/githubApi.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/ForkTimeline.tsx
User-reported BUG fixes:
1. Filter only fork repos: filteredForks now filters fork.fork===true
(removes self-created repos from list, no "非Fork" label needed)
2. Show "Forked from Owner/Repo" with clickable link to upstream repo
3. Sync button disabled by needsSync state (via checkForkSyncNeeded API)
Only enabled when GitHub returns non-422 (fork is out-of-date)
4. Confirm dialog: title "Update branch", message "Update {repo}'s
{branch} branch by merging upstream changes?" per GitHub UI copy
5. Clear unread badge on: toggle workflows, sync, run workflow (stopPropagation
handlers now call onMarkAsRead)
Also add checkForkSyncNeeded() to githubApi.ts — calls merge_upstream,
returns true if not-422 (out-of-date), false if 422 (already up-to-date).
Pre-check runs on refresh for all forks in parallel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit