feat: 支持 Gist 管理 (#222)#223
Conversation
- 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
|
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 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 ( ChangesGitHub Gists Management Feature
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 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)
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.
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.
| 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, | ||
| }; | ||
| } |
There was a problem hiding this comment.
在同步 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 }There was a problem hiding this comment.
已修复 ✅ — mergeGistMetadata(第251-274行)已实现文件深度合并:遍历 incoming.files,当列表 API 返回的文件缺少 content 而缓存中存在时,会保留已加载的文件内容,不会丢失。
| 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)); | ||
| }); | ||
| } |
There was a problem hiding this comment.
在对 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 }There was a problem hiding this comment.
已修复 ✅ — filterAndSortGists(第88-106行)已改为逐字段匹配 + Array.some 提前中断,不再将所有文件内容拼接成超大字符串。每个 query word 在找到第一个匹配字段后立即返回,避免了不必要的 CPU 开销。
| const createEmptyFile = (): EditableFile => ({ | ||
| id: crypto.randomUUID(), | ||
| filename: 'snippet.txt', | ||
| content: '', | ||
| }); |
There was a problem hiding this comment.
在非安全上下文(如 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});
There was a problem hiding this comment.
已修复 ✅ — 当前代码已封装 generateFileId() 函数(第23-28行),包含 crypto.randomUUID 可用性检测和降级方案(Date.now().toString(36) + Math.random()),不会再有运行时崩溃风险。
| 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); | ||
| } | ||
| } |
| gists: gistsResult.found | ||
| ? gistsResult.gists | ||
| : state.gists.some(item => item.id === gist.id) | ||
| ? state.gists | ||
| : [gist, ...state.gists], |
There was a problem hiding this comment.
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
📒 Files selected for processing (12)
src/App.tsxsrc/components/ForkTimeline.test.tsxsrc/components/GistCard.tsxsrc/components/GistDetailModal.tsxsrc/components/GistEditorModal.tsxsrc/components/GistView.tsxsrc/components/Header.tsxsrc/services/aiService.tssrc/services/githubApi.tssrc/store/useAppStore.tssrc/types/index.tssrc/utils/gistUtils.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
There was a problem hiding this comment.
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 winMissing error handling for submission failure.
If
onSubmitthrows (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 winAdd error handling to provide user feedback on failure.
API calls in
handleSubmitGistcan 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
📒 Files selected for processing (7)
src/components/GistCard.tsxsrc/components/GistEditorModal.tsxsrc/components/GistView.tsxsrc/services/aiService.tssrc/services/githubApi.tssrc/store/useAppStore.tssrc/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
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
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
📒 Files selected for processing (13)
src/App.tsxsrc/components/ForkTimeline.test.tsxsrc/components/GistCard.tsxsrc/components/GistDetailModal.tsxsrc/components/GistEditorModal.tsxsrc/components/GistView.tsxsrc/components/Header.tsxsrc/components/LoginScreen.tsxsrc/services/aiService.tssrc/services/githubApi.tssrc/store/useAppStore.tssrc/types/index.tssrc/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.
概述
实现 #222 提出的 Gist 支持需求,新增独立的 Gist 管理标签页。
需求对照
主要改动
审计修复
在审查过程中发现并修复两个问题:
验证
Closes #222
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes