Skip to content

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

Open
HuberttFox wants to merge 3 commits intoAmintaCCCP:mainfrom
HuberttFox:fix/mobile-sync-stability
Open

fix: improve sync stability, auth handling, and mobile layoutFix/mobile sync stability#55
HuberttFox wants to merge 3 commits intoAmintaCCCP:mainfrom
HuberttFox:fix/mobile-sync-stability

Conversation

@HuberttFox
Copy link

@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

    • Added sync action button to header for quick synchronization
    • Added top navigation on mobile devices for switching between repositories, releases, and settings
    • Enhanced visual feedback during repository synchronization operations
  • Improvements

    • Comprehensive responsive design overhaul across all major components for improved mobile experience
    • Improved backend authentication verification and error messaging
    • Better overflow handling for user info display

@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

This PR introduces a responsive UI redesign for mobile/tablet devices across multiple frontend components, adds bulk repository cleanup logic in the backend, and implements local change tracking with visual sync state signaling to prevent overwriting user edits during synchronization.

Changes

Cohort / File(s) Summary
Backend Repository Management
server/src/routes/repositories.ts
Adds bulk cleanup workflow: introduces cleaners for releases and repositories tables, deletes records not in incoming repo ID set, maintains referential integrity before upsert loop.
Responsive Layout Redesign
src/App.tsx, src/components/Header.tsx, src/components/CategorySidebar.tsx, src/components/ReleaseTimeline.tsx, src/components/SearchBar.tsx
Converts fixed layouts to responsive column/row designs with breakpoint-based visibility; adds responsive navigation, adjusted padding/spacing (px-3→px-4, py-4→py-8), horizontal scrollers on small screens, and conditional element visibility via sm:, lg: breakpoints.
Sync State & Visual Feedback
src/components/RepositoryCard.tsx, src/components/RepositoryList.tsx, src/index.css, src/services/autoSync.ts
Introduces local change tracking (_hasPendingLocalChanges), visual sync state signaling via gsm:repository-sync-visual-state event, forces sync on unstar action, disables animations during sync with .repository-list-syncing CSS class, and adds forceSyncToBackend() to immediately push changes.
Backend Authentication & Verification
src/services/backendAdapter.ts, src/components/SettingsPanel.tsx
Adds verifyAuth() method to check backend API secret validity, updates error messages to include both server status and API secret correctness, changes auth header retrieval from localStorage to useAppStore.
State Management & Utilities
src/store/useAppStore.ts
Implements session-scoped backend secret handling via sessionStorage, adds setGitHubToken() and logout() to AppActions, introduces getAllCategories() and translateCategoryName() helper functions for category management.
Service Logic Enhancement
src/services/aiService.ts
Improves message content extraction: safely checks message.content type, adds fallback to reasoning_content when primary content unavailable, maintains existing openai-responses path behavior.

Sequence Diagram

sequenceDiagram
    participant User as User Action
    participant UI as UI Component<br/>(RepositoryCard)
    participant AutoSync as AutoSync Service
    participant Backend as Backend API
    participant Store as App Store
    
    User->>UI: Unstar repository
    UI->>AutoSync: forceSyncToBackend()
    activate AutoSync
    AutoSync->>AutoSync: Clear debounce timer
    AutoSync->>AutoSync: Mark pending local changes
    AutoSync->>UI: Emit sync-visual-state (isSyncing=true)
    UI->>UI: Disable card animations
    AutoSync->>Backend: PATCH /api/repositories
    activate Backend
    Backend-->>AutoSync: Success response
    deactivate Backend
    AutoSync->>AutoSync: Update last-known hashes
    AutoSync->>AutoSync: Clear pending changes flag
    AutoSync->>UI: Emit sync-visual-state (isSyncing=false)
    deactivate AutoSync
    UI->>UI: Re-enable card animations
    UI-->>User: Visual feedback complete
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly Related PRs

Poem

🐰 Mobile screens now bend and sway,
Local changes won't be swept away,
Backend pruning, secrets to keep,
Sync flows silent, responsive and deep,
From tiny phones to grand displays,
This rabbit hops in responsive ways! 🌙

🚥 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 improvements, auth handling fixes, and mobile layout enhancements, which align with the actual changes across backend, sync, 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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant