Skip to content

feat: 添加 Fork 复刻管理页面#132

Merged
AmintaCCCP merged 11 commits intomainfrom
feature/fork-page
May 4, 2026
Merged

feat: 添加 Fork 复刻管理页面#132
AmintaCCCP merged 11 commits intomainfrom
feature/fork-page

Conversation

@AmintaCCCP
Copy link
Copy Markdown
Owner

@AmintaCCCP AmintaCCCP commented May 4, 2026

Summary

  • 在顶栏「发布」与「趋势」之间新增「复刻」(Forks) 标签页
  • Fork 列表按上游仓库最新更新时间降序排列
  • 每次点击刷新按钮时检测上游仓库是否有更新,如有更新则显示未读徽标
  • 用户点击仓库卡片或对仓库有任何操作时,未读徽标消失
  • 「资产」下拉替换为「工作流」下拉,可查看并触发运行工作流
  • 「同步上游仓库」按钮调用 GitHub merge upstream API 同步 Fork
  • 搜索与分页交互与 Release 页一致,无过滤器

Test plan

  • 切换到 Fork 标签页,点击刷新按钮拉取 Fork 列表
  • 确认 Fork 列表按上游仓库更新时间排序
  • 展开工作流下拉,确认能看到最近的工作流运行记录
  • 点击同步按钮,确认 Fork 与上游仓库同步
  • 搜索框搜索 Fork,确认过滤功能正常
  • 分页切换确认正常

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • New Forks view with navigation button to browse and manage your forked repositories.
    • Per-fork cards showing language badge, source/parent info, unread pulse, relative update times, and external repo link.
    • Expandable workflows per fork with loading/empty states, workflow status dots, Run actions, and per-fork running indicators.
    • "Sync upstream" action with progress and localized success/error toasts, search, pagination, refresh with last-refresh time, and persisted view/search/preferences.

- 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>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds 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)

Changes

Fork Management Feature

Layer / File(s) Summary
Type Definitions
src/types/index.ts
Adds ForkRepo and WorkflowDefinition; extends AppState.currentView with 'forks'; adds fork timeline view state fields (forks, readForks, forkViewMode, forkSelectedFilters, forkSearchQuery, forkExpandedRepositories, forkIsRefreshing).
Store surface & persistence
src/store/useAppStore.ts
Adds fork state to initial state and logout reset; new actions (setForks, addForks, updateFork, markForkAsRead, markAllForksAsRead, view/search/expansion controls); normalizes persisted fork fields during hydration; extends partialize to persist forks, serialized readForks, and fork view settings.
API Service
src/services/githubApi.ts
Adds getUserForks() (paged /user/repos?type=forks), syncFork(owner,repo,branch) (POST /merge_upstream with mapped error codes), getRepositoryWorkflows(owner,repo) (list workflows, returns [] on failure), and triggerWorkflowRun(owner,repo,workflowPath,branch) (dispatch workflow with URL-encoded path).
Core View implementation
src/components/ForkTimeline.tsx
New ForkTimeline component: sorts/filters/paginates forks; refresh merges fetched forks preserving unread/upstream metadata and updates read state; lazy-loads per-fork workflows; per-fork sync and workflow run flows with per-item loading state and localized toasts; full UI for empty/search/pagination.
Per-item UI
src/components/ForkCard.tsx
New ForkCard component: whole-card clickable to mark read, unread pulse indicator, header metadata and controls (toggle workflows, sync upstream with spinner/disabled logic, GitHub link), expandable workflows list with per-workflow Run buttons; event handlers stop propagation as needed.
View wiring & navigation
src/App.tsx, src/components/Header.tsx
Imports ForkTimeline, adds memoized ForksView and handles 'forks' in App.tsx view switch; Header.tsx adds GitFork icon and "Forks" navigation entries for desktop/tablet/mobile.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇
I hopped through forks and traced each trail,
Timelines opened where small workflows sail,
Spinners blinked, syncs hummed soft and bright,
Branches merged beneath the moonlight,
A rabbit cheers this tidy build tonight ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main feature added: a new Fork management page for displaying and managing user forks with related functionality.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 feature/fork-page

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.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

