Skip to content

Add notes feature, sysinfo, and auth window (v0.5.0)#5

Merged
devohmycode merged 1 commit intomasterfrom
0.5.0
Feb 24, 2026
Merged

Add notes feature, sysinfo, and auth window (v0.5.0)#5
devohmycode merged 1 commit intomasterfrom
0.5.0

Conversation

@devohmycode
Copy link
Copy Markdown
Owner

@devohmycode devohmycode commented Feb 24, 2026

Summary

  • Notes feature: New note-taking system with editor, panel, source list, and sticky board components (NoteEditor, NotePanel, NoteSourceList, NoteStickyBoard)
  • System info: Added system information display and Tauri backend integration
  • Auth window: Improved authentication flow with updated Supabase auth context
  • Sync service: Extended sync capabilities for notes and podcast sources
  • UI enhancements: Updated TitleBar, SettingsModal, SourcePanel, and FeedPanel with new styles and functionality

Test plan

  • Verify notes can be created, edited, and deleted
  • Verify sticky board displays notes correctly
  • Verify system info is displayed properly
  • Verify auth window flow works end-to-end
  • Verify sync service handles notes and podcast sources
  • Verify existing feed/source functionality is not broken

Summary by CodeRabbit

Release Notes

  • New Features

    • Notes system with card and board view modes, folder organization, and inline editing
    • Pin feeds and folders to the title bar for quick access
    • System monitoring display (CPU, memory, network speeds) in the title bar
    • Configurable sync interval from settings
    • Enhanced OAuth authentication flow with dedicated auth window
    • Podcast source support
  • Improvements

    • Better sync error visibility with dismissible notifications
    • Mark items as unread from the feed panel
    • System info visibility toggle in settings
    • New font styling enhancements

Introduce a local notes (SuperNote) subsystem and system info + authentication helpers.

- Add new Note UI: NotePanel, NoteEditor, NoteSourceList, NoteStickyBoard and wiring in App.tsx (notes state, folders, CRUD handlers, board/cards views, pinned items).
- Integrate notes into layout and TitleBar; add UI controls for sync interval, show system info toggle, and a visible sync error toast.
- FeedPanel: add "mark all as unread" support and update action button UI.
- Tauri: expose native commands for CPU/memory/network metrics and an open_auth_window helper; register them in invoke_handler and enable an "auth" webview capability. Bump tauri product version.
- Add sysinfo dependency to src-tauri/Cargo.toml (used by new native commands).
- Add a DB migration SQL to add podcast source and a minor font update in index.html.

These changes enable an offline note-taking mode, surface basic system metrics to the frontend, and provide a dedicated auth webview for sign-in flows while improving sync configurability and error visibility.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 24, 2026

📝 Walkthrough

Walkthrough

Introduces system monitoring capabilities through Tauri commands (CPU, memory, network), implements a comprehensive note-taking system with multiple UI components and view modes, adds feed/folder pinning functionality, enhances OAuth with Tauri window handling, improves sync error visibility and Supabase integration with PKCE flow.

Changes

Cohort / File(s) Summary
Tauri Backend & System Monitoring
src-tauri/src/lib.rs, src-tauri/Cargo.toml, src-tauri/tauri.conf.json, src-tauri/capabilities/default.json
Added new Tauri commands for system monitoring (get_cpu_usage, get_memory_usage, get_net_speed) with public data structures (MemoryInfo, NetSpeed). Implemented open_auth_window for OAuth flow with platform-specific handling. Added sysinfo dependency, updated capabilities to support auth window and event emission, and bumped config version to 0.5.0.
Note-Taking System Components
src/components/NotePanel.tsx, src/components/NoteEditor.tsx, src/components/NoteStickyBoard.tsx, src/components/NoteSourceList.tsx
Introduced four new note components: NotePanel (grid/board views with CRUD), NoteEditor (inline text editing with debounced updates), NoteStickyBoard (drag-and-drop sticky notes with color themes and rotation), NoteSourceList (hierarchical folder-based note organization with context menus).
Core App Integration & State Management
src/App.tsx, src/components/SourcePanel.tsx
Extended App.tsx with comprehensive note state management (notes, folders, selections) persisted to localStorage, sync interval configuration, sysinfo visibility toggle, and sync error handling. Updated SourcePanel props to integrate notes system, pinning functionality (PinnedItems type and getPinnedItems export), settings callbacks, and conditional NoteSourceList rendering.
UI Components & Settings
src/components/TitleBar.tsx, src/components/FeedPanel.tsx, src/components/SettingsModal.tsx
Extended TitleBar to display pinned items, system info (CPU/RAM/network) with polling when collapsed, and sync controls. Added onMarkAllAsUnread prop to FeedPanel with dynamic button behavior. Enhanced SettingsModal with sync interval dropdown and sysinfo visibility toggle, both persisted to localStorage.
Authentication & Data Sync
src/contexts/AuthContext.tsx, src/services/syncService.ts, src/lib/supabase.ts
Implemented Tauri-based OAuth flow using open_auth_window with auth-callback event listener and code exchange. Added SYNC_ERROR_EVENT export and dispatchSyncError helper. Enhanced fullSync with URL-based feed/item deduplication and remote-to-local ID mapping. Improved push logic with ensureFeedInSupabase checks and batch handling. Enabled PKCE auth flow in Supabase config.
Feed Store & Hook
src/hooks/useFeedStore.ts
Added markAllAsUnread(feedId?) method to FeedStore, mirroring existing markAllAsRead pattern with batch state updates and listener notifications.
Styling
src/index.css
Added 878 lines of new CSS covering titlebar system monitors, pins, sync button animations, and extensive note-panel styling for cards, board, sticky notes, source list, and editor views across light/dark/sepia themes.
Database & Configuration
supabase/migrations/20250224000004_add_podcast_source.sql, index.html
Added podcast to feeds_source_check constraint in Supabase migration. Extended Google Fonts link to include Caveat font alongside existing fonts.
Specification & Documentation
.specstory/history/2026-02-23_16-43Z-produire-une-description-de-pull-request-pour-la-branche-git.md
Added Markdown spec file defining instructions for AI-generated PR description generation, including GitHub naming/formatting standards and exclusion rules.

Sequence Diagram(s)

sequenceDiagram
    participant User as User (Browser)
    participant App as App.tsx
    participant AuthCtx as AuthContext
    participant Tauri as Tauri Backend
    participant OAuth as OAuth Provider
    participant Supabase as Supabase Auth

    User->>App: Trigger OAuth Sign In
    App->>AuthCtx: signInWithOAuth(provider)
    AuthCtx->>Supabase: signInWithOAuth (skipBrowserRedirect)
    Supabase-->>AuthCtx: Return OAuth URL + session data
    AuthCtx->>Tauri: invoke('open_auth_window', { url })
    Tauri->>OAuth: Open auth window with redirect
    OAuth->>User: Display login form
    User->>OAuth: Enter credentials & authorize
    OAuth-->>Tauri: Redirect to callback URL with PKCE code
    Tauri-->>AuthCtx: Emit 'auth-callback' event with code
    AuthCtx->>Supabase: exchangeCodeForSession(code)
    Supabase-->>AuthCtx: Return authenticated session + user
    AuthCtx->>AuthCtx: Update local state (user, session)
    AuthCtx->>App: Context listeners update
    App-->>User: Display authenticated state
    Tauri->>Tauri: Close auth window
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • SuperFlux#2 — Modifies overlapping frontend components (App.tsx, SourcePanel.tsx, FeedPanel.tsx) with similar UI action and prop wiring patterns for feed management features.

Poem

🐰 Hops of joy through notepads dancing,
Sticky dreams and pins are prancing,
Tauri's breath counts heartbeats fast,
OAuth flows—at last, at last!
Sync's soft whisper, errors clear,
Superflux springs forth with cheer! 🌱✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.00% 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 PR title clearly summarizes the main features added: notes feature, system information display, authentication window, and version bump to v0.5.0.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 0.5.0

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.

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Add notes feature, system monitoring, and auth improvements (v0.5.0)

✨ Enhancement 🐞 Bug fix

Grey Divider

Walkthroughs

Description
• **Notes feature**: Comprehensive note-taking system with editor, panel, source list with folder
  management, and sticky board component for visual note organization
• **System monitoring**: Added CPU, memory, and network speed monitoring with Tauri backend
  integration and real-time display in titlebar
• **Authentication improvements**: Implemented Tauri-based OAuth with PKCE flow, custom auth window,
  and enhanced session management with localStorage cleanup on sign-out
• **Sync service enhancements**: Fixed race conditions between pushFeed and pushNewItems with
  feed caching and in-flight promise tracking; improved merge logic to match by URL; added FK
  constraint validation; enhanced error handling with event dispatching
• **UI enhancements**: Added pinning feature for feeds/folders, mark-all-as-unread functionality
  with visual feedback, sync interval configuration, system info visibility toggle, and comprehensive
  styling for all new features
• **Database updates**: Added podcast source type support and configured PKCE authentication flow
Diagram
flowchart LR
  A["Notes System<br/>Editor, Panel, Folders,<br/>Sticky Board"]
  B["System Monitoring<br/>CPU, Memory,<br/>Network Speed"]
  C["Auth Window<br/>PKCE Flow<br/>Session Management"]
  D["Sync Service<br/>Race Condition Fixes<br/>Error Handling"]
  E["UI Enhancements<br/>Pinning, Mark All,<br/>Settings"]
  F["App Integration<br/>State Management<br/>localStorage"]
  
  A --> F
  B --> F
  C --> F
  D --> F
  E --> F
Loading

Grey Divider

File Changes

1. src/services/syncService.ts 🐞 Bug fix +180/-33

Sync service race condition fixes and error handling

• Added sync error event dispatching (SYNC_ERROR_EVENT) to surface errors in the UI
• Implemented feed cache and in-flight promise tracking to prevent race conditions between
 pushFeed and pushNewItems
• Enhanced merge logic to match feeds and items by URL in addition to ID, handling cross-platform ID
 mismatches
• Added ensureFeedInSupabase function to verify parent feeds exist before inserting items (FK
 constraint)
• Improved error handling with dispatchSyncError instead of silent console logging
• Changed from upsert to insert for new feeds/items to better handle database constraints

src/services/syncService.ts


2. src/hooks/useFeedStore.ts ✨ Enhancement +20/-0

Add mark all as unread functionality

• Added markAllAsUnread function to mark all items or items in a feed as unread
• Exported new function in the FeedStore interface

src/hooks/useFeedStore.ts


3. src/lib/supabase.ts ⚙️ Configuration changes +3/-1

Enable PKCE authentication flow

• Configured Supabase client with PKCE flow type for improved authentication security

src/lib/supabase.ts


View more (19)
4. src/index.css ✨ Enhancement +878/-0

Comprehensive styling for notes and system info features

• Added styles for system info display in titlebar (.titlebar-sysinfo, .titlebar-monitor,
 .titlebar-net)
• Added spinning animation for sync button (.titlebar-btn-sync.syncing)
• Added pinned feeds/folders display styles (.titlebar-pins, .titlebar-pin)
• Added comprehensive note panel and card styles (.note-panel, .note-card, .note-empty)
• Added note source list styles (.nsrc-* classes for folders and notes)
• Added sticky board and sticky note styles with drag, rotation, and color support
• Added note editor styles (.note-editor-* classes)

