Skip to content

fix: improve sync stability, auth handling, and mobile layoutFix/mobile sync stability#55

Merged
AmintaCCCP merged 4 commits intoAmintaCCCP:mainfrom
HuberttFox:fix/mobile-sync-stability
Mar 14, 2026
Merged

fix: improve sync stability, auth handling, and mobile layoutFix/mobile sync stability#55
AmintaCCCP merged 4 commits intoAmintaCCCP:mainfrom
HuberttFox:fix/mobile-sync-stability

Conversation

@HuberttFox
Copy link
Contributor

@HuberttFox HuberttFox commented Mar 12, 2026

Summary

  • improve backend auth and AI config handling
  • stabilize repository sync and unstar behavior
  • improve mobile layout across key views

Details

  • fix backend auth usage for protected API requests
  • improve backend connection validation flow
  • support DeepSeek reasoning responses in AI test flow
  • avoid stale backend data restoring unstarred repositories
  • force immediate backend sync after unstar
  • reduce sync-time visual reload on repository cards
  • improve mobile layout for header, category sidebar, search, and release views

Testing

  • verified frontend build succeeds
  • manually tested unstar flow and backend sync behavior
  • manually checked mobile layout adjustments

Summary by CodeRabbit

  • New Features

    • Quick sync action in header and a mobile top navigation for switching views
    • Immediate "force sync" trigger to push local changes
  • Improvements

    • Responsive redesign across repositories, releases, header, sidebar, and search for better mobile/layout behavior
    • Improved sync visual feedback and animations suppressed during syncing
    • Stronger backend connection/auth checks with clearer error messaging
  • Bug Fixes

    • Prevent backend polling from overwriting pending local edits

@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aec1a2e4-7a0f-461c-91eb-e74047173799

📥 Commits

Reviewing files that changed from the base of the PR and between 7b6bb85 and ea6ab3b.

📒 Files selected for processing (2)
  • server/src/routes/repositories.ts
  • src/components/RepositoryList.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/src/routes/repositories.ts
  • src/components/RepositoryList.tsx

📝 Walkthrough

Walkthrough

Adds backend full-sync pruning for repositories/releases, responsive frontend layout updates, session-scoped backend secret handling and verifyAuth, local-change tracking with visual sync signaling (and forceSyncToBackend), and a safer AI message extraction fallback.

Changes

Cohort / File(s) Summary
Backend Repository Management
server/src/routes/repositories.ts
Adds isFullSync-driven pruning: compute incoming numeric repo IDs, delete releases/repositories not in set (or all if none) before continuing with upsert.
Auto-sync & Visual State
src/services/autoSync.ts, src/components/RepositoryList.tsx, src/components/RepositoryCard.tsx, src/index.css
Tracks pending local edits, signals gsm:repository-sync-visual-state during pulls/pushes, adds forceSyncToBackend(), prevents auto-pulls when local changes exist, and disables card animations during sync.
Backend Auth & Store Secrets
src/services/backendAdapter.ts, src/store/useAppStore.ts, src/components/SettingsPanel.tsx
Replaces localStorage secret read with useAppStore session-backed secret, adds verifyAuth() to BackendAdapter, persists backend secret to sessionStorage, and updates SettingsPanel to check API secret validity on test.
Responsive UI Layouts
src/App.tsx, src/components/Header.tsx, src/components/CategorySidebar.tsx, src/components/ReleaseTimeline.tsx, src/components/SearchBar.tsx
Refactors layout to responsive column/row patterns, adds horizontal scrollers on small screens, updates spacing and breakpoint visibility, and passes onCategorySelect from CategorySidebar.
AI Service Robustness
src/services/aiService.ts
Safer assistant message extraction: validate message.content type, fallback to reasoning_content when content absent.
Minor UI & Settings
src/components/RepositoryCard.tsx, src/components/SettingsPanel.tsx
RepositoryCard triggers forceSyncToBackend after unstar; SettingsPanel error text updated to include API secret correctness.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant RepoCard as RepositoryCard (UI)
    participant AutoSync as AutoSync Service
    participant Store as App Store
    participant Backend as Backend API

    User->>RepoCard: Unstar repository
    RepoCard->>AutoSync: forceSyncToBackend()
    activate AutoSync
    AutoSync->>AutoSync: clear debounce timer
    AutoSync->>Store: mark pending local changes
    AutoSync->>RepoCard: emit gsm:repository-sync-visual-state (isSyncing=true)
    RepoCard->>RepoCard: disable card animations
    AutoSync->>Backend: PATCH /api/repositories (push)
    activate Backend
    Backend-->>AutoSync: success/failure
    deactivate Backend
    AutoSync->>Store: update last-known hashes / drain pending pushes
    AutoSync->>Store: clear pending flag (on success)
    AutoSync->>RepoCard: emit gsm:repository-sync-visual-state (isSyncing=false)
    RepoCard->>RepoCard: re-enable animations
    deactivate AutoSync
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

