Skip to content

fix: reduce jank when analyzing repository cards#199

Merged
AmintaCCCP merged 5 commits into
AmintaCCCP:mainfrom
xinvxueyuan:fix/repository-card-ai-analysis-freeze
Jun 6, 2026
Merged

fix: reduce jank when analyzing repository cards#199
AmintaCCCP merged 5 commits into
AmintaCCCP:mainfrom
xinvxueyuan:fix/repository-card-ai-analysis-freeze

Conversation

@xinvxueyuan

@xinvxueyuan xinvxueyuan commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #198

Summary

  • Narrow repository-card subscriptions so one card entering AI analysis does not cause every visible card to observe the entire analyzingRepositoryIds Set.
  • Add immediate local loading state and performance logging for single-card AI analysis.
  • Make repository updates no-op when the incoming repository is equivalent, and avoid rebuilding searchResults when the repo is not present there.
  • Move large persisted-state serialization into an idle task and remove the synchronous localStorage backup write.
  • Reduce repeated per-repo string work in category filtering.

Validation

  • npm run build
  • npx vitest run src/store/useAppStore.test.ts src/utils/markdownUtils.test.ts

Note

npm test -- --run still reports one unrelated existing failure in MarkdownRenderer.test.tsx:
Code Blocks > should show line numbers for code blocks with more than 3 lines.

Summary by CodeRabbit

  • Bug Fixes

    • Fix spinner/state inconsistencies so repositories reflect a local analyzing state reliably.
    • More accurate category filtering and keyword matching for repository lists.
  • Performance

    • Reduced unnecessary state updates to cut re-renders.
    • Debounced, idle-scheduled persistence with flush handlers for more efficient saves.
  • Observability

    • Enhanced analysis progress, timing and persistence logging.
  • Tests

    • Added tests validating store update behavior and performance guards.
  • Chores

    • Persistence now favors IndexedDB; legacy localStorage snapshots are removed and failures are surfaced.

@coderabbitai

coderabbitai Bot commented Jun 6, 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 79826b02-5781-457e-bb39-df68fe09ffd1

📥 Commits

Reviewing files that changed from the base of the PR and between 9f6b428 and 13cc02f.

📒 Files selected for processing (1)
  • src/services/indexedDbStorage.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/services/indexedDbStorage.ts

📝 Walkthrough

Walkthrough

This PR defers serialized state writes to idle time, avoids replacing repository lists when records are equal, narrows RepositoryCard subscriptions and adds local analyzing state with timing/logging, precomputes category filter keywords, and removes synchronous localStorage backup writes.

Changes

Performance optimization for large repository lists

Layer / File(s) Summary
Store idle scheduling and update optimization
src/store/useAppStore.ts
Adds scheduleIdleTask/cancelIdleTask, reworks debounced persistence to write JSON to IndexedDB during idle with timing/logging and flush-on-exit handlers, and introduces areRepositoryRecordsEqual/replaceRepositoryInList to avoid unnecessary list replacements. updateRepository returns early when no changes; setAnalyzingRepository short-circuits unchanged flags.
RepositoryCard local analysis state and instrumentation
src/components/RepositoryCard.tsx
Introduces local isLocallyAnalyzing and computes isAnalyzing as local OR store flag. Refactors store selection to boolean selectors by repoId. Instruments AI analysis with analysisStartedAt, requestAnimationFrame paint logging, onProgress step logs, and persistence timing around analyzeRepository and updateRepository.
RepositoryList category filter optimization
src/components/RepositoryList.tsx
Precomputes selectedCategoryKeywords (lowercased) for AI-tag and text matching, uses symmetric substring checks for ai_tags, and memoizes filterResetKey as an object to drive visibleCount resets.
Storage layer cleanup: remove localStorage dual-write
src/services/indexedDbStorage.ts
Removes the synchronous localStorage backup write from setItem, deletes migrated legacy localStorage entries during getItem migration, and updates module docs to state that writes go to IndexedDB and localStorage is a one-time legacy read fallback.
Performance guard regression tests
src/store/useAppStore.test.ts
Adds Vitest tests verifying updateRepository and setAnalyzingRepository avoid notifying subscribers or replacing state when incoming values are equivalent, and that repository-only updates don't touch searchResults.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