src/index.css


5. src-tauri/src/lib.rs ✨ Enhancement +120/-1

System monitoring and auth window Tauri commands

• Added get_cpu_usage command to retrieve CPU usage percentage
• Added get_memory_usage command returning used/total GB and percentage
• Added get_net_speed command to track download/upload speeds with delta calculation
• Added open_auth_window command to open authentication window with callback interception (desktop
 only)
• Registered new commands in the Tauri handler

src-tauri/src/lib.rs


6. index.html Dependencies +1/-1

Add Caveat font for notes styling

• Added Caveat font import for handwriting-style note display

index.html


7. src/App.tsx ✨ Enhancement +174/-12

Integrate notes feature and sync improvements

• Added sync error event listener to display error toasts
• Implemented notes state management with localStorage persistence
• Added note folders and selection state
• Integrated NotePanel and NoteEditor components for note mode
• Added sync interval configuration with localStorage
• Added system info visibility toggle
• Added pinned items state and management
• Updated TitleBar with new props for pins, sync, and system info
• Updated SourcePanel with note-related props
• Updated FeedPanel with markAllAsUnread callback

src/App.tsx


8. src/components/NoteStickyBoard.tsx ✨ Enhancement +305/-0

New sticky board component for note visualization

• New component for displaying notes as draggable sticky notes on a canvas
• Supports color selection, drag positioning, rotation, and z-index management
• Includes edit/save/delete actions for each sticky note
• Implements pointer events for drag handling with boundary constraints

src/components/NoteStickyBoard.tsx


9. src/components/NoteSourceList.tsx ✨ Enhancement +355/-0

New note source list with folder management

• New component for managing note organization with folders
• Supports creating, renaming, and deleting folders
• Implements context menus for moving notes between folders
• Displays folder hierarchy with expand/collapse and note counts
• Includes "All notes" view and root notes display

src/components/NoteSourceList.tsx


10. src/components/TitleBar.tsx ✨ Enhancement +121/-1

Enhanced titlebar with system info and pinned items

• Added system info display (CPU, memory, network speed) when collapsed
• Added pinned feeds/folders display with unread count badges
• Added sync button with spinning animation during sync
• Implemented polling for system metrics every 2 seconds
• Added speed formatting utility for network display

src/components/TitleBar.tsx


11. src/components/SourcePanel.tsx ✨ Enhancement +117/-1

Add pinning feature and note mode integration

• Added pin/unpin functionality for feeds and folders with localStorage persistence
• Integrated NoteSourceList component for note mode
• Added context menu options to pin/unpin feeds and folders
• Passed note-related props to child components
• Added sync interval and system info settings to SettingsModal

src/components/SourcePanel.tsx


12. src/components/SettingsModal.tsx ✨ Enhancement +70/-1

Add sync interval and system info settings

• Added sync interval configuration dropdown with 11 preset options (1 min to 12 hours)
• Added system info visibility toggle (enabled/disabled)
• Integrated callbacks for sync interval and system info changes

src/components/SettingsModal.tsx


13. src/components/NotePanel.tsx ✨ Enhancement +126/-0

New note panel with dual view modes

• New component for displaying notes in cards or sticky board view modes
• Supports switching between card grid and sticky board layouts
• Displays note count and provides add/delete actions
• Shows empty state with helpful hint

src/components/NotePanel.tsx


14. src/components/NoteEditor.tsx ✨ Enhancement +100/-0

New note editor with auto-save

• New component for editing individual notes with title and content
• Implements debounced auto-save (300ms) to persist changes
• Displays formatted modification date
• Shows empty state when no note is selected

src/components/NoteEditor.tsx


15. supabase/migrations/20250224000004_add_podcast_source.sql ⚙️ Configuration changes +3/-0

Add podcast source to database constraint

• Added podcast to the feeds source CHECK constraint to support podcast source type

supabase/migrations/20250224000004_add_podcast_source.sql


16. src/components/FeedPanel.tsx ✨ Enhancement +11/-4

Add mark-all-as-unread functionality with visual feedback

• Added onMarkAllAsUnread callback prop to FeedPanelProps interface
• Updated FeedPanel function signature to accept the new onMarkAllAsUnread prop
• Enhanced mark-all button with conditional logic to toggle between mark-as-read and mark-as-unread
 based on unreadCount
• Replaced text checkmark with SVG icon that displays as empty circle when unread items exist,
 filled circle when all items are read

src/components/FeedPanel.tsx


17. src/contexts/AuthContext.tsx ✨ Enhancement +51/-1

Implement Tauri-based OAuth authentication with session management

• Integrated Tauri API imports (invoke, listen, WebviewWindow) for native window management
• Enhanced signInWithOAuth to use PKCE flow with skipBrowserRedirect and custom redirect URL
• Implemented auth callback listener that exchanges OAuth code for session and updates auth state
• Added Tauri window invocation to open separate auth window for OAuth flow
• Extended signOut to clear all user data from localStorage including feeds, items, folders,
 notes, and sync metadata

src/contexts/AuthContext.tsx


18. src-tauri/gen/schemas/capabilities.json ⚙️ Configuration changes +1/-1

Update Tauri capabilities for auth window support

• Added auth window to the windows array alongside main
• Added core:event:default permission for event handling
• Added core:webview:allow-create-webview-window permission for creating auth window

src-tauri/gen/schemas/capabilities.json


19. .specstory/history/2026-02-23_16-43Z-produire-une-description-de-pull-request-pour-la-branche-git.md 📝 Documentation +18/-0

Add SpecStory session history documentation

• New SpecStory history file documenting a session for generating PR description
• Contains metadata about the session including timestamp and user request

.specstory/history/2026-02-23_16-43Z-produire-une-description-de-pull-request-pour-la-branche-git.md


20. src-tauri/capabilities/default.json ⚙️ Configuration changes +3/-1

Update default capabilities for auth window creation

• Added auth window to the windows array
• Added core:event:default permission for event communication
• Added core:webview:allow-create-webview-window permission for auth window creation

src-tauri/capabilities/default.json


21. src-tauri/tauri.conf.json ⚙️ Configuration changes +1/-1

Update application version to 0.5.0

• Bumped version from 0.4.0 to 0.5.0

src-tauri/tauri.conf.json


22. src-tauri/Cargo.toml Dependencies +1/-0

Add sysinfo dependency for system information

• Added sysinfo = "0.32" dependency for system information retrieval

src-tauri/Cargo.toml


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Feb 24, 2026

Code Review by Qodo

🐞 Bugs (6) 📘 Rule violations (6) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Notes CRUD lacks audit logs 📘 Rule violation ⛨ Security
Description
The new notes feature creates/updates/deletes user content without any audit trail (no user ID,
timestamped action, or outcome logging). This makes it difficult to reconstruct sensitive user-data
changes for security/compliance investigations.
Code

src/App.tsx[R139-163]

+  const handleAddNote = useCallback(() => {
+    const newNote: Note = {
+      id: crypto.randomUUID(),
+      title: 'Nouvelle note',
+      content: '',
+      folder: selectedNoteFolder ?? undefined,
+      createdAt: new Date().toISOString(),
+      updatedAt: new Date().toISOString(),
+    };
+    setNotes(prev => [newNote, ...prev]);
+    setSelectedNoteId(newNote.id);
+  }, [selectedNoteFolder]);
+
+  const handleDeleteNote = useCallback((noteId: string) => {
+    setNotes(prev => prev.filter(n => n.id !== noteId));
+    setSelectedNoteId(prev => prev === noteId ? null : prev);
+  }, []);
+
+  const handleUpdateNote = useCallback((noteId: string, updates: Partial<Note>) => {
+    setNotes(prev => prev.map(n =>
+      n.id === noteId
+        ? { ...n, ...updates, updatedAt: new Date().toISOString() }
+        : n
+    ));
+  }, []);
Evidence
PR Compliance ID 1 requires logging critical actions (including write/delete of sensitive data) with
user context and outcomes. The added note handlers mutate persisted note data (stored to
localStorage) but emit no audit events/log entries tying actions to a user or result.

Rule 1: Generic: Comprehensive Audit Trails
src/App.tsx[139-163]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New note create/update/delete operations are performed without audit logging, making it impossible to reconstruct user actions on potentially sensitive note content.

## Issue Context
Compliance requires audit logs for critical actions with user ID, timestamp, action description, and outcome. Notes are persisted (via `localStorage`) and can contain sensitive data.

## Fix Focus Areas
- src/App.tsx[139-163]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Sync toast leaks internal errors 📘 Rule violation ⛨ Security
Description
The UI displays raw sync error messages from backend/Supabase to end users. These messages can
reveal internal system details (e.g., table/constraint names) and should be replaced with generic
user-facing text while keeping detailed errors only in internal logs.
Code

src/App.tsx[R71-74]