❤️ Share

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: 5

🧹 Nitpick comments (1)
src/components/ForkTimeline.tsx (1)

172-176: 💤 Low value

Using useAppStore.setState directly is unconventional but functional.

While this works, consider using the markForkAsRead action 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

📥 Commits

Reviewing files that changed from the base of the PR and between 37cc724 and c36979e.

📒 Files selected for processing (7)
  • src/App.tsx
  • src/components/ForkCard.tsx
  • src/components/ForkTimeline.tsx
  • src/components/Header.tsx
  • src/services/githubApi.ts
  • src/store/useAppStore.ts
  • src/types/index.ts

Comment thread src/components/ForkCard.tsx
Comment thread src/components/ForkTimeline.tsx
Comment thread src/services/githubApi.ts Outdated
Comment thread src/services/githubApi.ts Outdated
Comment thread src/store/useAppStore.ts Outdated
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>
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: 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 win

Reset the fork UI state on logout.

Only forks and readForks are cleared here. forkViewMode, forkSelectedFilters, forkSearchQuery, forkExpandedRepositories, and forkIsRefreshing are 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

📥 Commits

Reviewing files that changed from the base of the PR and between c36979e and 8164401.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • src/components/ForkCard.tsx
  • src/components/ForkTimeline.tsx
  • src/services/githubApi.ts
  • src/store/useAppStore.ts
  • src/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

Comment thread src/services/githubApi.ts Outdated
Comment thread src/services/githubApi.ts Outdated
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>
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

🧹 Nitpick comments (1)
src/components/ForkTimeline.tsx (1)

162-189: ⚡ Quick win

Side effects inside map callback is a code smell.

The map at line 162 triggers useAppStore.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

📥 Commits

Reviewing files that changed from the base of the PR and between 8164401 and dff2a75.

📒 Files selected for processing (4)
  • src/components/ForkCard.tsx
  • src/components/ForkTimeline.tsx
  • src/services/githubApi.ts
  • src/types/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/types/index.ts

Comment thread src/components/ForkTimeline.tsx Outdated
- 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>
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

🧹 Nitpick comments (1)
src/components/ForkCard.tsx (1)

111-114: ⚡ Quick win

Make 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. Calling onMarkAsRead here 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

📥 Commits

Reviewing files that changed from the base of the PR and between dff2a75 and 6afc7a3.

📒 Files selected for processing (3)
  • src/components/ForkCard.tsx
  • src/components/ForkTimeline.tsx
  • src/types/index.ts
✅ Files skipped from review due to trivial changes (2)
  • src/types/index.ts
  • src/components/ForkTimeline.tsx

Comment thread src/components/ForkCard.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>
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)
src/services/githubApi.ts (1)

992-995: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't stamp sourceUpdatedAt on a no-op sync.

sourceUpdatedAt is always set to now, even when merge_type === 'none'. That makes an already up-to-date fork look freshly synced and can skew any downstream “updated” ordering or badge logic. Return null unless hasUpdates is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6afc7a3 and 82f362b.

📒 Files selected for processing (3)
  • src/components/ForkCard.tsx
  • src/components/ForkTimeline.tsx
  • src/services/githubApi.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/ForkTimeline.tsx

Comment thread src/components/ForkCard.tsx
AmintaCCCP and others added 6 commits May 4, 2026 22:36
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>
@AmintaCCCP AmintaCCCP merged commit 7276357 into main May 4, 2026
5 checks passed
@AmintaCCCP AmintaCCCP deleted the feature/fork-page branch May 4, 2026 15:58
@AmintaCCCP AmintaCCCP linked an issue May 4, 2026 that may be closed by this pull request
@AmintaCCCP AmintaCCCP mentioned this pull request May 4, 2026
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