Skip to content

feat: 支持 Gist 管理 (#222)#223

Merged
AmintaCCCP merged 9 commits into
mainfrom
codex/add-gist-support-222
Jun 18, 2026
Merged

feat: 支持 Gist 管理 (#222)#223
AmintaCCCP merged 9 commits into
mainfrom
codex/add-gist-support-222

Conversation

@AmintaCCCP

@AmintaCCCP AmintaCCCP commented Jun 17, 2026

Copy link
Copy Markdown
Owner

概述

实现 #222 提出的 Gist 支持需求,新增独立的 Gist 管理标签页。

需求对照

# 需求 实现
1 顶栏新增 Gist 标签 `Header.tsx` 在「仓库」右侧加入 Gist 入口,支持自适应换行(窄屏降级为图标)和徽标计数;页面最大宽度由 1200px 提升到 1280px 容纳新标签
2 左侧三类分类 `GistView.tsx` 提供「全部 gist / 星标 gist / 我的 gist」三个分类,含分类计数
3 卡片展示 + AI 搜索/分析 + 排序 `GistCard.tsx` 卡片化展示,搜索栏支持普通搜索与 AI 语义重排序搜索;排序支持更新/创建/名称/文件数 + 升降序,与仓库一致
4 AI 分析显示摘要 单卡片和批量两种入口,分析结果以 `ai_summary` 展示在卡片和详情弹窗
5 详情弹窗按后缀高亮 `GistDetailModal.tsx` 使用 highlight.js + 后缀映射 (`gistUtils.ts`) 自动解析语言
6 星标 gist 显示创建者/更新时间/复制链接/打开/取消收藏 `GistCard.tsx` 完整实现
7 我的 gist 增删改查 `GistEditorModal.tsx` 支持新建/编辑/多文件/删除文件,调用 `githubApi` 的 CRUD

主要改动

  • 新增组件: `GistView` / `GistCard` / `GistDetailModal` / `GistEditorModal`
  • 工具函数: `gistUtils.ts`(标题/语言推断/分类合并/过滤排序)
  • API 层: `githubApi.ts` 新增 Gist 的 CRUD、star/unstar、分页拉取、元数据保留合并
  • AI 层: `aiService.ts` 新增 `analyzeGist` 与 `searchGistsWithReranking`
  • Store: `useAppStore.ts` 新增 gists / starredGists / 过滤器 / 分析中等状态,并持久化;refetch 时保留 AI 摘要等本地元数据
  • 类型: `types/index.ts` 新增 `Gist` / `GistFile` / `GistCategoryId` / `GistSearchFilters`

审计修复

在审查过程中发现并修复两个问题:

  1. AI 搜索结果被立即覆盖: `GistView` 原有的 `useEffect` 监听 `gistSearchFilters`,导致 `aiSearch` 设置 query 后触发 effect,把 AI 重排序结果用本地排序覆盖。引入 `aiRerankedRef` 跳过紧接着的一次重算。
  2. 批量分析缺少 AI 配置完整性校验: `analyzeVisibleGists` 与单卡片分析不一致,补齐 baseUrl/apiKey/model/apiKeyStatus 校验。

验证

  • `npm run test:run`: 9 个测试文件 / 80 个用例全部通过(含同步更新的 `ForkTimeline.test.tsx` 中 toast 类型断言)
  • `npx tsc --noEmit`: 通过
  • `npx eslint`: 通过
  • `npm run build`: 通过

Closes #222

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a dedicated Gists view with desktop/tablet/mobile navigation, badge counts, search (basic + AI reranking), sorting, and refresh/sync.
    • Introduced gist cards and modals for viewing, creating/editing, syntax-highlighted code, and copy/share actions.
    • Enabled AI-assisted gist analysis, plus star/unstar, edit, and delete with inline feedback.
    • Updated README documentation to highlight Gist Management.
  • Bug Fixes

    • Corrected toast severity for gist/organization loading failures.

- Add Gist tab in header between Repositories and Releases with adaptive
  text-wrap layout and badge count; widen page max-width to 1280px
- Implement GistView with three categories (all/starred/mine), AI search
  with reranking, AI batch analysis, sort controls, and create/edit flow
- Add GistCard with AI analyze, copy link, open link, unstar, edit, delete
- Add GistDetailModal with syntax-highlighted file viewer (highlight.js)
- Add GistEditorModal for full create/update/delete-file operations
- Extend GitHubApiService with gist CRUD, star/unstar, pagination
- Extend AIService with analyzeGist and searchGistsWithReranking
- Persist gists/starredGists/filters/category in zustand store with
  metadata preservation on refetch
- Fix AI search results being overwritten by the filter effect via
  rerank-skip ref; add AI config completeness check to batch analysis
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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 comprehensive GitHub Gists management: new type contracts for gists and search filters, utility helpers for formatting and filtering, GitHub API CRUD and star/unstar operations, AI gist analysis and search reranking, Zustand store with persistence, and UI components (GistCard, detail/editor modals, GistView) integrated into navigation and routing.

Changes

GitHub Gists Management Feature

Layer / File(s) Summary
Gist types and data contracts
src/types/index.ts
Adds GistFile, Gist, GistCategoryId, and GistSearchFilters exported types. Extends AppState with gist collections, search filters, results, category selection, and analyzing IDs. Updates currentView to include 'gists' view.
Gist utility and formatting helpers
src/utils/gistUtils.ts
Implements title derivation with fallbacks, file count, primary language extraction, and code language inference from filenames. Provides category-based merging/deduplication/filtering and multi-field search with sorting by creation, name, file count, or update date.
GitHub API gist operations
src/services/githubApi.ts
Adds paginated getAllGists/getAllStarredGists with mergeGistMetadata to preserve analysis state, getGist for single fetch, createGist/updateGist with file deletion/rename support, deleteGist, starGist/unstarGist, and getGistContentPreview for truncated preview. Updates OAuth scope to request gist access.
AI service gist analysis and search reranking
src/services/aiService.ts
Adds analyzeGist for language-specific AI summaries from gist metadata and content preview. Adds searchGistsWithReranking to reorder gists by AI-ranked JSON ID list with fallback to original order on parse error.
Zustand store gist state and actions
src/store/useAppStore.ts
Extends store with gist collections, search state, category selection, and analysis tracking. Implements equality checks, state actions, rehydration normalization (normalizeStringSet, category validation, gist list merging), initial state, logout reset, and persistence partializer for IndexedDB.
GistCard component with interactions
src/components/GistCard.tsx
Renders card displaying gist metadata, owner, update time, file count, primary language, visibility, and optional AI summary. Supports AI analysis (with token/config validation, overwrite prompt, state tracking, and toasts), unstar (confirmation/API/callback), delete (ownership-gated, irreversible confirmation), and copy-link actions. Shows spinner during analysis and error banner on failure.
GistDetailModal and GistEditorModal components
src/components/GistDetailModal.tsx, src/components/GistEditorModal.tsx
GistDetailModal displays metadata, AI summary, file tabs, and syntax-highlighted code via highlight.js with fallback error handling; supports copying gist link and file content. GistEditorModal manages description, public flag (create-only), soft-deletable file list with identity tracking, unique filename validation, and create-vs-edit payload branching.
GistView orchestration component
src/components/GistView.tsx
Computes category lists, syncs search results with AI reranking guard. Implements refreshGists (concurrent fetch), basicSearch/aiSearch (AI reranking with fallback), analyzeVisibleGists (concurrency-limited batch), openDetail (request deduplication), and handleSubmitGist (create/update routing). Renders sidebar, search/sort, batch-analyze/sync buttons, card grid, empty state, and wired modals.
Header navigation gist button and routing
src/components/Header.tsx
Computes gistCount from unique gist IDs. Adds desktop navigation button with badge, tablet icon-only button, and mobile dropdown item for the gists view using FileCode2 icon.
App routing and container layout
src/App.tsx
Imports and registers memoized GistsView wrapper. Adds gists case to currentView switch. Widens main container from max-w-[1200px] to max-w-[1280px].
OAuth scope and login documentation update
src/components/LoginScreen.tsx
Updates token creation instructions to include gist scope alongside repo and user.
ForkTimeline test toast severity update
src/components/ForkTimeline.test.tsx
Updates organization load failure test to assert toastMock called with 'error' instead of 'warning'.
User-facing documentation updates
README.md, README_zh.md
Adds "Gist Management" entry to feature table and dedicated "5. Gist Management (Gist View)" section with feature list and screenshot; renumbers subsequent sections accordingly.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(100, 150, 255, 0.5)
    Note over User,useAppStore: Gist Sync Flow
    User->>GistView: clicks Sync from GitHub
    GistView->>GitHubApiService: getAllGists(existingGists)
    GitHubApiService->>GitHub: GET /gists paginated
    GitHub-->>GitHubApiService: Gist[] pages
    GitHubApiService-->>GistView: merged Gist[] (analysis metadata preserved)
    GistView->>GitHubApiService: getAllStarredGists(existingGists)
    GitHub-->>GitHubApiService: starred Gist[] pages
    GitHubApiService-->>GistView: merged starred Gist[]
    GistView->>useAppStore: setGists / setStarredGists
  end

  rect rgba(255, 150, 100, 0.5)
    Note over User,useAppStore: AI Batch Analysis Flow
    User->>GistView: clicks Analyze Visible Gists
    loop each target gist (concurrency limited)
      GistView->>GitHubApiService: getGist(gistId)
      GitHubApiService-->>GistView: Gist with file contents
      GistView->>GitHubApiService: getGistContentPreview(gist)
      GitHubApiService-->>GistView: preview string
      GistView->>AIService: analyzeGist(gist, preview)
      AIService->>AI Provider: requestText(prompt)
      AI Provider-->>AIService: ai_summary
      AIService-->>GistView: summary string
      GistView->>useAppStore: updateGist({ai_summary, analyzed_at})
    end
  end

  rect rgba(100, 255, 150, 0.5)
    Note over User,useAppStore: AI Search Reranking Flow
    User->>GistView: types query + clicks AI Search
    GistView->>AIService: searchGistsWithReranking(gists, query)
    AIService->>AI Provider: requestText(rerank prompt)
    AI Provider-->>AIService: JSON array of gist IDs
    AIService->>AIService: reorder gists by ID rank
    AIService-->>GistView: reordered Gist[]
    GistView->>useAppStore: setGistSearchResults(reordered)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐇 A bunny hops through gists galore,
Stars and secrets, files by the score!
analyzeGist whispers code's tale,
Reranking results without fail.
From sidebar to modal, the view's complete —
Now gists and repos share the same street! 🌟

🚥 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 title 'feat: 支持 Gist 管理 (#222)' clearly summarizes the main feature: adding Gist management support.
Linked Issues check ✅ Passed The PR fully implements the objective from #222 by adding comprehensive Gist management including starred Gist synchronization from gist.github.com.
Out of Scope Changes check ✅ Passed All changes are within scope: Gist UI components, store state, API services, types, utilities, and documentation updates directly support #222's objective.

✏️ 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 codex/add-gist-support-222

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive Gist management feature, allowing users to view, create, edit, search, and perform AI-powered analysis on their GitHub Gists. Key additions include new UI components (GistView, GistCard, GistDetailModal, GistEditorModal), updated state management in Zustand, and integration with GitHub and AI services. The review feedback highlights several critical improvement opportunities: preventing file content loss during metadata merging, optimizing local search performance to avoid UI lag, providing a fallback for crypto.randomUUID in non-secure environments, introducing concurrency for batch AI analysis, and simplifying redundant state update logic.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/services/githubApi.ts
Comment on lines +251 to +261
private mergeGistMetadata(existing: Gist | undefined, incoming: Gist, starred?: boolean): Gist {
return {
...incoming,
starred: starred ?? incoming.starred ?? existing?.starred,
ai_summary: existing?.ai_summary,
analyzed_at: existing?.analyzed_at,
analysis_failed: existing?.analysis_failed,
analysis_error: existing?.analysis_error,
last_edited: existing?.last_edited,
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

在同步 Gist 列表时,incoming 对象来自列表 API(不包含文件内容 content)。直接使用 ...incoming 会导致 incoming.files 覆盖掉 existing.files,从而丢失之前已经加载过的文件内容(content)。\n\n建议在合并元数据时,对 files 进行深度合并,保留已有的文件内容。

  private mergeGistMetadata(existing: Gist | undefined, incoming: Gist, starred?: boolean): Gist {\n    const mergedFiles = { ...incoming.files };\n    if (existing?.files) {\n      for (const [filename, file] of Object.entries(mergedFiles)) {\n        const existingFile = existing.files[filename];\n        if (existingFile && !file.content && existingFile.content) {\n          mergedFiles[filename] = {\n            ...file,\n            content: existingFile.content,\n          };\n        }\n      }\n    }\n\n    return {\n      ...incoming,\n      files: mergedFiles,\n      starred: starred ?? incoming.starred ?? existing?.starred,\n      ai_summary: existing?.ai_summary,\n      analyzed_at: existing?.analyzed_at,\n      analysis_failed: existing?.analysis_failed,\n      analysis_error: existing?.analysis_error,\n      last_edited: existing?.last_edited,\n    };\n  }

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

已修复 ✅ — mergeGistMetadata(第251-274行)已实现文件深度合并:遍历 incoming.files,当列表 API 返回的文件缺少 content 而缓存中存在时,会保留已加载的文件内容,不会丢失。

Comment thread src/utils/gistUtils.ts
Comment on lines +88 to +103
if (queryWords.length > 0) {
filtered = filtered.filter(gist => {
const searchable = [
gist.description || '',
gist.owner?.login || '',
gist.ai_summary || '',
...Object.values(gist.files || {}).flatMap(file => [
file.filename,
file.language || '',
file.type || '',
file.content || '',
]),
].join(' ').toLowerCase();
return queryWords.every(word => searchable.includes(word));
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

在对 Gist 进行本地过滤时,将所有文件的内容(file.content)拼接成一个大字符串并调用 .toLowerCase() 会消耗大量的内存和 CPU。如果用户拥有较多或较大的 Gist 文件,在输入框中每输入一个字符都会导致明显的 UI 卡顿甚至浏览器崩溃。\n\n建议改为逐个字段进行匹配,并利用 some 提前中断,避免不必要的全量字符串拼接和转换。

  if (queryWords.length > 0) {\n    filtered = filtered.filter(gist => {\n      return queryWords.every(word => {\n        if ((gist.description || '').toLowerCase().includes(word)) return true;\n        if ((gist.owner?.login || '').toLowerCase().includes(word)) return true;\n        if ((gist.ai_summary || '').toLowerCase().includes(word)) return true;\n        return Object.values(gist.files || {}).some(file => \n          file.filename.toLowerCase().includes(word) ||\n          (file.language || '').toLowerCase().includes(word) ||\n          (file.type || '').toLowerCase().includes(word) ||\n          (file.content || '').toLowerCase().includes(word)\n        );\n      });\n    });\n  }

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

已修复 ✅ — filterAndSortGists(第88-106行)已改为逐字段匹配 + Array.some 提前中断,不再将所有文件内容拼接成超大字符串。每个 query word 在找到第一个匹配字段后立即返回,避免了不必要的 CPU 开销。

Comment on lines +23 to +27
const createEmptyFile = (): EditableFile => ({
id: crypto.randomUUID(),
filename: 'snippet.txt',
content: '',
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

在非安全上下文(如 HTTP 环境)或较旧的浏览器中,crypto.randomUUID 可能为 undefined,这会导致运行时崩溃。\n\n建议提供一个简单的降级方案,以确保在所有环境下都能正常运行。

const createEmptyFile = (): EditableFile => ({\n  id: typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2, 15),\n  filename: 'snippet.txt',\n  content: '',\n});

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

已修复 ✅ — 当前代码已封装 generateFileId() 函数(第23-28行),包含 crypto.randomUUID 可用性检测和降级方案(Date.now().toString(36) + Math.random()),不会再有运行时崩溃风险。

Comment thread src/components/GistView.tsx Outdated
Comment on lines +172 to +196
for (const gist of targets) {
setAnalyzingGist(gist.id, true);
try {
const detail = await api.getGist(gist.id, gist);
const summary = await aiService.analyzeGist(detail, api.getGistContentPreview(detail));
updateGist({
...detail,
ai_summary: summary.trim(),
analyzed_at: new Date().toISOString(),
analysis_failed: false,
analysis_error: undefined,
});
success++;
} catch (error) {
updateGist({
...gist,
analyzed_at: new Date().toISOString(),
analysis_failed: true,
analysis_error: error instanceof Error ? error.message : String(error),
});
failed++;
} finally {
setAnalyzingGist(gist.id, false);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

for...of 循环中串行使用 await 会导致 Gist 的 AI 分析只能一个接一个地执行。如果待分析的 Gist 数量较多,这会非常缓慢。\n\n建议根据 activeConfig.concurrency 或使用一个简单的并发控制(例如分批处理或 Promise 池)来并行执行分析,以显著提高批量分析的效率。

Comment thread src/store/useAppStore.ts Outdated
Comment on lines +1193 to +1197
gists: gistsResult.found
? gistsResult.gists
: state.gists.some(item => item.id === gist.id)
? state.gists
: [gist, ...state.gists],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

gistsResult.foundfalse 时,说明 replaceGistInList 未能在 state.gists 中找到该 Gist。因此,后续的 state.gists.some(item => item.id === gist.id) 必然也为 false。\n\n这个三元表达式可以简化,避免冗余的数组遍历。

          gists: gistsResult.found\n            ? gistsResult.gists\n            : [gist, ...state.gists],

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/GistCard.tsx`:
- Around line 39-45: The `isBusy` state is being reused for both analyze
operations and unstar/delete mutations, causing the analyze spinner to show
incorrectly when clicking unstar/delete. Create a separate state variable for
analyze operations (e.g., `isAnalyzing` state) distinct from `isBusy`, and
update the isAnalyzing computed variable to use only the dedicated analyze flag
and `isStoreAnalyzing`. Then locate all the places where `setIsBusy` is called
within analyze operation handlers (in the ranges around lines 121-131, 144-154,
187-192) and replace them with calls to the new dedicated analyze state setter
to keep mutation loading separate from analyze loading.

In `@src/components/GistEditorModal.tsx`:
- Around line 50-53: The canSubmit useMemo validation in GistEditorModal does
not check for duplicate filenames, which allows files with identical names to be
submitted and cause overwrites in the gist payload. Add an additional validation
condition in the canSubmit useMemo that verifies all filenames in visibleFiles
are unique by comparing the count of unique filenames against the total
visibleFiles length. This should be included in the logical AND condition
alongside the existing checks for non-empty length and filename/content content.

In `@src/components/GistView.tsx`:
- Around line 401-403: The onUnstarred callback uses the starredGists variable
from the render-time closure, which can cause stale state issues when multiple
unstar operations resolve out of order. Fix this by using the functional
setState pattern in the setStarredGists call, where instead of filtering based
on the closure's starredGists variable, pass a callback function to
setStarredGists that receives the current/latest state as a parameter and
performs the filter operation on that latest state value.
- Around line 202-213: The openDetail function has a race condition where an
earlier getGist request can resolve after a later click and overwrite the modal
state with the wrong gist details. To fix this, add a check before the state
updates at lines 209-210 (the updateGist and setDetailGist calls) to verify that
the response corresponds to the currently opened gist. Compare the id of the
detail response with the id of the gist parameter to ensure only the most recent
request's response updates the state. If the ids do not match, skip the state
updates and return early.

In `@src/services/aiService.ts`:
- Around line 631-636: The ids array from the model output may contain duplicate
values, which causes duplicate gists to appear in results. Before mapping the
ids to retrieve gists from gistById, deduplicate the ids array by converting it
to a Set and back to an array. Apply this deduplication step immediately before
the .map() call that retrieves gists, so that the subsequent filter and ranking
logic works with unique IDs only.

In `@src/services/githubApi.ts`:
- Around line 251-255: The mergeGistMetadata method is carrying forward stale
starred state from the cache by falling back to existing?.starred when both the
starred parameter and incoming.starred are undefined. This causes previously
starred gists to remain marked as starred even after being unstarred via the
API. Remove the fallback to existing?.starred from the starred property
assignment so that only the explicit starred parameter or the fresh
incoming.starred value is used, preventing stale cached state from persisting in
subsequent views.

In `@src/store/useAppStore.ts`:
- Around line 1187-1198: The updateGist function in the set callback incorrectly
adds non-owned gists to the state.gists list. When gistsResult.found is false,
the fallback logic checks if the gist exists in state.gists and unconditionally
prepends it if missing. This causes starred gists (which may not be owned) to
pollute the owned gists list. Fix the fallback for the gists property: when
gistsResult.found is false, simply return state.gists unchanged rather than
prepending the gist, since a gist should only remain in state.gists if it was
already there or successfully replaced from that list.
🪄 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: 65753a7f-525b-4598-a50b-4dd43b12fc07

📥 Commits

Reviewing files that changed from the base of the PR and between 3a343b1 and 200002b.

📒 Files selected for processing (12)
  • src/App.tsx
  • src/components/ForkTimeline.test.tsx
  • src/components/GistCard.tsx
  • src/components/GistDetailModal.tsx
  • src/components/GistEditorModal.tsx
  • src/components/GistView.tsx
  • src/components/Header.tsx
  • src/services/aiService.ts
  • src/services/githubApi.ts
  • src/store/useAppStore.ts
  • src/types/index.ts
  • src/utils/gistUtils.ts

Comment thread src/components/GistCard.tsx Outdated
Comment thread src/components/GistEditorModal.tsx
Comment thread src/components/GistView.tsx
Comment thread src/components/GistView.tsx
Comment thread src/services/aiService.ts
Comment thread src/services/githubApi.ts
Comment thread src/store/useAppStore.ts
- githubApi.ts: deep-merge files in mergeGistMetadata to preserve already-
  loaded content from detail API; drop existing?.starred fallback to avoid
  stale starred state after unstar
- gistUtils.ts: per-field short-circuit matching instead of joining all
  file content into one giant string for search
- GistEditorModal.tsx: add crypto.randomUUID fallback for non-secure
  contexts; add duplicate filename validation with inline error message
- GistCard.tsx: split isBusy into isAnalyzingLocal / isMutating so
  unstar/delete no longer incorrectly shows the analyze spinner
- GistView.tsx: add request-sequence guard in openDetail to prevent stale
  responses from overwriting the currently opened gist; use getState() in
  onUnstarred to avoid stale closure; support concurrency in batch AI
  analysis via activeConfig.concurrency
- aiService.ts: deduplicate reranked gist IDs before mapping to prevent
  duplicated gists in UI
- useAppStore.ts: only insert gists into gists (mine) list when the gist
  owner matches the current user, preventing starred-only gists from
  polluting the mine category

@coderabbitai coderabbitai Bot left a comment

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/components/GistEditorModal.tsx (1)

85-113: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing error handling for submission failure.

If onSubmit throws (e.g., network error or API error), the error propagates as an unhandled rejection. The modal stays open but the user receives no feedback about what went wrong.

Consider catching errors to show an appropriate message:

Suggested fix
   const handleSubmit = async () => {
     if (!canSubmit || isSaving) return;
     setIsSaving(true);
     try {
       if (gist) {
         await onSubmit({
           description,
           files: files.map(file => ({
             filename: file.filename.trim(),
             previousFilename: file.originalFilename,
             content: file.content,
             deleted: file.deleted,
           })),
         });
       } else {
         await onSubmit({
           description,
           public: isPublic,
           files: visibleFiles.map(file => ({
             filename: file.filename.trim(),
             content: file.content,
           })),
         });
       }
       onClose();
+    } catch {
+      // Error handling (e.g., toast) is expected in onSubmit; prevent unhandled rejection
     } finally {
       setIsSaving(false);
     }
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GistEditorModal.tsx` around lines 85 - 113, The handleSubmit
function currently lacks error handling for the onSubmit call. Add a catch block
after the try block to handle errors thrown by onSubmit. In the catch block,
capture the error and display it to the user using an appropriate feedback
mechanism (such as a toast notification, error state, or dialog). Ensure the
error message is user-friendly and helps them understand what went wrong. The
finally block will still execute to reset the isSaving state, and the modal will
remain open allowing the user to retry or close it themselves.
src/components/GistView.tsx (1)

228-241: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add error handling to provide user feedback on failure.

API calls in handleSubmitGist can fail (network errors, rate limits, validation errors), but errors propagate unhandled. The user receives no feedback, and the modal remains in an ambiguous state.

Suggested fix
   const handleSubmitGist = async (input: GistCreateInput | GistUpdateInput) => {
     if (!githubToken) return;
     const api = new GitHubApiService(githubToken);
-    if (editingGist) {
-      const updated = await api.updateGist(editingGist.id, input as GistUpdateInput, editingGist);
-      updateGist({ ...updated, last_edited: new Date().toISOString() });
-      toast(t('Gist 已更新', 'Gist updated'), 'success');
-      return;
+    try {
+      if (editingGist) {
+        const updated = await api.updateGist(editingGist.id, input as GistUpdateInput, editingGist);
+        updateGist({ ...updated, last_edited: new Date().toISOString() });
+        toast(t('Gist 已更新', 'Gist updated'), 'success');
+        return;
+      }
+
+      const created = await api.createGist(input as GistCreateInput);
+      updateGist({ ...created, last_edited: new Date().toISOString() });
+      toast(t('Gist 已创建', 'Gist created'), 'success');
+    } catch (error) {
+      toast(error instanceof Error ? error.message : t('操作失败', 'Operation failed'), 'error');
+      throw error;
     }
-
-    const created = await api.createGist(input as GistCreateInput);
-    updateGist({ ...created, last_edited: new Date().toISOString() });
-    toast(t('Gist 已创建', 'Gist created'), 'success');
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GistView.tsx` around lines 228 - 241, Wrap the API calls in
the handleSubmitGist function (both the api.updateGist call for editingGist and
the api.createGist call for new gists) in a try-catch block. In the catch block,
use the toast function to display an error message to the user with the actual
error details, similar to how success messages are shown with the toast function
calls for 'Gist 已更新' and 'Gist 已创建'. This ensures users receive feedback when
API operations fail due to network errors, rate limits, or validation issues.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/components/GistEditorModal.tsx`:
- Around line 85-113: The handleSubmit function currently lacks error handling
for the onSubmit call. Add a catch block after the try block to handle errors
thrown by onSubmit. In the catch block, capture the error and display it to the
user using an appropriate feedback mechanism (such as a toast notification,
error state, or dialog). Ensure the error message is user-friendly and helps
them understand what went wrong. The finally block will still execute to reset
the isSaving state, and the modal will remain open allowing the user to retry or
close it themselves.

In `@src/components/GistView.tsx`:
- Around line 228-241: Wrap the API calls in the handleSubmitGist function (both
the api.updateGist call for editingGist and the api.createGist call for new
gists) in a try-catch block. In the catch block, use the toast function to
display an error message to the user with the actual error details, similar to
how success messages are shown with the toast function calls for 'Gist 已更新' and
'Gist 已创建'. This ensures users receive feedback when API operations fail due to
network errors, rate limits, or validation issues.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 93fff94a-fb3a-4bd7-bebb-b49fab30f313

📥 Commits

Reviewing files that changed from the base of the PR and between 200002b and 1c9bea8.

📒 Files selected for processing (7)
  • src/components/GistCard.tsx
  • src/components/GistEditorModal.tsx
  • src/components/GistView.tsx
  • src/services/aiService.ts
  • src/services/githubApi.ts
  • src/store/useAppStore.ts
  • src/utils/gistUtils.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/services/aiService.ts
  • src/utils/gistUtils.ts
  • src/services/githubApi.ts
  • src/components/GistCard.tsx
  • src/store/useAppStore.ts

…guidance

Root cause: OAuth scope was 'read:user user:email repo', which lacks the
gist scope required to read secret/private gists and to create/edit/delete
any gists. Tokens minted before this change cannot access private gists.

- githubApi.ts: add 'gist' to the OAuth authorize scope
- LoginScreen.tsx: document the gist scope in the PAT creation guidance
- GistView.tsx: add a help tooltip next to the Gist sidebar title
  explaining the gist scope requirement and that re-login is needed after
  changing scopes
@AmintaCCCP

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/GistView.tsx`:
- Around line 228-241: The handleSubmitGist function lacks error handling for
the api.createGist and api.updateGist calls, leaving users without feedback if
these operations fail. Wrap the entire async function body in a try-catch block,
ensuring that if either api.updateGist or api.createGist throws an error, the
catch block displays an error toast message to inform the user of the failure.
This will provide proper user feedback when gist creation or updating operations
encounter errors.

In `@src/services/aiService.ts`:
- Around line 606-613: The gistSummaries mapping at line 606 caps gists to the
first 120 items before sending to the AI model, but subsequent gists appended at
line 642 never receive AI ranking, creating bias in semantic search results. To
fix this, implement either a chunked reranking approach where you process gists
in batches through the AI model (e.g., first 120, then next 120, etc.) and merge
the ranked results, or establish a deterministic candidate prefilter that
selects the most relevant gists before applying the 120-item prompt cap,
ensuring all potentially relevant gists can be considered for semantic search
ranking rather than only the initial batch.
- Around line 594-600: The requestText method call at line 594 sends Gist
content that should not be logged in debug outputs to protect private source
code. Add a redaction flag parameter (such as a boolean option for redacted or
shouldRedact) to the requestText call to indicate that the request body contains
sensitive data. Then update the requestText method to accept this parameter and
pass it to logAIRequestDebug, which should check this flag and skip logging the
full request body when redaction is enabled.

In `@src/services/githubApi.ts`:
- Around line 330-379: The getGist, createGist, and updateGist methods all call
makeRequest which logs response body previews, exposing sensitive file content
in debug logs. Add a redaction option parameter to the makeRequest calls in all
three methods to prevent logging of files[*].content and any other sensitive
Gist data. Ensure the redaction configuration is applied consistently across all
three Gist-related endpoints to mask private/secret code from diagnostic logs.

In `@src/store/useAppStore.ts`:
- Line 596: The `analyzingGistIds` field is being restored from persisted
storage on line 596, but these represent in-flight async analysis jobs that do
not survive app reloads, causing Gist cards to remain stuck in an analyzing
state after reload. Remove the restoration of `analyzingGistIds` from
`safePersisted` at the location where it's being assigned from
`safePersisted.analyzingGistIds`, and instead initialize it to an empty set.
Additionally, verify that line 1904 is not persisting `analyzingGistIds` to
storage in the first place, as this is runtime-only state that should not be
persisted.
- Around line 1896-1899: When persisting the gists and starredGists properties
in the state serialization, remove the file content from each Gist object to
prevent sensitive code from being stored long-term. Create a sanitization
function that iterates through each gist in both the gists and starredGists
collections, and for each gist's files array, delete or omit the content
property while preserving all other metadata and AI summaries. Apply this
sanitization function to transform the gists and starredGists values before they
are assigned to the persistence object.

In `@src/utils/gistUtils.ts`:
- Around line 69-75: In the forEach loop where gists are deduplicated and merged
into byId, the current spread order spreads gist after existing, which causes
undefined values in the incoming gist to overwrite previously populated optional
fields like ai_summary or analysis flags. Modify the object spread logic in the
byId.set() call to preserve existing optional metadata when the incoming gist
has undefined values for those fields, ensuring that duplicate gists merge
without losing previously stored analysis state. Consider reversing the spread
order or adding conditional logic to only override fields when they have defined
values.
🪄 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: 444a65b9-7d79-41c3-9fce-d76fb81c3e04

📥 Commits

Reviewing files that changed from the base of the PR and between 3a343b1 and f4af13e.

📒 Files selected for processing (13)
  • src/App.tsx
  • src/components/ForkTimeline.test.tsx
  • src/components/GistCard.tsx
  • src/components/GistDetailModal.tsx
  • src/components/GistEditorModal.tsx
  • src/components/GistView.tsx
  • src/components/Header.tsx
  • src/components/LoginScreen.tsx
  • src/services/aiService.ts
  • src/services/githubApi.ts
  • src/store/useAppStore.ts
  • src/types/index.ts
  • src/utils/gistUtils.ts

Comment thread src/components/GistView.tsx
Comment thread src/services/aiService.ts
Comment thread src/services/aiService.ts
Comment thread src/services/githubApi.ts
Comment thread src/store/useAppStore.ts Outdated
Comment thread src/store/useAppStore.ts
Comment thread src/utils/gistUtils.ts
- GistView: add try/catch with error toast for handleSubmitGist
  (r3434039119 - API errors now show user feedback)
- useAppStore: reset analyzingGistIds to empty on hydration
  (r3434039148 - prevents cards stuck in 'analyzing' state after reload)
- gistUtils: preserve optional metadata during dedup merge
  (r3434039162 - ai_summary/analyzed_at no longer overwritten by undefined)
Add -mb-px to arrow element so it overlaps the tooltip's top edge,
making the pointer triangle visible.
Add z-10 to the sticky sidebar container to create a stacking context,
allowing the tooltip z-[9999] to properly layer above the search/filter
section.
- Remove OAuth references from tooltip, use PAT-only instructions with
  step-by-step path to GitHub token settings
- Add gist scope guidance to create/update error toasts (GistView)
- Add gist scope guidance to delete error toast (GistCard)
- Detect 403/404/forbidden errors to show targeted permission hints
Change Gist title from text-sm to text-lg to match the font size used
in CategorySidebar's 'Categories' heading.
@AmintaCCCP AmintaCCCP merged commit eab5217 into main Jun 18, 2026
5 checks passed
@AmintaCCCP AmintaCCCP deleted the codex/add-gist-support-222 branch June 18, 2026 08:50
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.

[Feature] 支持 gist 上面的 star 同步

1 participant