"I hop and time each little task,
Idle writes keep the UI unmasked.
Spinner paints, logs hum along,
No frozen cards — the scroll is strong.
A rabbit cheers: performant and fast." 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: reduce jank when analyzing repository cards' accurately describes the PR's primary objective—improving UI responsiveness during repository card AI analysis.
Linked Issues check ✅ Passed All coding-related requirements from issue #198 are met: narrowed subscriptions, local loading state with performance logging, no-op repository updates for equivalent data, reduced filtering string work, and idle-scheduled persistence.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #198 objectives: RepositoryCard instrumentation, RepositoryList filtering optimization, IndexedDB storage fallback hardening, and store performance guards.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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: 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)

69-114: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Queued writes can recreate state after removeItem().

timeoutId, idleTaskId, and latestValue live inside setItem's closure, so removeItem has no way to cancel them. Any reset/clear path that deletes the persisted snapshot while a debounced write is pending will write the old snapshot back afterward, resurrecting deleted repositories and persisted credentials.

Suggested refactor
-const debouncedPersistStorage: PersistStorage<any> = {
+let persistTimeoutId: ReturnType<typeof setTimeout> | null = null;
+let persistIdleTaskId: number | null = null;
+let latestPersistValue: StorageValue<any> | null = null;
+
+const debouncedPersistStorage: PersistStorage<any> = {
   getItem: async (name) => {
     const str = await indexedDBStorage.getItem(name);
     if (!str) return null;
     try {
       return JSON.parse(str);
@@
-  setItem: (() => {
-    let timeoutId: ReturnType<typeof setTimeout> | null = null;
-    let idleTaskId: number | null = null;
-    let latestValue: StorageValue<any> | null = null;
-    return (name: string, value: StorageValue<any>) => {
-      latestValue = value;
-      if (timeoutId) clearTimeout(timeoutId);
-      if (idleTaskId !== null) {
-        cancelIdleTask(idleTaskId);
-        idleTaskId = null;
+  setItem: (name: string, value: StorageValue<any>) => {
+      latestPersistValue = value;
+      if (persistTimeoutId) clearTimeout(persistTimeoutId);
+      if (persistIdleTaskId !== null) {
+        cancelIdleTask(persistIdleTaskId);
+        persistIdleTaskId = null;
       }
-      timeoutId = setTimeout(() => {
-        timeoutId = null;
-        idleTaskId = scheduleIdleTask(() => {
+      persistTimeoutId = setTimeout(() => {
+        persistTimeoutId = null;
+        persistIdleTaskId = scheduleIdleTask(() => {
           const startedAt = performance.now();
           try {
-            const str = JSON.stringify(latestValue);
+            const str = JSON.stringify(latestPersistValue);
@@
           } finally {
-            idleTaskId = null;
+            persistIdleTaskId = null;
           }
         });
       }, 1000);
-    };
-  })(),
+  },
   removeItem: (name) => {
-    indexedDBStorage.removeItem(name);
+    latestPersistValue = null;
+    if (persistTimeoutId) {
+      clearTimeout(persistTimeoutId);
+      persistTimeoutId = null;
+    }
+    if (persistIdleTaskId !== null) {
+      cancelIdleTask(persistIdleTaskId);
+      persistIdleTaskId = null;
+    }
+    return indexedDBStorage.removeItem(name);
   }
 };
🤖 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/store/useAppStore.ts` around lines 69 - 114, The bug is that timeoutId,
idleTaskId and latestValue are closed over inside setItem so removeItem cannot
cancel pending debounced/idle writes and an old snapshot can be written back
after removal; fix by hoisting those variables to shared scope (or to the
surrounding store object) so both setItem and removeItem can access them, and
update removeItem (the indexedDBStorage.removeItem caller) to
clearTimeout(timeoutId), cancelIdleTask(idleTaskId) and reset
idleTaskId/timeoutId/latestValue (or bump a write-version token) so the
scheduled task checks that token/latestValue is still valid before calling
indexedDBStorage.setItem(name, ...); ensure the scheduled closure returns early
when latestValue is null or the token/version mismatches to prevent resurrecting
removed state.
🧹 Nitpick comments (1)
src/services/indexedDbStorage.ts (1)

100-100: ⚡ Quick win

Update stale comment to reflect removal of dual writes.

The comment header mentions "dual write:" but the code no longer writes to both IndexedDB and localStorage (only IndexedDB, as correctly noted in lines 103-104). This inconsistency could confuse future maintainers.

📝 Suggested fix
-/**
- * IndexedDB-backed Zustand persist storage with seamless migration + dual write:
- * - First read from IndexedDB
- * - If empty, fall back to existing localStorage snapshot and migrate to IndexedDB
- * - Writes go to IndexedDB only; localStorage is read as a one-time legacy fallback.
- *   Keeping large repository snapshots out of localStorage avoids synchronous main-thread stalls.
- */
+/**
+ * IndexedDB-backed Zustand persist storage with seamless migration:
+ * - First read from IndexedDB
+ * - If empty, fall back to existing localStorage snapshot and migrate to IndexedDB
+ * - Writes go to IndexedDB only; localStorage is read as a one-time legacy fallback.
+ *   Keeping large repository snapshots out of localStorage avoids synchronous main-thread stalls.
+ */
🤖 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/services/indexedDbStorage.ts` at line 100, Update the top-of-file comment
that currently reads "IndexedDB-backed Zustand persist storage with seamless
migration + dual write:" to reflect that dual write was removed (e.g.,
"IndexedDB-backed Zustand persist storage with seamless migration" or
"single-write to IndexedDB"). Locate the header comment above the exported
storage implementation (the module-level comment that precedes the IndexedDB
storage functions such as the exported storage creator/handler in this file) and
edit it so it no longer references "dual write" or implies writing to both
IndexedDB and localStorage.
🤖 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/store/useAppStore.test.ts`:
- Around line 6-37: The test imports the real useAppStore singleton (via
vi.importActual) while the store uses async persistence (name:
'github-stars-manager', storage: debouncedPersistStorage/ indexedDBStorage
fallback), so persisted snapshot can rehydrate and break assertions; fix by
isolating this suite from persistence: before importing useAppStore, either
stub/disable the persist layer (mock debouncedPersistStorage/indexedDBStorage to
a no-op) or clear the persisted key ('github-stars-manager') from storage
(indexedDB/localStorage) so hydration has nothing to restore, then import
useAppStore (the code using vi.importActual and the beforeAll/createRepository
helpers will work unchanged).

In `@src/store/useAppStore.ts`:
- Around line 80-109: The delayed persistence path using timeoutId +
scheduleIdleTask causes recent updates to be lost if the page is hidden/closed
before the idle callback runs; add a flush function that cancels timeoutId and
idleTaskId, synchronously stringifies latestValue and calls
indexedDBStorage.setItem(name, str) (handling errors via logger.errorFromError)
and invoke it on visibilitychange/pagehide (when document.visibilityState ===
'hidden') and beforeunload so critical writes bypass the idle queue; keep
scheduleIdleTask usage for normal runs but ensure the new flush references
timeoutId, idleTaskId, latestValue, indexedDBStorage.setItem, scheduleIdleTask,
logger and name to locate integration points.

---

Outside diff comments:
In `@src/store/useAppStore.ts`:
- Around line 69-114: The bug is that timeoutId, idleTaskId and latestValue are
closed over inside setItem so removeItem cannot cancel pending debounced/idle
writes and an old snapshot can be written back after removal; fix by hoisting
those variables to shared scope (or to the surrounding store object) so both
setItem and removeItem can access them, and update removeItem (the
indexedDBStorage.removeItem caller) to clearTimeout(timeoutId),
cancelIdleTask(idleTaskId) and reset idleTaskId/timeoutId/latestValue (or bump a
write-version token) so the scheduled task checks that token/latestValue is
still valid before calling indexedDBStorage.setItem(name, ...); ensure the
scheduled closure returns early when latestValue is null or the token/version
mismatches to prevent resurrecting removed state.

---

Nitpick comments:
In `@src/services/indexedDbStorage.ts`:
- Line 100: Update the top-of-file comment that currently reads
"IndexedDB-backed Zustand persist storage with seamless migration + dual write:"
to reflect that dual write was removed (e.g., "IndexedDB-backed Zustand persist
storage with seamless migration" or "single-write to IndexedDB"). Locate the
header comment above the exported storage implementation (the module-level
comment that precedes the IndexedDB storage functions such as the exported
storage creator/handler in this file) and edit it so it no longer references
"dual write" or implies writing to both IndexedDB and localStorage.
🪄 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: 9276130c-a19b-4db5-a034-8b068e6871c6

📥 Commits

Reviewing files that changed from the base of the PR and between 4353206 and 25ea9ae.

📒 Files selected for processing (5)
  • src/components/RepositoryCard.tsx
  • src/components/RepositoryList.tsx
  • src/services/indexedDbStorage.ts
  • src/store/useAppStore.test.ts
  • src/store/useAppStore.ts

Comment thread src/store/useAppStore.test.ts
Comment thread src/store/useAppStore.ts Outdated
@xinvxueyuan

Copy link
Copy Markdown
Contributor Author

Addressed the AI review feedback in 357e0bf:

  • Hoisted pending persist state so removeItem cancels queued timeout/idle writes and cannot resurrect a removed snapshot.
  • Added flush-on-hidden/pagehide/beforeunload handling for the latest debounced persist snapshot.
  • Cleared the real persisted store key before importing the actual Zustand store in useAppStore tests.
  • Updated the IndexedDB storage comment to remove the stale dual-write wording.

Validation:

  • npx vitest run src/store/useAppStore.test.ts src/utils/markdownUtils.test.ts
  • npm run build

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

@xinvxueyuan Thanks for the detailed update! The fixes look solid — hoisting pending persist state to prevent resurrection after removeItem, adding flush handlers for page lifecycle events (visibilitychange/pagehide/beforeunload), and clearing the persisted store key in tests before importing the actual store are all important correctness improvements. I'll kick off a fresh review of the latest changes now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 1

🧹 Nitpick comments (1)
src/store/useAppStore.ts (1)

967-979: ⚡ Quick win

Reuse the first replacement when both lists share identity.

Line 966 and Lines 1011-1014 keep repositories and searchResults aliased on the common unfiltered path. This branch still does the same findIndex/copy work twice and then breaks that shared reference, which adds avoidable cost on large lists.

💡 Targeted refactor
       updateRepository: (repo) => set((state) => {
         const repositoriesResult = replaceRepositoryInList(state.repositories, repo);
-        const searchResultsResult = replaceRepositoryInList(state.searchResults, repo);
+        const searchResultsResult =
+          state.searchResults === state.repositories
+            ? {
+                repositories: repositoriesResult.repositories,
+                changed: repositoriesResult.changed,
+                found: repositoriesResult.found,
+              }
+            : replaceRepositoryInList(state.searchResults, repo);
 
         if (!repositoriesResult.changed && !searchResultsResult.changed) {
           return state;
         }
🤖 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/store/useAppStore.ts` around lines 967 - 979, The updateRepository
handler is doing two separate replaceRepositoryInList calls even when
state.repositories and state.searchResults are the same array; compute the
replacement once in updateRepository (call replaceRepositoryInList on
state.repositories), then if state.repositories === state.searchResults reuse
that single result for both repositories and searchResults to avoid repeating
findIndex/copy work; otherwise run replaceRepositoryInList on searchResults and
merge results as before, preserving the existing changed checks and returned
shape.
🤖 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/store/useAppStore.ts`:
- Around line 91-100: The indexedDBStorage.setItem promise calls in
useAppStore.ts (the one that currently measures writeMs and calls logger.warn)
lack rejection handlers; add a .catch to both indexedDBStorage.setItem(...)
usages to handle failures: compute writeMs like the success path, call
logger.error('store.persist', 'IndexedDB write failed', { source, writeMs,
bytes: str.length, error: err }) and surface the failure to the app state (e.g.,
set a boolean persistenceFailed / call an existing store mutation) so the UI can
react instead of silently dropping persistence. Ensure you reference the same
variables used there (writeStartedAt, str, source) and keep the success .then
logic intact.

---

Nitpick comments:
In `@src/store/useAppStore.ts`:
- Around line 967-979: The updateRepository handler is doing two separate
replaceRepositoryInList calls even when state.repositories and
state.searchResults are the same array; compute the replacement once in
updateRepository (call replaceRepositoryInList on state.repositories), then if
state.repositories === state.searchResults reuse that single result for both
repositories and searchResults to avoid repeating findIndex/copy work; otherwise
run replaceRepositoryInList on searchResults and merge results as before,
preserving the existing changed checks and returned shape.
🪄 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: d2f7dc80-220a-4797-9b80-bd264ec814b0

📥 Commits

Reviewing files that changed from the base of the PR and between 25ea9ae and 357e0bf.

📒 Files selected for processing (3)
  • src/services/indexedDbStorage.ts
  • src/store/useAppStore.test.ts
  • src/store/useAppStore.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/store/useAppStore.test.ts
  • src/services/indexedDbStorage.ts

Comment thread src/store/useAppStore.ts Outdated
@xinvxueyuan

Copy link
Copy Markdown
Contributor Author

Addressed the latest CodeRabbit review in 45ed403:

  • Added rejection handling for the debounced IndexedDB persist write, including elapsed write time, payload size, and source in the structured error log.
  • Added rejection handling for persisted snapshot removal so failed cleanup is not silently dropped.
  • Reused the repository replacement result when repositories and searchResults share the same array reference, avoiding duplicate findIndex/copy work.

Validation:

  • git diff --check
  • npx vitest run src/store/useAppStore.test.ts src/utils/markdownUtils.test.ts
  • npm run build

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

@xinvxueyuan Thanks for the continued improvements! The additions in 45ed403 look like solid defensive measures — structured error logging for IndexedDB write failures (with elapsed time and payload size for diagnosability), silent-drop prevention on snapshot removal failures, and the optimization to skip redundant findIndex/array-copy work when repositories and searchResults share the same reference are all good additions. Kicking off a fresh review now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Clear migrated localStorage snapshots after IndexedDB becomes authoritative so a later IndexedDB read failure cannot hydrate stale data. Keep localStorage writes only as the constrained-environment fallback when IndexedDB is unavailable or a write fails.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@AmintaCCCP

AmintaCCCP commented Jun 6, 2026

Copy link
Copy Markdown
Owner

补充修复提交:fix: harden IndexedDB persistence fallback(9f6b428)。

原因:前一版移除了 localStorage 双写,但读取路径仍会在 IndexedDB 失败时 fallback 到 localStorage。如果迁移后的旧 localStorage 快照不清理,后续 IndexedDB 读取超时/失败时可能 hydrate 到陈旧状态,表现为数据回滚。同时 IndexedDB 不可用的受限环境下也会失去新的持久化写入。

本提交处理:

  • 成功迁移 localStorage 快照到 IndexedDB 后立即删除 legacy localStorage,避免未来 stale fallback。
  • IndexedDB 正常写入成功后清理 legacy localStorage。
  • 仅在 IndexedDB 不可用或写入失败时,把 localStorage 作为当前 fallback 快照写入。

已验证:npx vitest run src/store/useAppStore.test.ts src/utils/markdownUtils.test.ts 通过(18 tests)。

说明:npm run build 本地未能执行:Claude Code 权限安全分类器临时不可用,命令未被启动。

@AmintaCCCP

AmintaCCCP commented Jun 6, 2026

Copy link
Copy Markdown
Owner

已在原 PR 分支追加修复提交:9f6b428 fix: harden IndexedDB persistence fallback

修复原因:前一版移除了 localStorage 双写,但读取路径仍会在 IndexedDB 失败时 fallback 到 localStorage。如果迁移后的旧 localStorage 快照不清理,后续 IndexedDB 读取超时/失败时可能 hydrate 到陈旧状态,表现为数据回滚。同时 IndexedDB 不可用的受限环境下也会失去新的持久化写入。

本提交处理:

  • 成功迁移 localStorage 快照到 IndexedDB 后立即删除 legacy localStorage,避免未来 stale fallback。
  • IndexedDB 正常写入成功后清理 legacy localStorage。
  • 仅在 IndexedDB 不可用或写入失败时,把 localStorage 作为当前 fallback 快照写入。
  • 测试里对 localStorage 不可用环境做可选调用保护,避免 jsdom/Node 环境报错。

验证:npx vitest run src/store/useAppStore.test.ts src/utils/markdownUtils.test.ts 通过,18 tests。

说明:npm run build 曾尝试执行,但 Claude Code 权限安全分类器临时不可用,命令未启动;未能完成 build 验证。

@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: 1

🤖 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/services/indexedDbStorage.ts`:
- Around line 24-30: safeLocalStorageSet currently swallows localStorage.setItem
errors; change it to return a boolean (or throw) indicating success so callers
can detect failure: update function safeLocalStorageSet(key: string, value:
string) to return true on success and false (or rethrow the caught error) on
failure, and then update the code paths that call safeLocalStorageSet (the
IndexedDB fallback logic around the persist/save snapshot routine) to treat a
false result as a write failure and reject or propagate an error when neither
IndexedDB nor localStorage succeeded so the upstream persist-failure handling
can run.
🪄 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: 863a5a17-e837-4d7a-bf79-14f8f6320b4f

📥 Commits

Reviewing files that changed from the base of the PR and between 45ed403 and 9f6b428.

📒 Files selected for processing (2)
  • src/services/indexedDbStorage.ts
  • src/store/useAppStore.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/store/useAppStore.test.ts

@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

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🤖 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/services/indexedDbStorage.ts`:
- Around line 24-30: safeLocalStorageSet currently swallows localStorage.setItem
errors; change it to return a boolean (or throw) indicating success so callers
can detect failure: update function safeLocalStorageSet(key: string, value:
string) to return true on success and false (or rethrow the caught error) on
failure, and then update the code paths that call safeLocalStorageSet (the
IndexedDB fallback logic around the persist/save snapshot routine) to treat a
false result as a write failure and reject or propagate an error when neither
IndexedDB nor localStorage succeeded so the upstream persist-failure handling
can run.
🪄 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: 863a5a17-e837-4d7a-bf79-14f8f6320b4f

📥 Commits

Reviewing files that changed from the base of the PR and between 45ed403 and 9f6b428.

📒 Files selected for processing (2)
  • src/services/indexedDbStorage.ts
  • src/store/useAppStore.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/store/useAppStore.test.ts
🛑 Comments failed to post (1)
src/services/indexedDbStorage.ts (1)

24-30: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propagate the fallback write failure instead of swallowing it.

On Line 24-Line 30 and Line 151-Line 156, safeLocalStorageSet turns localStorage.setItem failures into a successful setItem call. If IndexedDB already failed, that makes “nothing was persisted” look like success, so the new persist-failure handling upstream never fires and the latest snapshot can be dropped silently. Return a success flag from the helper and reject when neither backend stores the value. This conflicts with the PR objective to surface failed persistence paths.

Suggested fix
-const safeLocalStorageSet = (key: string, value: string): void => {
+const safeLocalStorageSet = (key: string, value: string): boolean => {
   try {
     window.localStorage.setItem(key, value);
+    return true;
   } catch {
     // Quota/security errors are expected in some environments; ignore.
+    return false;
   }
 };
...
-    safeLocalStorageSet(name, value);
+    if (!safeLocalStorageSet(name, value)) {
+      throw new Error('[storage] localStorage fallback write failed');
+    }
   },

Also applies to: 151-156

🤖 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/services/indexedDbStorage.ts` around lines 24 - 30, safeLocalStorageSet
currently swallows localStorage.setItem errors; change it to return a boolean
(or throw) indicating success so callers can detect failure: update function
safeLocalStorageSet(key: string, value: string) to return true on success and
false (or rethrow the caught error) on failure, and then update the code paths
that call safeLocalStorageSet (the IndexedDB fallback logic around the
persist/save snapshot routine) to treat a false result as a write failure and
reject or propagate an error when neither IndexedDB nor localStorage succeeded
so the upstream persist-failure handling can run.

Return a success flag from the localStorage fallback writer and throw when neither IndexedDB nor localStorage can persist the snapshot. This lets the upstream persistence error handling report a real write failure instead of treating a dropped snapshot as success.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@AmintaCCCP

Copy link
Copy Markdown
Owner

已追加修复 CodeRabbit 最新审计问题:13cc02f fix: propagate localStorage fallback failures

原因:safeLocalStorageSet 原本会吞掉 localStorage.setItem 失败。如果 IndexedDB 已经失败,而 localStorage fallback 也失败,上层仍会把这次持久化当成成功,导致快照实际丢失却没有错误日志。

本提交处理:

  • safeLocalStorageSet 改为返回 boolean
  • fallback 写入失败时抛出错误。
  • 这样 indexedDBStorage.setItem 会 reject,上层已有的 .catch 可以记录真正的持久化失败。

验证:

  • npx vitest run src/store/useAppStore.test.ts src/utils/markdownUtils.test.ts 通过,18 tests。
  • git diff --check 通过。

说明:npm run build 再次尝试执行,但当前 Claude Code 权限安全分类器临时不可用,命令未启动。

@AmintaCCCP AmintaCCCP merged commit dcdc1c7 into AmintaCCCP:main Jun 6, 2026
5 checks passed
@xinvxueyuan xinvxueyuan deleted the fix/repository-card-ai-analysis-freeze branch June 6, 2026 13:37
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.

Repository card AI analysis button freezes UI on large star lists

2 participants