Poem

🐇 I hop, I prune, I sync tonight,
Quietly keep your local edits right,
Secrets tucked in session's nest,
Cards stand still while backend rests,
Responsive leaps from small to wide delight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title addresses multiple key aspects of the PR: sync stability, auth handling, and mobile layout—all of which are reflected in the changeset across backend, sync service, and UI components.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

CodeRabbit can use TruffleHog to scan for secrets in your code with verification capabilities.

Add a TruffleHog config file (e.g. trufflehog-config.yml, trufflehog.yml) to your project to customize detectors and scanning behavior. The tool runs only when a config file is present.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

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

483-493: ⚠️ Potential issue | 🟠 Major

The repository-list-syncing class is applied to the wrong element.

The class is applied to a <div> within the statistics section, but the repository cards are rendered in a separate grid at line 516. The CSS selector .repository-list-syncing .repository-card requires the class to be on an ancestor of the cards.

The class should be applied to a container that wraps the repository grid, or directly on the outer <div className="space-y-6"> at line 351.

🐛 Proposed fix
   return (
-    <div className="space-y-6">
+    <div className={`space-y-6 ${disableCardAnimations ? 'repository-list-syncing' : ''}`}>


       {/* AI Analysis Controls */}
       <div className="flex items-center justify-between bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-4">
         ...
         {/* Statistics */}
         <div className="text-sm text-gray-500 dark:text-gray-400">
           <div className="flex items-center justify-between">
-    <div className={disableCardAnimations ? 'repository-list-syncing' : undefined}>
+            <div>
               {t(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/RepositoryList.tsx` around lines 483 - 493, The CSS class
"repository-list-syncing" is currently applied to the statistics <div> (the
t(...) string) but must be on the ancestor that contains the repository cards so
the selector ".repository-list-syncing .repository-card" matches; remove the
className={disableCardAnimations ? 'repository-list-syncing' : undefined} from
that stats <div> and add it instead to the higher-level container that wraps the
repository grid (the element with className "space-y-6" or the element that
renders the repository map in the RepositoryList component) so the
repository-card elements become children of the element with
"repository-list-syncing".
🧹 Nitpick comments (2)
src/services/autoSync.ts (1)

52-60: Consider potential starvation scenario with _debounceTimer guard.

The guard skips syncFromBackend if _debounceTimer is active. While this prevents overwriting pending local changes, if the user makes frequent edits, the debounce timer keeps resetting, and backend data may never be pulled. This could lead to stale data on multi-device scenarios where other devices push updates.

Consider whether a maximum "staleness" threshold might be appropriate for future enhancement, or document this trade-off as intentional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/autoSync.ts` around lines 52 - 60, The guard that returns early
when _debounceTimer is active can starve syncs during frequent local edits;
update syncFromBackend to track when debouncing started (e.g., add
_debounceStartTimestamp or reuse _lastSuccessfulSyncAt) and introduce a
MAX_STALENESS_MS constant so that if Date.now() - _debounceStartTimestamp (or
_lastSuccessfulSyncAt) exceeds that threshold the function ignores
_debounceTimer and proceeds with the sync; change the conditional that currently
includes _debounceTimer to also check the staleness threshold (while preserving
the other guards like _isSyncingFromBackendActive and _isPushingToBackend) so
multi-device updates aren’t permanently delayed.
server/src/routes/repositories.ts (1)

103-108: Add a foreign key constraint to enforce release cleanup at the schema level.

The manual delete statements keep releases consistent only in this handler. server/src/db/schema.ts does not declare a foreign key from releases.repo_id to repositories.id, so any other repository-delete path can orphan release rows despite foreign-key enforcement being enabled in connection.ts. Adding ON DELETE CASCADE to the schema would centralize the invariant and eliminate the need for manual cleanup here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/src/routes/repositories.ts` around lines 103 - 108, The releases table
lacks a foreign key to repositories so orphaned releases can occur; update the
schema in server/src/db/schema.ts to declare releases.repo_id as a FOREIGN KEY
referencing repositories.id with ON DELETE CASCADE, then remove or simplify the
manual cleanup logic in this route (remove usages of deleteAllReleases,
deleteReleasesNotIn and related manual deletes in
server/src/routes/repositories.ts) so the database enforces cascading deletions
centrally.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/src/routes/repositories.ts`:
- Around line 111-123: The current logic treats a filtered-empty repoIds as an
intent to purge; instead, validate inputs and make full-delete explicit: before
the transaction, check the original repositories array and ensure every item
yields a numeric id (compare repositories.length vs repoIds.length) and reject
(throw/return 400) if any id is missing/malformed; only allow the
deleteAllReleases.run() / deleteAllRepositories.run() path when the caller
explicitly requested a purge (e.g., an explicit boolean flag or an empty
original repositories payload known-intent), and otherwise skip deletion when
repoIds.length === 0; keep the deleteReleasesNotIn(placeholders).run(...repoIds)
and deleteRepositoriesNotIn(placeholders).run(...repoIds) usage but only call
them when repoIds.length > 0.

---

Outside diff comments:
In `@src/components/RepositoryList.tsx`:
- Around line 483-493: The CSS class "repository-list-syncing" is currently
applied to the statistics <div> (the t(...) string) but must be on the ancestor
that contains the repository cards so the selector ".repository-list-syncing
.repository-card" matches; remove the className={disableCardAnimations ?
'repository-list-syncing' : undefined} from that stats <div> and add it instead
to the higher-level container that wraps the repository grid (the element with
className "space-y-6" or the element that renders the repository map in the
RepositoryList component) so the repository-card elements become children of the
element with "repository-list-syncing".

---

Nitpick comments:
In `@server/src/routes/repositories.ts`:
- Around line 103-108: The releases table lacks a foreign key to repositories so
orphaned releases can occur; update the schema in server/src/db/schema.ts to
declare releases.repo_id as a FOREIGN KEY referencing repositories.id with ON
DELETE CASCADE, then remove or simplify the manual cleanup logic in this route
(remove usages of deleteAllReleases, deleteReleasesNotIn and related manual
deletes in server/src/routes/repositories.ts) so the database enforces cascading
deletions centrally.

In `@src/services/autoSync.ts`:
- Around line 52-60: The guard that returns early when _debounceTimer is active
can starve syncs during frequent local edits; update syncFromBackend to track
when debouncing started (e.g., add _debounceStartTimestamp or reuse
_lastSuccessfulSyncAt) and introduce a MAX_STALENESS_MS constant so that if
Date.now() - _debounceStartTimestamp (or _lastSuccessfulSyncAt) exceeds that
threshold the function ignores _debounceTimer and proceeds with the sync; change
the conditional that currently includes _debounceTimer to also check the
staleness threshold (while preserving the other guards like
_isSyncingFromBackendActive and _isPushingToBackend) so multi-device updates
aren’t permanently delayed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0e263125-ce01-4201-9e79-6486344d2804

📥 Commits

Reviewing files that changed from the base of the PR and between 1e0cbdb and 7b6bb85.

📒 Files selected for processing (14)
  • server/src/routes/repositories.ts
  • src/App.tsx
  • src/components/CategorySidebar.tsx
  • src/components/Header.tsx
  • src/components/ReleaseTimeline.tsx
  • src/components/RepositoryCard.tsx
  • src/components/RepositoryList.tsx
  • src/components/SearchBar.tsx
  • src/components/SettingsPanel.tsx
  • src/index.css
  • src/services/aiService.ts
  • src/services/autoSync.ts
  • src/services/backendAdapter.ts
  • src/store/useAppStore.ts

Comment on lines +111 to +123
const repoIds = repositories
.map((repo) => repo.id)
.filter((id): id is number => typeof id === 'number');

if (repoIds.length === 0) {
deleteAllReleases.run();
deleteAllRepositories.run();
return 0;
}

const placeholders = repoIds.map(() => '?').join(', ');
deleteReleasesNotIn(placeholders).run(...repoIds);
deleteRepositoriesNotIn(placeholders).run(...repoIds);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Don't let an empty or malformed sync purge the whole dataset.

Line 115 deletes both tables whenever repoIds.length === 0. Because that array is built by filtering non-numeric IDs, a malformed batch is treated the same as an intentional empty sync. Unlike server/src/routes/releases.ts:78-93, this route never rejects missing IDs, and src/services/backendAdapter.ts:201-210 can also send an empty in-memory state. That turns transient client-state or payload bugs into full data loss. Validate every repository ID before entering the transaction, and make the delete-all path explicit instead of the default for [].

Minimum validation guard
     if (!Array.isArray(repositories)) {
       res.status(400).json({ error: 'repositories array required', code: 'REPOSITORIES_ARRAY_REQUIRED' });
       return;
     }
+    for (const repo of repositories) {
+      const id = repo.id;
+      if (typeof id !== 'number' || !Number.isInteger(id) || id <= 0) {
+        res.status(400).json({ error: 'Each repository must have a numeric id', code: 'REPOSITORY_ID_REQUIRED' });
+        return;
+      }
+    }

     const upsert = db.transaction(() => {
-      const repoIds = repositories
-        .map((repo) => repo.id)
-        .filter((id): id is number => typeof id === 'number');
+      const repoIds = repositories.map((repo) => repo.id as number);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/src/routes/repositories.ts` around lines 111 - 123, The current logic
treats a filtered-empty repoIds as an intent to purge; instead, validate inputs
and make full-delete explicit: before the transaction, check the original
repositories array and ensure every item yields a numeric id (compare
repositories.length vs repoIds.length) and reject (throw/return 400) if any id
is missing/malformed; only allow the deleteAllReleases.run() /
deleteAllRepositories.run() path when the caller explicitly requested a purge
(e.g., an explicit boolean flag or an empty original repositories payload
known-intent), and otherwise skip deletion when repoIds.length === 0; keep the
deleteReleasesNotIn(placeholders).run(...repoIds) and
deleteRepositoriesNotIn(placeholders).run(...repoIds) usage but only call them
when repoIds.length > 0.

@AmintaCCCP
Copy link
Owner

AmintaCCCP commented Mar 14, 2026

已推送修复到 PR 分支(fix/mobile-sync-stability)。

修改原因:

  • 避免后端在非全量同步/空列表场景误删所有数据。
  • 修复统计区块 JSX 结构异常,防止布局错乱。

修复说明:

  1. 后端只在请求携带 isFullSync=true 时才执行删除不在列表内的 repos/releases。
  2. 恢复 RepositoryList 统计区块正常嵌套结构,并将同步动画禁用包裹在统计块外层。

@AmintaCCCP AmintaCCCP merged commit 1e9f603 into AmintaCCCP:main Mar 14, 2026
5 checks passed
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.

2 participants