+    const handler = (e: Event) => {
+      const detail = (e as CustomEvent).detail as { operation: string; message: string };
+      setSyncError(`Sync: ${detail.operation} — ${detail.message}`);
+      setTimeout(() => setSyncError(null), 8000);
Evidence
PR Compliance ID 4 requires generic user-facing errors and prohibits exposing internal details. The
added handler constructs a visible toast string using the raw detail.message, which is derived
from underlying errors and can contain sensitive implementation details.

Rule 4: Generic: Secure Error Handling
src/App.tsx[71-74]
src/App.tsx[605-613]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
User-facing UI toasts show raw sync error messages (`detail.message`) which may leak internal system details.

## Issue Context
Secure error handling requires generic messages for end users and detailed diagnostics only in secure/internal logs.

## Fix Focus Areas
- src/App.tsx[71-74]
- src/App.tsx[605-613]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. console.log leaks userId 📘 Rule violation ⛨ Security
Description
Sync logging prints userId (and other operational details) to the console in unstructured form.
This increases the risk of exposing PII/sensitive identifiers in logs and makes logs harder to
audit.
Code

src/services/syncService.ts[R190-193]

  setUserId(userId: string | null) {
+    console.log('[sync] setUserId:', userId);
    _currentUserId = userId;
    if (!userId) {
Evidence
PR Compliance ID 5 requires secure logging practices: no sensitive data in logs and structured logs
for auditing. The new code logs userId directly to the console with free-form strings.

Rule 5: Generic: Secure Logging Practices
src/services/syncService.ts[190-193]
src/services/syncService.ts[367-377]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Sync code logs `userId` and other details in plain, unstructured console logs.

## Issue Context
Secure logging requires avoiding PII/sensitive identifiers in logs and using structured logs for auditing.

## Fix Focus Areas
- src/services/syncService.ts[190-193]
- src/services/syncService.ts[367-377]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (5)
4. open_auth_window accepts any URL 📘 Rule violation ⛨ Security
Description
The new open_auth_window command builds an external webview window from a caller-provided URL
without allowlisting/validation. This can enable opening arbitrary external content inside the app
if the command is invoked with a malicious URL.
Code

src-tauri/src/lib.rs[R594-606]

+async fn open_auth_window(app: tauri::AppHandle, url: String) -> Result<(), String> {
+    use tauri::{Emitter, WebviewUrl, WebviewWindowBuilder};
+
+    // Close any existing auth window
+    if let Some(existing) = app.get_webview_window("auth") {
+        let _ = existing.close();
+    }
+
+    let parsed_url: Url = url.parse().map_err(|e: url::ParseError| format!("Invalid URL: {e}"))?;
+    let app_handle = app.clone();
+
+    WebviewWindowBuilder::new(&app, "auth", WebviewUrl::External(parsed_url))
+        .title("Sign in")
Evidence
PR Compliance ID 6 requires security-first validation of external inputs. The added Tauri command
parses an arbitrary url string and opens it as WebviewUrl::External without validating
scheme/host against expected auth domains.

Rule 6: Generic: Security-First Input Validation and Data Handling
src-tauri/src/lib.rs[594-606]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`open_auth_window` opens an external webview for any parsed URL without allowlisting, which is unsafe for externally-influenced input.

## Issue Context
Security-first input validation requires validating/sanitizing external inputs and preventing unsafe navigation to arbitrary domains.

## Fix Focus Areas
- src-tauri/src/lib.rs[594-606]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. mtx.lock().unwrap() may panic 📘 Rule violation ⛯ Reliability
Description
The new sysinfo Tauri commands use unwrap() on mutex locks, which can crash the app if the mutex
is poisoned or unavailable. This is a new failure mode without graceful error handling or fallback
behavior.
Code

src-tauri/src/lib.rs[R267-275]

+    static SYS: OnceLock<Mutex<System>> = OnceLock::new();
+    let mtx = SYS.get_or_init(|| {
+        let mut sys = System::new();
+        sys.refresh_cpu_usage();
+        Mutex::new(sys)
+    });
+    let mut sys = mtx.lock().unwrap();
+    sys.refresh_cpu_usage();
+    sys.global_cpu_usage()
Evidence
PR Compliance ID 3 requires robust error handling and graceful degradation. The added sysinfo code
uses unwrap() on a mutex lock, which can panic rather than returning a controlled error to the
frontend.

Rule 3: Generic: Robust Error Handling and Edge Case Management
src-tauri/src/lib.rs[267-275]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Sysinfo commands use `unwrap()` on mutex locks, which can panic and crash the app under error conditions.

## Issue Context
Robust error handling requires graceful degradation and actionable diagnostics rather than panics.

## Fix Focus Areas
- src-tauri/src/lib.rs[267-275]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Feed folders not synced 🐞 Bug ✓ Correctness
Description
syncService.feedToRow() no longer includes the folder column, so folder organization changes
will not be persisted to Supabase and will be lost/mismatched across devices. The app heavily relies
on feed.folder for folder UI and pinned-folder unread aggregation.
Code

src/services/syncService.ts[R43-48]

    icon: feed.icon,
    url: feed.url,
    color: feed.color,
-    folder: feed.folder ?? null,
    updated_at: feed.updated_at ?? new Date().toISOString(),
  };
}
Evidence
Supabase schema includes feeds.folder, the app mutates feed.folder during folder CRUD, and
rowToFeed still reads row.folder; but feedToRow omits it, meaning pushes/upserts never write
folder values.

src/services/syncService.ts[37-62]
supabase/migrations/20250216000002_feed_folders.sql[1-1]
supabase/migrations/20250214000001_init.sql[82-94]
src/hooks/useFeedStore.ts[491-551]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Feed folder organization is not being persisted to Supabase because `feedToRow()` omits the `folder` column.

## Issue Context
- The database schema includes `feeds.folder`.
- The UI and folder CRUD logic rely on `feed.folder`.
- Sync pulls `row.folder` but never pushes `feed.folder`.

## Fix Focus Areas
- src/services/syncService.ts[37-48]
- src/services/syncService.ts[50-62]

## Suggested change
- Add `folder: feed.folder ?? null` (or `folder: feed.folder`) back to `feedToRow()` so `pushFeed`, `ensureFeedInSupabase`, and `fullSync` feed inserts/upserts persist folder state.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Android auth invoke mismatch 🐞 Bug ✓ Correctness
Description
On Android, the Rust open_auth_window command parameter is named _url, but the frontend invokes
it with { url: ... }. Tauri matches parameters by name, so OAuth will likely fail to open the auth
window on Android.
Code

src-tauri/src/lib.rs[R623-626]

+#[cfg(target_os = "android")]
+#[tauri::command]
+async fn open_auth_window(_url: String) -> Result<(), String> {
+    Ok(())
Evidence
Frontend invokes the command with the url key, while the Android command signature expects _url.
The non-Android version uses url: String, highlighting the inconsistency.

src-tauri/src/lib.rs[592-627]
src/contexts/AuthContext.tsx[57-94]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Android build likely fails to invoke `open_auth_window` because the Rust parameter name is `_url` but the frontend sends `{ url: ... }`.

## Issue Context
Tauri `invoke()` payload keys must match the command function parameter names.

## Fix Focus Areas
- src-tauri/src/lib.rs[623-627]
- src/contexts/AuthContext.tsx[57-94]

## Suggested change
- Change Android implementation to:
 - `async fn open_auth_window(url: String) -&gt; Result&lt;(), String&gt; { let _ = url; Ok(()) }`
- Keep frontend invocation as-is (`{ url: data.url }`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Sticky z-index collision 🐞 Bug ✓ Correctness
Description
When initializing sticky-note properties, all notes missing stickyX receive `stickyZIndex: maxZ +
1` within the same effect run, leading to identical z-indexes and incorrect stacking order.
Code

src/components/NoteStickyBoard.tsx[R240-249]

+    for (const note of notes) {
+      if (note.stickyX === undefined) {
+        onUpdateNote(note.id, {
+          stickyX: Math.random() * Math.max(100, w - 260) + 30,
+          stickyY: Math.random() * Math.max(100, h - 260) + 30,
+          stickyRotation: randomRotation(),
+          stickyZIndex: maxZ + 1,
+          stickyColor: selectedColor,
+        });
+        setMaxZ(z => z + 1);
Evidence
maxZ is read once per effect run; setMaxZ increments state asynchronously, but stickyZIndex
uses maxZ + 1 for every initialized note in the loop.

src/components/NoteStickyBoard.tsx[210-219]
src/components/NoteStickyBoard.tsx[235-252]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Sticky note initialization assigns the same z-index to multiple notes created/initialized in a single effect run.

## Issue Context
This manifests when multiple notes are present without sticky props (e.g., initial migration, importing notes, or creating multiple notes quickly).

## Fix Focus Areas
- src/components/NoteStickyBoard.tsx[235-252]

## Suggested change
- Use a local counter:
 - `let z = maxZ;`
 - for each init: `z += 1; stickyZIndex: z`
 - after loop: `setMaxZ(z)`
- Alternatively store `maxZ` in a ref and increment synchronously.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

9. Single-letter variable v 📘 Rule violation ✓ Correctness
Description
New code introduces non-idiomatic single-letter variables (e.g., v) for values that are not
conventional short-lived iterators. This reduces readability and makes intent harder to understand
without extra context.
Code

src/App.tsx[R33-36]

+  try {
+    const v = localStorage.getItem(SYNC_INTERVAL_KEY);
+    if (v) return Number(v);
+  } catch { /* ignore */ }
Evidence
PR Compliance ID 2 requires meaningful, self-documenting identifier names and discourages
single-letter variables except conventional iterators. The new getSyncInterval() uses v for a
persisted configuration value, obscuring intent.

Rule 2: Generic: Meaningful Naming and Self-Documenting Code
src/App.tsx[33-36]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Single-letter variables like `v` reduce readability and violate the self-documenting naming guideline.

## Issue Context
The value represents a persisted configuration (sync interval) and should be named accordingly.

## Fix Focus Areas
- src/App.tsx[33-36]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Invalid sync interval value 🐞 Bug ⛯ Reliability
Description
getSyncInterval() returns Number(localStorageValue) without checking for NaN/finite bounds. A
corrupted value (e.g., 'abc') will propagate into setInterval, which can effectively become a 0ms
loop and hammer sync APIs.
Code

src/App.tsx[R32-37]

+function getSyncInterval(): number {
+  try {
+    const v = localStorage.getItem(SYNC_INTERVAL_KEY);
+    if (v) return Number(v);
+  } catch { /* ignore */ }
+  return DEFAULT_SYNC_INTERVAL;
Evidence
The sync interval is loaded from localStorage via Number(v) and then used directly as the delay
for periodic fullSync and provider sync timers.

src/App.tsx[28-38]
src/App.tsx[213-240]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Sync interval is parsed from localStorage without validation; corrupted values can become `NaN` and cause excessively frequent timers.

## Issue Context
This is primarily defensive: Settings UI writes valid values, but localStorage can be corrupted or manually edited.

## Fix Focus Areas
- src/App.tsx[28-38]
- src/App.tsx[213-240]

## Suggested change
- Update `getSyncInterval()` to:
 - `const n = Number(v); if (Number.isFinite(n) &amp;&amp; n &gt;= 60_000) return n;`
 - else return `DEFAULT_SYNC_INTERVAL`
- Optionally clamp to the known option set used in Settings.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. OAuth listener not cleaned 🐞 Bug ⛯ Reliability
Description
The auth-callback listener is only removed when the callback event fires. If the user
closes/cancels the auth window (or the invoke fails), the listener remains registered and future
sign-in attempts can accumulate handlers.
Code

src/contexts/AuthContext.tsx[R68-94]

+    // Listen for the auth callback from the Tauri auth window
+    const unlisten = await listen<string>('auth-callback', async (event) => {
+      unlisten();
+      // Close the auth window
+      const authWindow = await WebviewWindow.getByLabel('auth');
+      if (authWindow) await authWindow.close();
+
+      // Extract PKCE code from callback URL and establish session
+      try {
+        const callbackUrl = new URL(event.payload);
+        const code = callbackUrl.searchParams.get('code');
+        if (code) {
+          const { data, error } = await supabase.auth.exchangeCodeForSession(code);
+          if (error) throw error;
+          // Explicitly update auth state so downstream sync triggers immediately
+          if (data.session) {
+            setState({ user: data.session.user, session: data.session, loading: false });
+          }
+        }
+      } catch (e) {
+        console.error('[auth] Failed to exchange code:', e);
+      }
+    });
+
+    // Open a separate Tauri window for the OAuth flow
+    await invoke('open_auth_window', { url: data.url });
  }, []);
Evidence
The only call to unlisten() is inside the callback handler; there is no timeout, cancellation, or
failure-path cleanup.

src/contexts/AuthContext.tsx[57-94]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`listen(&#x27;auth-callback&#x27;, ...)` is only cleaned up on success. If the user cancels auth, the listener leaks.

## Issue Context
This can cause multiple active listeners over time and duplicated handling if a later auth succeeds.

## Fix Focus Areas
- src/contexts/AuthContext.tsx[57-94]

## Suggested change
- Wrap the flow in try/finally and unlisten in finally when appropriate.
- Add a timeout (e.g., 60s) that calls `unlisten()` and optionally closes the auth window.
- Optionally listen for window close events from the `auth` window and unlisten then.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
12. Orphaned pins after logout 🐞 Bug ⛯ Reliability
Description
Pins are persisted in superflux_pinned but are not cleared on sign-out. If feeds/folders are
cleared (as they are on signOut), TitleBar can still render pinned buttons with empty labels because
it can’t resolve them against categories.
Code

src/contexts/AuthContext.tsx[R99-111]

+    // Clear all user data from localStorage
+    const userDataKeys = [
+      'superflux_feeds',
+      'superflux_items',
+      'superflux_folders',
+      'superflux_favorites_order',
+      'superflux_readlater_order',
+      'superflux_highlights',
+      'superflux_notes',
+      'superflux_note_folders',
+      'superflux_last_sync',
+    ];
+    userDataKeys.forEach(key => localStorage.removeItem(key));
Evidence
Pins are stored under superflux_pinned; signOut clears feeds/items/folders but not pins; TitleBar
renders pins even when the referenced feed/folder can’t be found and leaves the label as an empty
string.

src/components/SourcePanel.tsx[112-129]
src/contexts/AuthContext.tsx[96-112]
src/components/TitleBar.tsx[206-247]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Pins persist across sign-out and can become orphaned after clearing feeds/folders, producing blank/invalid pin buttons.

## Issue Context
`superflux_pinned` is persisted independently from feed data.

## Fix Focus Areas
- src/contexts/AuthContext.tsx[96-112]
- src/components/SourcePanel.tsx[112-129]
- src/components/TitleBar.tsx[206-247]

## Suggested change
- Add `&#x27;superflux_pinned&#x27;` to `userDataKeys` in `signOut()`.
- Optionally also prune pins in `getPinnedItems()` or in `App` when categories load (filter pins that cannot be resolved).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +139 to +163
const handleAddNote = useCallback(() => {
const newNote: Note = {
id: crypto.randomUUID(),
title: 'Nouvelle note',
content: '',
folder: selectedNoteFolder ?? undefined,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
setNotes(prev => [newNote, ...prev]);
setSelectedNoteId(newNote.id);
}, [selectedNoteFolder]);

const handleDeleteNote = useCallback((noteId: string) => {
setNotes(prev => prev.filter(n => n.id !== noteId));
setSelectedNoteId(prev => prev === noteId ? null : prev);
}, []);

const handleUpdateNote = useCallback((noteId: string, updates: Partial<Note>) => {
setNotes(prev => prev.map(n =>
n.id === noteId
? { ...n, ...updates, updatedAt: new Date().toISOString() }
: n
));
}, []);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Notes crud lacks audit logs 📘 Rule violation ⛨ Security

The new notes feature creates/updates/deletes user content without any audit trail (no user ID,
timestamped action, or outcome logging). This makes it difficult to reconstruct sensitive user-data
changes for security/compliance investigations.
Agent Prompt
## Issue description
New note create/update/delete operations are performed without audit logging, making it impossible to reconstruct user actions on potentially sensitive note content.

## Issue Context
Compliance requires audit logs for critical actions with user ID, timestamp, action description, and outcome. Notes are persisted (via `localStorage`) and can contain sensitive data.

## Fix Focus Areas
- src/App.tsx[139-163]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +71 to +74
const handler = (e: Event) => {
const detail = (e as CustomEvent).detail as { operation: string; message: string };
setSyncError(`Sync: ${detail.operation} — ${detail.message}`);
setTimeout(() => setSyncError(null), 8000);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Sync toast leaks internal errors 📘 Rule violation ⛨ Security

The UI displays raw sync error messages from backend/Supabase to end users. These messages can
reveal internal system details (e.g., table/constraint names) and should be replaced with generic
user-facing text while keeping detailed errors only in internal logs.
Agent Prompt
## Issue description
User-facing UI toasts show raw sync error messages (`detail.message`) which may leak internal system details.

## Issue Context
Secure error handling requires generic messages for end users and detailed diagnostics only in secure/internal logs.

## Fix Focus Areas
- src/App.tsx[71-74]
- src/App.tsx[605-613]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 190 to 193
setUserId(userId: string | null) {
console.log('[sync] setUserId:', userId);
_currentUserId = userId;
if (!userId) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. console.log leaks userid 📘 Rule violation ⛨ Security

Sync logging prints userId (and other operational details) to the console in unstructured form.
This increases the risk of exposing PII/sensitive identifiers in logs and makes logs harder to
audit.
Agent Prompt
## Issue description
Sync code logs `userId` and other details in plain, unstructured console logs.

## Issue Context
Secure logging requires avoiding PII/sensitive identifiers in logs and using structured logs for auditing.

## Fix Focus Areas
- src/services/syncService.ts[190-193]
- src/services/syncService.ts[367-377]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +594 to +606
async fn open_auth_window(app: tauri::AppHandle, url: String) -> Result<(), String> {
use tauri::{Emitter, WebviewUrl, WebviewWindowBuilder};

// Close any existing auth window
if let Some(existing) = app.get_webview_window("auth") {
let _ = existing.close();
}

let parsed_url: Url = url.parse().map_err(|e: url::ParseError| format!("Invalid URL: {e}"))?;
let app_handle = app.clone();

WebviewWindowBuilder::new(&app, "auth", WebviewUrl::External(parsed_url))
.title("Sign in")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

4. open_auth_window accepts any url 📘 Rule violation ⛨ Security

The new open_auth_window command builds an external webview window from a caller-provided URL
without allowlisting/validation. This can enable opening arbitrary external content inside the app
if the command is invoked with a malicious URL.
Agent Prompt
## Issue description
`open_auth_window` opens an external webview for any parsed URL without allowlisting, which is unsafe for externally-influenced input.

## Issue Context
Security-first input validation requires validating/sanitizing external inputs and preventing unsafe navigation to arbitrary domains.

## Fix Focus Areas
- src-tauri/src/lib.rs[594-606]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +267 to +275
static SYS: OnceLock<Mutex<System>> = OnceLock::new();
let mtx = SYS.get_or_init(|| {
let mut sys = System::new();
sys.refresh_cpu_usage();
Mutex::new(sys)
});
let mut sys = mtx.lock().unwrap();
sys.refresh_cpu_usage();
sys.global_cpu_usage()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

5. mtx.lock().unwrap() may panic 📘 Rule violation ⛯ Reliability

The new sysinfo Tauri commands use unwrap() on mutex locks, which can crash the app if the mutex
is poisoned or unavailable. This is a new failure mode without graceful error handling or fallback
behavior.
Agent Prompt
## Issue description
Sysinfo commands use `unwrap()` on mutex locks, which can panic and crash the app under error conditions.

## Issue Context
Robust error handling requires graceful degradation and actionable diagnostics rather than panics.

## Fix Focus Areas
- src-tauri/src/lib.rs[267-275]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 43 to 48
icon: feed.icon,
url: feed.url,
color: feed.color,
folder: feed.folder ?? null,
updated_at: feed.updated_at ?? new Date().toISOString(),
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

6. Feed folders not synced 🐞 Bug ✓ Correctness

syncService.feedToRow() no longer includes the folder column, so folder organization changes
will not be persisted to Supabase and will be lost/mismatched across devices. The app heavily relies
on feed.folder for folder UI and pinned-folder unread aggregation.
Agent Prompt
## Issue description
Feed folder organization is not being persisted to Supabase because `feedToRow()` omits the `folder` column.

## Issue Context
- The database schema includes `feeds.folder`.
- The UI and folder CRUD logic rely on `feed.folder`.
- Sync pulls `row.folder` but never pushes `feed.folder`.

## Fix Focus Areas
- src/services/syncService.ts[37-48]
- src/services/syncService.ts[50-62]

## Suggested change
- Add `folder: feed.folder ?? null` (or `folder: feed.folder`) back to `feedToRow()` so `pushFeed`, `ensureFeedInSupabase`, and `fullSync` feed inserts/upserts persist folder state.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +623 to +626
#[cfg(target_os = "android")]
#[tauri::command]
async fn open_auth_window(_url: String) -> Result<(), String> {
Ok(())
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

7. Android auth invoke mismatch 🐞 Bug ✓ Correctness

On Android, the Rust open_auth_window command parameter is named _url, but the frontend invokes
it with { url: ... }. Tauri matches parameters by name, so OAuth will likely fail to open the auth
window on Android.
Agent Prompt
## Issue description
Android build likely fails to invoke `open_auth_window` because the Rust parameter name is `_url` but the frontend sends `{ url: ... }`.

## Issue Context
Tauri `invoke()` payload keys must match the command function parameter names.

## Fix Focus Areas
- src-tauri/src/lib.rs[623-627]
- src/contexts/AuthContext.tsx[57-94]

## Suggested change
- Change Android implementation to:
  - `async fn open_auth_window(url: String) -> Result<(), String> { let _ = url; Ok(()) }`
- Keep frontend invocation as-is (`{ url: data.url }`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +240 to +249
for (const note of notes) {
if (note.stickyX === undefined) {
onUpdateNote(note.id, {
stickyX: Math.random() * Math.max(100, w - 260) + 30,
stickyY: Math.random() * Math.max(100, h - 260) + 30,
stickyRotation: randomRotation(),
stickyZIndex: maxZ + 1,
stickyColor: selectedColor,
});
setMaxZ(z => z + 1);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

8. Sticky z-index collision 🐞 Bug ✓ Correctness

When initializing sticky-note properties, all notes missing stickyX receive `stickyZIndex: maxZ +
1` within the same effect run, leading to identical z-indexes and incorrect stacking order.
Agent Prompt
## Issue description
Sticky note initialization assigns the same z-index to multiple notes created/initialized in a single effect run.

## Issue Context
This manifests when multiple notes are present without sticky props (e.g., initial migration, importing notes, or creating multiple notes quickly).

## Fix Focus Areas
- src/components/NoteStickyBoard.tsx[235-252]

## Suggested change
- Use a local counter:
  - `let z = maxZ;`
  - for each init: `z += 1; stickyZIndex: z`
  - after loop: `setMaxZ(z)`
- Alternatively store `maxZ` in a ref and increment synchronously.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown

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

Caution

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

⚠️ Outside diff range comments (2)
src/services/syncService.ts (1)

188-197: ⚠️ Potential issue | 🟠 Major

Clear in-memory caches when the user changes.
_feedsCache and _pushFeedPromises persist across sessions; after sign-out they can upsert prior-user feeds into the next account. Clear them when userId changes or becomes null.

🔒 Suggested fix
  setUserId(userId: string | null) {
     console.log('[sync] setUserId:', userId);
-    _currentUserId = userId;
-    if (!userId) {
-      _pendingItemUpdates.clear();
-      if (_debounceTimer) clearTimeout(_debounceTimer);
-    }
+    const prevUserId = _currentUserId;
+    _currentUserId = userId;
+    if (!userId || prevUserId !== userId) {
+      _pendingItemUpdates.clear();
+      if (_debounceTimer) clearTimeout(_debounceTimer);
+      _feedsCache.clear();
+      _pushFeedPromises.clear();
+    }
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/syncService.ts` around lines 188 - 197, When setting a new user
in SyncService.setUserId, clear in-memory caches when the user changes or
becomes null to avoid leaking prior-user data: compare existing _currentUserId
to the incoming userId and if different (or if userId is null) call
_feedsCache.clear() and _pushFeedPromises.clear(); keep the existing behavior of
clearing _pendingItemUpdates and cancelling _debounceTimer when userId is falsy.
Ensure you reference SyncService.setUserId, _currentUserId, _feedsCache,
_pushFeedPromises, _pendingItemUpdates, and _debounceTimer when making the
change.
src/components/SourcePanel.tsx (1)

353-377: ⚠️ Potential issue | 🟡 Minor

Reconcile pinned entries on feed/folder rename or delete.

Pins are keyed by feedId/folderPath and persisted. When a feed or folder is renamed/deleted, those pins remain stale and can surface dead items in TitleBar. Update or remove affected pins during rename/delete operations.

🔧 Example fix (remove pins on delete; extend for rename)
+  const removePinned = useCallback((predicate: (p: PinEntry) => boolean) => {
+    setPinnedItems(prev => {
+      const next = prev.filter(p => !predicate(p));
+      savePinnedItems(next);
+      onPinsChange?.(next);
+      return next;
+    });
+  }, [onPinsChange]);
             onClick={() => {
+              removePinned(p => p.kind === 'folder' && p.categoryId === contextMenu.categoryId && p.folderPath === contextMenu.folderPath);
               onDeleteFolder(contextMenu.categoryId, contextMenu.folderPath);
               setContextMenu(null);
             }}
             onClick={() => { 
+              removePinned(p => p.kind === 'feed' && p.feedId === contextMenu.feed.id);
               onRemoveFeed(contextMenu.feed.id); 
               setContextMenu(null); 
             }}

Consider extending the same idea to rename flows to update pin labels/paths.

Also applies to: 465-485, 915-919, 961-963

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

In `@src/components/SourcePanel.tsx` around lines 353 - 377, Pins are stored keyed
by feedId/folderPath and are not updated when feeds/folders are renamed or
deleted, causing stale entries in TitleBar; add reconciliation logic that on
feed delete (where onDeleteFeed is called) removes any pins matching that
feedId, and on feed rename (where onRenameFeed is called) updates any pins'
labels/paths to the new name/path (or removes them if path semantics change);
factor this into small helpers (e.g., reconcilePinsByFeedId and
reconcilePinsByFolderPath) that read the persisted pin store, filter/update
entries, persist the result, and invoke these helpers from the feed/folder
rename and delete handlers (also apply the same calls in the folder
rename/delete flows referenced around the other locations).
🧹 Nitpick comments (2)
supabase/migrations/20250224000004_add_podcast_source.sql (1)

2-3: Consider minimizing lock/scan when re-adding the CHECK constraint.

On large feeds tables, re-adding a CHECK constraint can scan the table and hold stronger locks. Consider NOT VALID + VALIDATE CONSTRAINT to reduce lock duration.

♻️ Suggested migration tweak
 alter table feeds drop constraint feeds_source_check;
-alter table feeds add constraint feeds_source_check check (source in ('article','reddit','youtube','twitter','mastodon','podcast'));
+alter table feeds add constraint feeds_source_check check (source in ('article','reddit','youtube','twitter','mastodon','podcast')) not valid;
+alter table feeds validate constraint feeds_source_check;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/migrations/20250224000004_add_podcast_source.sql` around lines 2 -
3, When re-adding the feeds_source_check constraint on table feeds, avoid a full
table scan/long lock by creating the check as NOT VALID and then validating it
separately; specifically, replace the direct "add constraint feeds_source_check
check (source in (...))" with adding the same CHECK as NOT VALID and then run
"VALIDATE CONSTRAINT feeds_source_check" once low-lock conditions are acceptable
so the initial ADD CONSTRAINT is fast and the expensive validation is performed
separately.
src/components/NoteSourceList.tsx (1)

109-183: Pre-group notes by folder to avoid repeated filtering.

notes.filter(...) runs once for root notes and again for every folder, which scales poorly as notes grow. Consider grouping notes in a useMemo to keep renders predictable.

♻️ Suggested refactor
-  const rootNotes = notes.filter(n => !n.folder);
+  const notesByFolder = useMemo(() => {
+    const map = new Map<string, Note[]>();
+    for (const n of notes) {
+      const key = n.folder ?? '__root__';
+      const list = map.get(key) ?? [];
+      list.push(n);
+      map.set(key, list);
+    }
+    return map;
+  }, [notes]);
+
+  const rootNotes = notesByFolder.get('__root__') ?? [];
-        const folderNotes = notes.filter(n => n.folder === folder);
+        const folderNotes = notesByFolder.get(folder) ?? [];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/NoteSourceList.tsx` around lines 109 - 183, The component
repeatedly calls notes.filter (once for rootNotes and again inside folders to
build folderNotes) causing O(n*m) work; refactor by computing a grouped map of
notes by folder using React.useMemo (e.g., const notesByFolder = useMemo(() => {
... }, [notes])) and replace rootNotes and the per-folder notes.filter with
lookups like notesByFolder[''] or notesByFolder[folder]; update references in
renderNoteItem, where folderNotes is used, and keep expandedFolders logic
unchanged so rendering uses the memoized group instead of repeated filtering.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
@.specstory/history/2026-02-23_16-43Z-produire-une-description-de-pull-request-pour-la-branche-git.md:
- Around line 9-13: Corrigez les fautes et améliorez la formulation du texte
affiché (repérez la ligne commençant par "Produire une description de Pull
Request pour la branche Git actuelle...") : appliquez les accents corrects (ex.
"clôtures"), remplacez formulations maladroites par des équivalents naturels en
français (par ex. "branche Git courante" ou "conformément aux conventions GitHub
de nommage, syntaxe et formatage"), enlevez éléments superflus/markers inutiles
(ex. underscores autour de "Agent") et assurez une phrase concise qui conserve
l'instruction "Ne pas inclure de captures d'écrans et ne pas indiquer de
clôtures d'issue GitHub."

In `@src-tauri/capabilities/default.json`:
- Around line 5-10: Remove "auth" from the default capability set so the auth
webview does not inherit broad permissions; update the "windows" array that
currently contains ["main", "auth"] to only include "main" and create a new
minimal capability file (or capability entry) for the auth window that omits
"core:default" and other sensitive permissions (keep only explicitly required
permissions for OAuth webviews such as safe webview-specific entries). Ensure
references to the auth window everywhere (the "auth" window name) point to the
new minimal capability instead of the default.

In `@src/App.tsx`:
- Around line 68-78: The handler sets a timeout each time without clearing prior
timers, causing older timers to clear newer messages; change the effect to track
the timeout ID (e.g., let timeoutId: ReturnType<typeof setTimeout> | null) in
the closure, call clearTimeout(timeoutId) before creating a new setTimeout in
the handler, assign the new ID to timeoutId, and also clearTimeout(timeoutId) in
the cleanup function returned by useEffect; update references to
syncError/setSyncError and SYNC_ERROR_EVENT only within the existing handler and
effect.
- Around line 28-38: The getSyncInterval function currently returns Number(v)
unchecked, which can be NaN or <=0; update getSyncInterval to validate the
parsed value from localStorage (using Number or parseInt) ensuring it's a finite
positive number and enforce a sensible minimum (e.g., at least
DEFAULT_SYNC_INTERVAL or another MIN_SYNC_INTERVAL constant) before returning
it; if validation fails, fall back to DEFAULT_SYNC_INTERVAL. Reference:
SYNC_INTERVAL_KEY, DEFAULT_SYNC_INTERVAL, and getSyncInterval.

In `@src/components/NoteSourceList.tsx`:
- Around line 111-121: renderNoteItem uses plain <div>s for interactive rows
which aren't keyboard-focusable; update the note and folder row renderers (e.g.,
renderNoteItem and the folder row renderer handling
handleNoteContext/onSelectNote) to add role="button" and tabIndex={0}, and
implement an onKeyDown handler that triggers onSelectNote(note.id) when Enter or
Space is pressed and triggers handleNoteContext when the context-menu key or
Shift+F10 is detected; ensure the same changes are applied to the folder rows
referenced around lines 192-222 so keyboard-only users can focus and activate
rows.
- Around line 39-75: expandedFolders is initialized from folders but not kept in
sync; add a useEffect(() => { setExpandedFolders(prev => { const next = new
Set(prev); // remove any keys not present in folders for (const k of
Array.from(next)) if (!folders.includes(k)) next.delete(k); return next; }); },
[folders]) to reconcile state when the folders prop changes, and update
handleRename to migrate the key by calling setExpandedFolders(prev => { const
next = new Set(prev); if (renamingFolder) { next.delete(renamingFolder);
next.add(name); } return next; }) after a successful onRenameFolder call (use
the same name variable in handleRename), so renamed folders remain expanded;
keep handleCreateFolder logic (it already adds new folder) and rely on the
reconcile effect to remove entries when folders are deleted.

In `@src/components/NoteStickyBoard.tsx`:
- Around line 235-252: When initializing missing sticky props in the useEffect
that iterates over notes, multiple notes are being assigned the same
stickyZIndex via stickyZIndex: maxZ + 1; fix this by tracking a local z counter
(start at maxZ) and for each note assign stickyZIndex: localZ + 1 then increment
localZ, call onUpdateNote for each note as before, and after the loop call
setMaxZ(localZ) once; update references in the useEffect body that uses
boardRef, notes, onUpdateNote, randomRotation, selectedColor and maxZ
accordingly.

In `@src/components/SettingsModal.tsx`:
- Around line 166-172: The current useState initializer for syncIntervalMs reads
localStorage.getItem(SYNC_INTERVAL_KEY) and returns Number(v) without
validating, which allows NaN to propagate; update the initializer in
SettingsModal (the function that defines syncIntervalMs and setSyncIntervalMs)
to parse the stored string, check Number.isFinite(parsed) or
!Number.isNaN(parsed), and only return the parsed numeric value when valid,
otherwise return DEFAULT_SYNC_INTERVAL; ensure any catch still falls back to
DEFAULT_SYNC_INTERVAL so the <select> always matches an option.

In `@src/components/SourcePanel.tsx`:
- Around line 127-129: Wrap the localStorage write in savePinnedItems so
failures (private mode/quota) don't throw: in function savePinnedItems(pins:
PinEntry[]) catch exceptions from localStorage.setItem(PINS_KEY, ...) and handle
them gracefully (e.g., swallow or log via console.warn/processLogger) so
toggling pins continues to work; ensure you still stringify the pins and do not
change the function's signature.

In `@src/contexts/AuthContext.tsx`:
- Around line 68-93: The auth callback listener currently uses
listen('auth-callback', ...) which can register multiple handlers across
retries; replace listen with once('auth-callback', ...) so the handler
auto-unregisters after the first invocation, remove the manual unlisten() call
(or adjust it to the once return value if your API differs), and keep the
existing logic that closes the WebviewWindow and exchanges the code with
supabase.auth.exchangeCodeForSession; target the listener creation in
AuthContext (the listen call for 'auth-callback') and ensure error handling and
state update (setState with data.session) remain unchanged.

---

Outside diff comments:
In `@src/components/SourcePanel.tsx`:
- Around line 353-377: Pins are stored keyed by feedId/folderPath and are not
updated when feeds/folders are renamed or deleted, causing stale entries in
TitleBar; add reconciliation logic that on feed delete (where onDeleteFeed is
called) removes any pins matching that feedId, and on feed rename (where
onRenameFeed is called) updates any pins' labels/paths to the new name/path (or
removes them if path semantics change); factor this into small helpers (e.g.,
reconcilePinsByFeedId and reconcilePinsByFolderPath) that read the persisted pin
store, filter/update entries, persist the result, and invoke these helpers from
the feed/folder rename and delete handlers (also apply the same calls in the
folder rename/delete flows referenced around the other locations).

In `@src/services/syncService.ts`:
- Around line 188-197: When setting a new user in SyncService.setUserId, clear
in-memory caches when the user changes or becomes null to avoid leaking
prior-user data: compare existing _currentUserId to the incoming userId and if
different (or if userId is null) call _feedsCache.clear() and
_pushFeedPromises.clear(); keep the existing behavior of clearing
_pendingItemUpdates and cancelling _debounceTimer when userId is falsy. Ensure
you reference SyncService.setUserId, _currentUserId, _feedsCache,
_pushFeedPromises, _pendingItemUpdates, and _debounceTimer when making the
change.

---

Nitpick comments:
In `@src/components/NoteSourceList.tsx`:
- Around line 109-183: The component repeatedly calls notes.filter (once for
rootNotes and again inside folders to build folderNotes) causing O(n*m) work;
refactor by computing a grouped map of notes by folder using React.useMemo
(e.g., const notesByFolder = useMemo(() => { ... }, [notes])) and replace
rootNotes and the per-folder notes.filter with lookups like notesByFolder[''] or
notesByFolder[folder]; update references in renderNoteItem, where folderNotes is
used, and keep expandedFolders logic unchanged so rendering uses the memoized
group instead of repeated filtering.

In `@supabase/migrations/20250224000004_add_podcast_source.sql`:
- Around line 2-3: When re-adding the feeds_source_check constraint on table
feeds, avoid a full table scan/long lock by creating the check as NOT VALID and
then validating it separately; specifically, replace the direct "add constraint
feeds_source_check check (source in (...))" with adding the same CHECK as NOT
VALID and then run "VALIDATE CONSTRAINT feeds_source_check" once low-lock
conditions are acceptable so the initial ADD CONSTRAINT is fast and the
expensive validation is performed separately.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31b683b and 0f0a0de.

⛔ Files ignored due to path filters (2)
  • src-tauri/Cargo.lock is excluded by !**/*.lock
  • src-tauri/gen/schemas/capabilities.json is excluded by !**/gen/**
📒 Files selected for processing (21)
  • .specstory/history/2026-02-23_16-43Z-produire-une-description-de-pull-request-pour-la-branche-git.md
  • index.html
  • src-tauri/Cargo.toml
  • src-tauri/capabilities/default.json
  • src-tauri/src/lib.rs
  • src-tauri/tauri.conf.json
  • src/App.tsx
  • src/components/FeedPanel.tsx
  • src/components/NoteEditor.tsx
  • src/components/NotePanel.tsx
  • src/components/NoteSourceList.tsx
  • src/components/NoteStickyBoard.tsx
  • src/components/SettingsModal.tsx
  • src/components/SourcePanel.tsx
  • src/components/TitleBar.tsx
  • src/contexts/AuthContext.tsx
  • src/hooks/useFeedStore.ts
  • src/index.css
  • src/lib/supabase.ts
  • src/services/syncService.ts
  • supabase/migrations/20250224000004_add_podcast_source.sql

Comment on lines +9 to +13
Produire une description de Pull Request pour la branche Git actuelle en analysant les derniers commit effectués sur celle-ci et en respectant les standards GitHub de nommage, syntaxe et formatage. Ne pas inclure de captures d'écrans et ne pas indiquer de clotures d'issue GitHub.

---

_**Agent (model default, mode Agent)**_
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix minor French typos and phrasing.

These are small but user-visible text issues.

✍️ Suggested edits
-Produire une description de Pull Request pour la branche Git actuelle en analysant les derniers commit effectués sur celle-ci et en respectant les standards GitHub de nommage, syntaxe et formatage. Ne pas inclure de captures d'écrans et ne pas indiquer de clotures d'issue GitHub.
+Produire une description de Pull Request pour la branche Git actuelle en analysant les derniers commits effectués sur celle-ci et en respectant les standards GitHub de nommage, syntaxe et formatage. Ne pas inclure de captures d'écrans et ne pas indiquer de clôtures d'issue GitHub.

-_**Agent (model default, mode Agent)**_
+_**Agent (modèle par défaut, mode Agent)**_
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Produire une description de Pull Request pour la branche Git actuelle en analysant les derniers commit effectués sur celle-ci et en respectant les standards GitHub de nommage, syntaxe et formatage. Ne pas inclure de captures d'écrans et ne pas indiquer de clotures d'issue GitHub.
---
_**Agent (model default, mode Agent)**_
Produire une description de Pull Request pour la branche Git actuelle en analysant les derniers commits effectués sur celle-ci et en respectant les standards GitHub de nommage, syntaxe et formatage. Ne pas inclure de captures d'écrans et ne pas indiquer de clôtures d'issue GitHub.
---
_**Agent (modèle par défaut, mode Agent)**_
🧰 Tools
🪛 LanguageTool

[grammar] ~9-~9: Le mot «  commits  » est plus probable.
Context: ... Git actuelle en analysant les derniers commit effectués sur celle-ci et en respectant...

(QB_NEW_FR_OTHER_ERROR_IDS_REPLACEMENT_CONFUSION_COMMIT_COMMITS)


[grammar] ~9-~9: Utilisez les accents correctement
Context: ...captures d'écrans et ne pas indiquer de clotures d'issue GitHub. --- _**Agent (model d...

(QB_NEW_FR_OTHER_ERROR_IDS_MISSING_ORTHOGRAPHY_DIACRITIC_CIRCUMFLEX)


[grammar] ~13-~13: Essayez «  modèle par défaut  »
Context: ...lotures d'issue GitHub. --- Agent (model default, mode Agent) ---

(QB_NEW_FR_OTHER_ERROR_IDS_REPLACEMENT_MULTITOKEN)


[grammar] ~13-~13: Essayez «  modèle par défaut  »
Context: ...s d'issue GitHub. --- Agent (model default, mode Agent) ---

(QB_NEW_FR_OTHER_ERROR_IDS_REPLACEMENT_MULTITOKEN)

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

In
@.specstory/history/2026-02-23_16-43Z-produire-une-description-de-pull-request-pour-la-branche-git.md
around lines 9 - 13, Corrigez les fautes et améliorez la formulation du texte
affiché (repérez la ligne commençant par "Produire une description de Pull
Request pour la branche Git actuelle...") : appliquez les accents corrects (ex.
"clôtures"), remplacez formulations maladroites par des équivalents naturels en
français (par ex. "branche Git courante" ou "conformément aux conventions GitHub
de nommage, syntaxe et formatage"), enlevez éléments superflus/markers inutiles
(ex. underscores autour de "Agent") et assurez une phrase concise qui conserve
l'instruction "Ne pas inclure de captures d'écrans et ne pas indiquer de
clôtures d'issue GitHub."

Comment on lines +5 to 10
"windows": ["main", "auth"],
"permissions": [
"core:default",
"core:event:default",
"core:webview:allow-create-webview-window",
"core:window:allow-minimize",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid granting default capabilities to the auth window.

If the auth webview loads remote OAuth pages, inheriting core:default (and other window permissions) exposes sensitive APIs to untrusted content. Move auth into a minimal, dedicated capability file instead of the default set.

🔒 Suggested fix (remove auth from default capability)
-  "windows": ["main", "auth"],
+  "windows": ["main"],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"windows": ["main", "auth"],
"permissions": [
"core:default",
"core:event:default",
"core:webview:allow-create-webview-window",
"core:window:allow-minimize",
"windows": ["main"],
"permissions": [
"core:default",
"core:event:default",
"core:webview:allow-create-webview-window",
"core:window:allow-minimize",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src-tauri/capabilities/default.json` around lines 5 - 10, Remove "auth" from
the default capability set so the auth webview does not inherit broad
permissions; update the "windows" array that currently contains ["main", "auth"]
to only include "main" and create a new minimal capability file (or capability
entry) for the auth window that omits "core:default" and other sensitive
permissions (keep only explicitly required permissions for OAuth webviews such
as safe webview-specific entries). Ensure references to the auth window
everywhere (the "auth" window name) point to the new minimal capability instead
of the default.

Comment on lines +28 to +38
const SYNC_INTERVAL_KEY = 'superflux_sync_interval';
const DEFAULT_SYNC_INTERVAL = 5 * 60 * 1000; // 5 minutes
const SHOW_SYSINFO_KEY = 'superflux_show_sysinfo';

function getSyncInterval(): number {
try {
const v = localStorage.getItem(SYNC_INTERVAL_KEY);
if (v) return Number(v);
} catch { /* ignore */ }
return DEFAULT_SYNC_INTERVAL;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate the sync interval before scheduling fullSync.

Number(v) can yield NaN or 0, which will cause setInterval to hammer fullSync. Add finite/positive checks (and a minimum) before accepting the stored value.

🛡️ Suggested fix
 const SYNC_INTERVAL_KEY = 'superflux_sync_interval';
 const DEFAULT_SYNC_INTERVAL = 5 * 60 * 1000; // 5 minutes
+const MIN_SYNC_INTERVAL = 30 * 1000; // 30 seconds
 
 function getSyncInterval(): number {
   try {
     const v = localStorage.getItem(SYNC_INTERVAL_KEY);
-    if (v) return Number(v);
+    if (v != null) {
+      const n = Number(v);
+      if (Number.isFinite(n) && n >= MIN_SYNC_INTERVAL) return n;
+    }
   } catch { /* ignore */ }
   return DEFAULT_SYNC_INTERVAL;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/App.tsx` around lines 28 - 38, The getSyncInterval function currently
returns Number(v) unchecked, which can be NaN or <=0; update getSyncInterval to
validate the parsed value from localStorage (using Number or parseInt) ensuring
it's a finite positive number and enforce a sensible minimum (e.g., at least
DEFAULT_SYNC_INTERVAL or another MIN_SYNC_INTERVAL constant) before returning
it; if validation fails, fall back to DEFAULT_SYNC_INTERVAL. Reference:
SYNC_INTERVAL_KEY, DEFAULT_SYNC_INTERVAL, and getSyncInterval.

Comment on lines +68 to +78
// Surface sync errors as a visible toast
const [syncError, setSyncError] = useState<string | null>(null);
useEffect(() => {
const handler = (e: Event) => {
const detail = (e as CustomEvent).detail as { operation: string; message: string };
setSyncError(`Sync: ${detail.operation} — ${detail.message}`);
setTimeout(() => setSyncError(null), 8000);
};
window.addEventListener(SYNC_ERROR_EVENT, handler);
return () => window.removeEventListener(SYNC_ERROR_EVENT, handler);
}, []);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clear sync-error timeouts to avoid race conditions.

Each error sets a timeout without clearing previous ones, so older timers can hide newer messages. Store the timer ID and clear it on each event and on unmount.

🧹 Suggested fix
+  const syncErrorTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
   const [syncError, setSyncError] = useState<string | null>(null);
   useEffect(() => {
     const handler = (e: Event) => {
       const detail = (e as CustomEvent).detail as { operation: string; message: string };
       setSyncError(`Sync: ${detail.operation} — ${detail.message}`);
-      setTimeout(() => setSyncError(null), 8000);
+      if (syncErrorTimeoutRef.current) clearTimeout(syncErrorTimeoutRef.current);
+      syncErrorTimeoutRef.current = setTimeout(() => setSyncError(null), 8000);
     };
     window.addEventListener(SYNC_ERROR_EVENT, handler);
-    return () => window.removeEventListener(SYNC_ERROR_EVENT, handler);
+    return () => {
+      if (syncErrorTimeoutRef.current) clearTimeout(syncErrorTimeoutRef.current);
+      window.removeEventListener(SYNC_ERROR_EVENT, handler);
+    };
   }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Surface sync errors as a visible toast
const [syncError, setSyncError] = useState<string | null>(null);
useEffect(() => {
const handler = (e: Event) => {
const detail = (e as CustomEvent).detail as { operation: string; message: string };
setSyncError(`Sync: ${detail.operation}${detail.message}`);
setTimeout(() => setSyncError(null), 8000);
};
window.addEventListener(SYNC_ERROR_EVENT, handler);
return () => window.removeEventListener(SYNC_ERROR_EVENT, handler);
}, []);
// Surface sync errors as a visible toast
const syncErrorTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const [syncError, setSyncError] = useState<string | null>(null);
useEffect(() => {
const handler = (e: Event) => {
const detail = (e as CustomEvent).detail as { operation: string; message: string };
setSyncError(`Sync: ${detail.operation}${detail.message}`);
if (syncErrorTimeoutRef.current) clearTimeout(syncErrorTimeoutRef.current);
syncErrorTimeoutRef.current = setTimeout(() => setSyncError(null), 8000);
};
window.addEventListener(SYNC_ERROR_EVENT, handler);
return () => {
if (syncErrorTimeoutRef.current) clearTimeout(syncErrorTimeoutRef.current);
window.removeEventListener(SYNC_ERROR_EVENT, handler);
};
}, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/App.tsx` around lines 68 - 78, The handler sets a timeout each time
without clearing prior timers, causing older timers to clear newer messages;
change the effect to track the timeout ID (e.g., let timeoutId:
ReturnType<typeof setTimeout> | null) in the closure, call
clearTimeout(timeoutId) before creating a new setTimeout in the handler, assign
the new ID to timeoutId, and also clearTimeout(timeoutId) in the cleanup
function returned by useEffect; update references to syncError/setSyncError and
SYNC_ERROR_EVENT only within the existing handler and effect.

Comment on lines +39 to +75
const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set(folders));
const [newFolderInput, setNewFolderInput] = useState(false);
const [newFolderName, setNewFolderName] = useState('');
const [renamingFolder, setRenamingFolder] = useState<string | null>(null);
const [renameValue, setRenameValue] = useState('');
const [contextMenu, setContextMenu] = useState<ContextMenuState>(null);
const [moveSubmenuOpen, setMoveSubmenuOpen] = useState(false);
const newFolderRef = useRef<HTMLInputElement>(null);
const renameRef = useRef<HTMLInputElement>(null);

const toggleFolder = useCallback((folder: string) => {
setExpandedFolders(prev => {
const next = new Set(prev);
if (next.has(folder)) next.delete(folder);
else next.add(folder);
return next;
});
}, []);

const handleCreateFolder = useCallback(() => {
const name = newFolderName.trim();
if (name && !folders.includes(name)) {
onCreateFolder(name);
setExpandedFolders(prev => new Set(prev).add(name));
}
setNewFolderName('');
setNewFolderInput(false);
}, [newFolderName, folders, onCreateFolder]);

const handleRename = useCallback(() => {
const name = renameValue.trim();
if (name && renamingFolder && name !== renamingFolder && !folders.includes(name)) {
onRenameFolder(renamingFolder, name);
}
setRenamingFolder(null);
setRenameValue('');
}, [renameValue, renamingFolder, folders, onRenameFolder]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Keep expanded folder state in sync after rename/remove.

expandedFolders is initialized from folders but never reconciled when folders changes, and renaming doesn’t migrate the expanded key. This leaves stale entries and collapses renamed folders unexpectedly.

🔧 Suggested fix (reconcile + rename migration)
 const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set(folders));
+
+useEffect(() => {
+  setExpandedFolders(prev => new Set([...prev].filter(f => folders.includes(f))));
+}, [folders]);
   const handleRename = useCallback(() => {
     const name = renameValue.trim();
     if (name && renamingFolder && name !== renamingFolder && !folders.includes(name)) {
       onRenameFolder(renamingFolder, name);
+      setExpandedFolders(prev => {
+        if (!prev.has(renamingFolder)) return prev;
+        const next = new Set(prev);
+        next.delete(renamingFolder);
+        next.add(name);
+        return next;
+      });
     }
     setRenamingFolder(null);
     setRenameValue('');
   }, [renameValue, renamingFolder, folders, onRenameFolder]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set(folders));
const [newFolderInput, setNewFolderInput] = useState(false);
const [newFolderName, setNewFolderName] = useState('');
const [renamingFolder, setRenamingFolder] = useState<string | null>(null);
const [renameValue, setRenameValue] = useState('');
const [contextMenu, setContextMenu] = useState<ContextMenuState>(null);
const [moveSubmenuOpen, setMoveSubmenuOpen] = useState(false);
const newFolderRef = useRef<HTMLInputElement>(null);
const renameRef = useRef<HTMLInputElement>(null);
const toggleFolder = useCallback((folder: string) => {
setExpandedFolders(prev => {
const next = new Set(prev);
if (next.has(folder)) next.delete(folder);
else next.add(folder);
return next;
});
}, []);
const handleCreateFolder = useCallback(() => {
const name = newFolderName.trim();
if (name && !folders.includes(name)) {
onCreateFolder(name);
setExpandedFolders(prev => new Set(prev).add(name));
}
setNewFolderName('');
setNewFolderInput(false);
}, [newFolderName, folders, onCreateFolder]);
const handleRename = useCallback(() => {
const name = renameValue.trim();
if (name && renamingFolder && name !== renamingFolder && !folders.includes(name)) {
onRenameFolder(renamingFolder, name);
}
setRenamingFolder(null);
setRenameValue('');
}, [renameValue, renamingFolder, folders, onRenameFolder]);
const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set(folders));
const [newFolderInput, setNewFolderInput] = useState(false);
const [newFolderName, setNewFolderName] = useState('');
const [renamingFolder, setRenamingFolder] = useState<string | null>(null);
const [renameValue, setRenameValue] = useState('');
const [contextMenu, setContextMenu] = useState<ContextMenuState>(null);
const [moveSubmenuOpen, setMoveSubmenuOpen] = useState(false);
const newFolderRef = useRef<HTMLInputElement>(null);
const renameRef = useRef<HTMLInputElement>(null);
useEffect(() => {
setExpandedFolders(prev => new Set([...prev].filter(f => folders.includes(f))));
}, [folders]);
const toggleFolder = useCallback((folder: string) => {
setExpandedFolders(prev => {
const next = new Set(prev);
if (next.has(folder)) next.delete(folder);
else next.add(folder);
return next;
});
}, []);
const handleCreateFolder = useCallback(() => {
const name = newFolderName.trim();
if (name && !folders.includes(name)) {
onCreateFolder(name);
setExpandedFolders(prev => new Set(prev).add(name));
}
setNewFolderName('');
setNewFolderInput(false);
}, [newFolderName, folders, onCreateFolder]);
const handleRename = useCallback(() => {
const name = renameValue.trim();
if (name && renamingFolder && name !== renamingFolder && !folders.includes(name)) {
onRenameFolder(renamingFolder, name);
setExpandedFolders(prev => {
if (!prev.has(renamingFolder)) return prev;
const next = new Set(prev);
next.delete(renamingFolder);
next.add(name);
return next;
});
}
setRenamingFolder(null);
setRenameValue('');
}, [renameValue, renamingFolder, folders, onRenameFolder]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/NoteSourceList.tsx` around lines 39 - 75, expandedFolders is
initialized from folders but not kept in sync; add a useEffect(() => {
setExpandedFolders(prev => { const next = new Set(prev); // remove any keys not
present in folders for (const k of Array.from(next)) if (!folders.includes(k))
next.delete(k); return next; }); }, [folders]) to reconcile state when the
folders prop changes, and update handleRename to migrate the key by calling
setExpandedFolders(prev => { const next = new Set(prev); if (renamingFolder) {
next.delete(renamingFolder); next.add(name); } return next; }) after a
successful onRenameFolder call (use the same name variable in handleRename), so
renamed folders remain expanded; keep handleCreateFolder logic (it already adds
new folder) and rely on the reconcile effect to remove entries when folders are
deleted.

Comment on lines +111 to +121
const renderNoteItem = (note: Note) => (
<div
key={note.id}
className={`nsrc-note ${selectedNoteId === note.id ? 'active' : ''}`}
onClick={() => onSelectNote(note.id)}
onContextMenu={(e) => handleNoteContext(e, note.id, note.folder)}
>
<span className="nsrc-note-icon">✎</span>
<span className="nsrc-note-title">{note.title || 'Sans titre'}</span>
</div>
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make note/folder rows keyboard accessible.

These interactive rows are <div> elements without focus/keyboard handling, so keyboard-only users can’t select notes or folders. Add role/tabIndex and handle Enter/Space (or switch to buttons) to avoid an accessibility blocker.

✅ Suggested fix (minimal ARIA/keyboard support)
-  const renderNoteItem = (note: Note) => (
-    <div
+  const renderNoteItem = (note: Note) => (
+    <div
+      role="button"
+      tabIndex={0}
       key={note.id}
       className={`nsrc-note ${selectedNoteId === note.id ? 'active' : ''}`}
       onClick={() => onSelectNote(note.id)}
       onContextMenu={(e) => handleNoteContext(e, note.id, note.folder)}
+      onKeyDown={(e) => {
+        if (e.key === 'Enter' || e.key === ' ') {
+          e.preventDefault();
+          onSelectNote(note.id);
+        }
+      }}
     >
-            <div
+            <div
+              role="button"
+              tabIndex={0}
               className={`nsrc-folder-header ${selectedFolder === folder ? 'active' : ''}`}
               onClick={() => { onSelectFolder(folder); if (!isExpanded) toggleFolder(folder); }}
               onContextMenu={(e) => handleFolderContext(e, folder)}
+              onKeyDown={(e) => {
+                if (e.target !== e.currentTarget) return;
+                if (e.key === 'Enter' || e.key === ' ') {
+                  e.preventDefault();
+                  onSelectFolder(folder);
+                  if (!isExpanded) toggleFolder(folder);
+                }
+              }}
             >

Also applies to: 192-222

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

In `@src/components/NoteSourceList.tsx` around lines 111 - 121, renderNoteItem
uses plain <div>s for interactive rows which aren't keyboard-focusable; update
the note and folder row renderers (e.g., renderNoteItem and the folder row
renderer handling handleNoteContext/onSelectNote) to add role="button" and
tabIndex={0}, and implement an onKeyDown handler that triggers
onSelectNote(note.id) when Enter or Space is pressed and triggers
handleNoteContext when the context-menu key or Shift+F10 is detected; ensure the
same changes are applied to the folder rows referenced around lines 192-222 so
keyboard-only users can focus and activate rows.

Comment on lines +235 to +252
// When a new note appears without sticky props, initialize them
useEffect(() => {
const boardEl = boardRef.current;
const w = boardEl?.clientWidth || 600;
const h = boardEl?.clientHeight || 400;
for (const note of notes) {
if (note.stickyX === undefined) {
onUpdateNote(note.id, {
stickyX: Math.random() * Math.max(100, w - 260) + 30,
stickyY: Math.random() * Math.max(100, h - 260) + 30,
stickyRotation: randomRotation(),
stickyZIndex: maxZ + 1,
stickyColor: selectedColor,
});
setMaxZ(z => z + 1);
}
}
}, [notes.length]); // eslint-disable-line react-hooks/exhaustive-deps
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Ensure unique z-index when initializing multiple notes.
If several notes lack sticky props (e.g., first run/migration), they all get stickyZIndex: maxZ + 1, so stacking collides. Increment a local counter and update setMaxZ once.

🛠️ Suggested fix
  useEffect(() => {
    const boardEl = boardRef.current;
    const w = boardEl?.clientWidth || 600;
    const h = boardEl?.clientHeight || 400;
+    let nextZ = maxZ;
    for (const note of notes) {
      if (note.stickyX === undefined) {
+        nextZ += 1;
        onUpdateNote(note.id, {
          stickyX: Math.random() * Math.max(100, w - 260) + 30,
          stickyY: Math.random() * Math.max(100, h - 260) + 30,
          stickyRotation: randomRotation(),
-          stickyZIndex: maxZ + 1,
+          stickyZIndex: nextZ,
          stickyColor: selectedColor,
        });
-        setMaxZ(z => z + 1);
      }
    }
+    if (nextZ !== maxZ) setMaxZ(nextZ);
  }, [notes.length]); // eslint-disable-line react-hooks/exhaustive-deps
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// When a new note appears without sticky props, initialize them
useEffect(() => {
const boardEl = boardRef.current;
const w = boardEl?.clientWidth || 600;
const h = boardEl?.clientHeight || 400;
for (const note of notes) {
if (note.stickyX === undefined) {
onUpdateNote(note.id, {
stickyX: Math.random() * Math.max(100, w - 260) + 30,
stickyY: Math.random() * Math.max(100, h - 260) + 30,
stickyRotation: randomRotation(),
stickyZIndex: maxZ + 1,
stickyColor: selectedColor,
});
setMaxZ(z => z + 1);
}
}
}, [notes.length]); // eslint-disable-line react-hooks/exhaustive-deps
// When a new note appears without sticky props, initialize them
useEffect(() => {
const boardEl = boardRef.current;
const w = boardEl?.clientWidth || 600;
const h = boardEl?.clientHeight || 400;
let nextZ = maxZ;
for (const note of notes) {
if (note.stickyX === undefined) {
nextZ += 1;
onUpdateNote(note.id, {
stickyX: Math.random() * Math.max(100, w - 260) + 30,
stickyY: Math.random() * Math.max(100, h - 260) + 30,
stickyRotation: randomRotation(),
stickyZIndex: nextZ,
stickyColor: selectedColor,
});
}
}
if (nextZ !== maxZ) setMaxZ(nextZ);
}, [notes.length]); // eslint-disable-line react-hooks/exhaustive-deps
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/NoteStickyBoard.tsx` around lines 235 - 252, When initializing
missing sticky props in the useEffect that iterates over notes, multiple notes
are being assigned the same stickyZIndex via stickyZIndex: maxZ + 1; fix this by
tracking a local z counter (start at maxZ) and for each note assign
stickyZIndex: localZ + 1 then increment localZ, call onUpdateNote for each note
as before, and after the loop call setMaxZ(localZ) once; update references in
the useEffect body that uses boardRef, notes, onUpdateNote, randomRotation,
selectedColor and maxZ accordingly.

Comment on lines +166 to +172
const [syncIntervalMs, setSyncIntervalMs] = useState(() => {
try {
const v = localStorage.getItem(SYNC_INTERVAL_KEY);
if (v) return Number(v);
} catch { /* ignore */ }
return DEFAULT_SYNC_INTERVAL;
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against invalid persisted sync intervals.
If localStorage contains a non-numeric value, Number(v) becomes NaN and the <select> won’t match any option. Validate and fall back to the default.

🧰 Suggested fix
  const [syncIntervalMs, setSyncIntervalMs] = useState(() => {
    try {
      const v = localStorage.getItem(SYNC_INTERVAL_KEY);
-      if (v) return Number(v);
+      if (v) {
+        const parsed = Number(v);
+        if (Number.isFinite(parsed) && SYNC_OPTIONS.some(o => o.value === parsed)) {
+          return parsed;
+        }
+      }
    } catch { /* ignore */ }
    return DEFAULT_SYNC_INTERVAL;
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/SettingsModal.tsx` around lines 166 - 172, The current
useState initializer for syncIntervalMs reads
localStorage.getItem(SYNC_INTERVAL_KEY) and returns Number(v) without
validating, which allows NaN to propagate; update the initializer in
SettingsModal (the function that defines syncIntervalMs and setSyncIntervalMs)
to parse the stored string, check Number.isFinite(parsed) or
!Number.isNaN(parsed), and only return the parsed numeric value when valid,
otherwise return DEFAULT_SYNC_INTERVAL; ensure any catch still falls back to
DEFAULT_SYNC_INTERVAL so the <select> always matches an option.

Comment on lines +127 to +129
function savePinnedItems(pins: PinEntry[]) {
localStorage.setItem(PINS_KEY, JSON.stringify(pins));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard localStorage writes for pin persistence.

savePinnedItems writes directly to localStorage and can throw in private mode or quota limits, which would break pin toggling. Wrap it in a try/catch for resilience.

🧯 Suggested fix
 function savePinnedItems(pins: PinEntry[]) {
-  localStorage.setItem(PINS_KEY, JSON.stringify(pins));
+  try {
+    localStorage.setItem(PINS_KEY, JSON.stringify(pins));
+  } catch { /* ignore */ }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function savePinnedItems(pins: PinEntry[]) {
localStorage.setItem(PINS_KEY, JSON.stringify(pins));
}
function savePinnedItems(pins: PinEntry[]) {
try {
localStorage.setItem(PINS_KEY, JSON.stringify(pins));
} catch { /* ignore */ }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/SourcePanel.tsx` around lines 127 - 129, Wrap the localStorage
write in savePinnedItems so failures (private mode/quota) don't throw: in
function savePinnedItems(pins: PinEntry[]) catch exceptions from
localStorage.setItem(PINS_KEY, ...) and handle them gracefully (e.g., swallow or
log via console.warn/processLogger) so toggling pins continues to work; ensure
you still stringify the pins and do not change the function's signature.

Comment on lines +68 to +93
// Listen for the auth callback from the Tauri auth window
const unlisten = await listen<string>('auth-callback', async (event) => {
unlisten();
// Close the auth window
const authWindow = await WebviewWindow.getByLabel('auth');
if (authWindow) await authWindow.close();

// Extract PKCE code from callback URL and establish session
try {
const callbackUrl = new URL(event.payload);
const code = callbackUrl.searchParams.get('code');
if (code) {
const { data, error } = await supabase.auth.exchangeCodeForSession(code);
if (error) throw error;
// Explicitly update auth state so downstream sync triggers immediately
if (data.session) {
setState({ user: data.session.user, session: data.session, loading: false });
}
}
} catch (e) {
console.error('[auth] Failed to exchange code:', e);
}
});

// Open a separate Tauri window for the OAuth flow
await invoke('open_auth_window', { url: data.url });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, examine the AuthContext.tsx file to see the current implementation
cd src/contexts && cat -n AuthContext.tsx | head -100

Repository: devohmycode/SuperFlux

Length of output: 4338


🏁 Script executed:

# Check what's exported from `@tauri-apps/api/event`
grep -r "from '@tauri-apps/api/event'" --include="*.tsx" --include="*.ts" | head -20

Repository: devohmycode/SuperFlux

Length of output: 141


🏁 Script executed:

# Look for the full signInWithOAuth function to understand the context
ast-grep --pattern 'async signInWithOAuth($_) { $$$ }'

Repository: devohmycode/SuperFlux

Length of output: 47


🏁 Script executed:

# Check if there's any listener for window close events
rg "close|error" src/contexts/AuthContext.tsx -B2 -A2

Repository: devohmycode/SuperFlux

Length of output: 1557


🏁 Script executed:

# Look for any auth-related error handling or listeners
rg "listen|unlisten|once" src/contexts/AuthContext.tsx -B1 -A1

Repository: devohmycode/SuperFlux

Length of output: 415


🏁 Script executed:

# Check if signInWithOAuth is being called conditionally or if it can be triggered multiple times
rg "signInWithOAuth" src/ -B3 -A3

Repository: devohmycode/SuperFlux

Length of output: 2464


🌐 Web query:

@tauri-apps/api/event once function documentation

💡 Result:

once (from @tauri-apps/api/event) registers an event listener that will fire exactly one time, then be unregistered automatically.

  • Signature: once<T>(event: EventName, handler: EventCallback<T>, options?): Promise<UnlistenFn> [1]
  • Returns: a Promise that resolves to an unlisten function (you should still call it if the handler may go out of scope, e.g., component unmount) [1]
  • Event name rules: only alphanumeric characters plus -, /, :, _ [1]

Example (TypeScript):

import { once } from '@tauri-apps/api/event';

interface LoadedPayload {
  loggedIn: boolean;
  token: string;
}

const unlisten = await once<LoadedPayload>('loaded', (event) => {
  console.log(event.payload.loggedIn, event.payload.token);
});

// call if the handler could go out of scope (e.g. component unmount)
unlisten();

[1][2]

Docs: Tauri JS event.once reference [1] (older Tauri v1 page for once is here [2]).

Sources: [1] [2]


Replace listen with once to prevent stacked handlers on retry.

Each call to signInWithOAuth registers a new listener without cleaning up previous ones; if the auth window closes before the callback fires or the user retries, multiple listeners can accumulate and trigger duplicate session exchanges. Use once instead to auto-unregister after the first callback.

Suggested fix
-import { listen } from '@tauri-apps/api/event';
+import { once } from '@tauri-apps/api/event';

-    const unlisten = await listen<string>('auth-callback', async (event) => {
-      unlisten();
+    await once<string>('auth-callback', async (event) => {
       // Close the auth window
       const authWindow = await WebviewWindow.getByLabel('auth');
       if (authWindow) await authWindow.close();
       ...
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contexts/AuthContext.tsx` around lines 68 - 93, The auth callback
listener currently uses listen('auth-callback', ...) which can register multiple
handlers across retries; replace listen with once('auth-callback', ...) so the
handler auto-unregisters after the first invocation, remove the manual
unlisten() call (or adjust it to the once return value if your API differs), and
keep the existing logic that closes the WebviewWindow and exchanges the code
with supabase.auth.exchangeCodeForSession; target the listener creation in
AuthContext (the listen call for 'auth-callback') and ensure error handling and
state update (setState with data.session) remain unchanged.

@devohmycode devohmycode merged commit 498d69c into master Feb 24, 2026
1 check 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.

1 participant