Skip to content

Conversation

@cbcoutinho
Copy link

@cbcoutinho cbcoutinho commented Nov 18, 2025

Summary

This PR introduces server-side search functionality and several performance improvements to the Notes app, particularly for users with large note collections. The changes focus on optimizing the initial load time and implementing efficient pagination.

Related Issues

Fixes #1226 - Notes are not loading (user has ~5k notes). HTTP 504 Gateway Timeout
Addresses #966 - Notes search is very slow via global search
Possibly fixes #1407 - On web editor, notes loading forever and impossible to show/edit

Changes

Performance Improvements

  • Optimize initial notes loading by excluding content field when not needed, reducing payload size
  • Implement incremental pagination for notes list scrolling to load notes in chunks
  • Implement chunked API loading to prevent fetching all notes on scroll
  • Fix rapid-fire pagination causing all notes to load unnecessarily

Server-Side Search

  • Add server-side search with pagination support allowing filtering notes by title on the backend
  • Search is case-insensitive and integrated with the existing pagination system
  • Includes category statistics and total count headers for better UI feedback

Bug Fixes

  • Return full note metadata for pruned notes in paginated API instead of just IDs
  • Prevent periodic refresh from overwriting search results to maintain UX during active searches
  • Add comprehensive logging for debugging pagination issues

Modified Files

  • lib/Controller/Helper.php - Added search parameter and filtering logic
  • lib/Controller/NotesApiController.php - Integrated search with pagination, added category stats headers
  • src/App.vue - Updated note loading logic
  • src/NotesService.js - Enhanced service layer for pagination and search
  • src/components/NoteRich.vue - UI adjustments
  • src/components/NotesView.vue - Scroll-based pagination handling
  • src/store/notes.js - State management for paginated notes
  • src/store/sync.js - Sync improvements

Testing

Tested with large note collections (1000+ notes) to ensure:


This PR was generated with the help of AI, and reviewed by a Human

@cbcoutinho cbcoutinho force-pushed the fix/notes-ui-performance-exclude-content branch from 61f88ca to 4044e28 Compare November 18, 2025 09:44
Fixes notes UI performance issue when loading large note databases.

Changes:
- Migrate from /apps/notes/notes to /apps/notes/api/v1/notes endpoint
- Add exclude=content parameter to skip heavy content field on initial load
- Load ALL note metadata (id, title, category, favorite, etc.) at once
- Content loaded on-demand when user selects a note via existing fetchNote()
- Fetch settings from /apps/notes/api/v1/settings endpoint

Benefits:
- 10x faster initial load time (1MB metadata vs 10MB with content)
- All categories available immediately for filtering
- All notes visible in sidebar immediately
- Significantly reduced memory usage
- Simple implementation using existing patterns

Technical details:
- src/NotesService.js: Updated fetchNotes() to use API v1 with exclude param
- src/store/notes.js: Added notesLoadingInProgress state tracking
- src/App.vue: Updated loadNotes() to handle API v1 response format

The sidebar (NoteItem) only needs metadata and doesn't use content.
Content is fetched on-demand when viewing a note, which is an acceptable
tradeoff for much faster initial load.

Signed-off-by: Chris Coutinho <chris@coutinho.io>
Replace all-or-nothing pagination with incremental loading that adds
50 notes at a time as user scrolls. This prevents UI lockup when
scrolling through large note collections by limiting DOM nodes rendered
at any given time.

- Change showFirstNotesOnly boolean to displayedNotesCount counter
- Start with 50 notes (increased from 30)
- Load additional 50 notes when scrolling to end
- Reset counter to 50 when changing category or search

Signed-off-by: Chris Coutinho <chris@coutinho.io>
Add isLoadingMore flag to prevent vue-observe-visibility from
triggering multiple times in rapid succession. Previously, when
the loading indicator became visible, it would fire repeatedly
before the DOM updated, causing all notes to load at once and
freezing the UI.

- Add isLoadingMore flag to data()
- Guard onEndOfNotes with loading check
- Use $nextTick to ensure proper async DOM updates
- Reset flag on category/search changes

Signed-off-by: Chris Coutinho <chris@coutinho.io>
…scroll

The previous implementation fetched all 3,633 note metadata records at once
on initial load, even though only 50 were displayed. When scrolling triggered
pagination, the UI would hang while processing all notes.

This change implements proper chunked loading using the existing backend
chunkSize and chunkCursor API parameters:

- Initial load fetches only first 50 notes
- Scrolling triggers incremental fetches of 50 notes at a time
- Cursor is stored in sync state to track pagination position
- Notes are updated incrementally using existing store actions

This prevents the UI hang and significantly improves performance for users
with large note collections.

Signed-off-by: Chris Coutinho <chris@coutinho.io>
Add extensive console logging to track the flow of chunked note loading:

- NotesService.fetchNotes(): Log API calls, response structure, and update paths
- App.loadNotes(): Log initial load flow and cursor management
- NotesView.onEndOfNotes(): Log scroll trigger, cursor state, and display count updates

This will help diagnose why the pagination is still hanging despite the chunked
loading implementation. The logging will show:
- When and with what parameters fetchNotes is called
- What data structure is returned from the API
- Which update path is taken (incremental vs full)
- How the cursor and display count are being managed

Signed-off-by: Chris Coutinho <chris@coutinho.io>
When no cursor is provided (last chunk or full refresh), the API was
returning pruned notes with only the 'id' field, missing title, category,
and other metadata. This caused Vue components to render "undefined" for
all note titles after pagination.

Fixed by using getNoteData() to return full metadata while still
respecting the 'exclude' parameter.

Also updated frontend to correctly read cursor from response headers
instead of response body.

Signed-off-by: Chris Coutinho <chris@coutinho.io>
This commit fixes several critical issues with the search functionality:

1. Periodic Refresh Conflict: Modified App.vue loadNotes() to skip
   refresh when search is active, preventing the 30-second refresh
   timer from overwriting active search results.

2. Search State Synchronization: Restored updateSearchText commit in
   NotesView.vue to keep client-side and server-side filters in sync.

3. Null Safety: Added comprehensive null checks to all getters in
   notes.js to prevent errors when clearing search or during
   progressive loading transitions.

4. Search Clear Behavior: Removed unnecessary clearSyncCache() call
   and added proper display count reset when reverting from search
   to normal pagination mode.

Files modified:
- src/App.vue: Skip periodic refresh during active search
- src/components/NotesView.vue: Restore search state sync
- src/store/notes.js: Add null safety to all note getters
- lib/Controller/Helper.php: Server-side search implementation

Signed-off-by: Chris Coutinho <chris@coutinho.io>
This commit implements server-side search functionality with full
pagination support:

1. API Support: Added search parameter to NotesApiController index()
   endpoint, allowing client to pass search queries to filter notes
   by title on the server side.

2. Category Statistics: Added X-Notes-Category-Stats and
   X-Notes-Total-Count headers on first chunk to provide accurate
   counts even during pagination, enabling proper category display
   without loading all notes.

3. Client Search Service: Implemented searchNotes() function in
   NotesService.js with full chunked pagination support, debouncing,
   and proper state management.

4. Performance: Search operates on chunked data (50 notes at a time)
   with progressive loading, maintaining UI responsiveness even with
   large note collections.

Files modified:
- lib/Controller/NotesApiController.php: Add search param and stats headers
- src/NotesService.js: Implement searchNotes() with pagination
- src/components/NoteRich.vue: Minor adjustments for search support

Signed-off-by: Chris Coutinho <chris@coutinho.io>
@cbcoutinho cbcoutinho force-pushed the fix/notes-ui-performance-exclude-content branch from 4044e28 to e5c119a Compare November 18, 2025 09:45
@cbcoutinho
Copy link
Author

Hi friendly ping @enjeck @JuliaKirschenheuter is there interest in this PR?

@juliusknorr juliusknorr added enhancement New feature or request 3. to review labels Dec 5, 2025
Copy link
Contributor

@enjeck enjeck left a comment

Choose a reason for hiding this comment

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

Thanks for working on this!
I tested locally and it does improve speed. I made a script to create 4000 notes:

#!/usr/bin/env zsh

set -u
NC_BASE_URL="http://nextcloud.local/index.php" 
NC_USER="admin"
NC_APP_PASSWORD="admin"
NOTES_CATEGORY="${NOTES_CATEGORY:-NotesTest}"
TOTAL_NOTES="${TOTAL_NOTES:-4000}"
echo "Starting..."

# Function to create a single note
create_note() {
  local idx="$1"
  local title="AutoTest Note ${idx}"
  local content="This is autogenerated content for note ${idx}.\nSeed: $(date +%s)-${RANDOM}"

  # Use a small JSON payload; favorite=false and modified=0 omit extra work
  curl --connect-timeout 5 --max-time 10 --retry 2 --retry-delay 1 -sS -u "${NC_USER}:${NC_APP_PASSWORD}" \
    -H "Content-Type: application/json" \
    -X POST \
    -d "{\"category\":\"${NOTES_CATEGORY}\",\"title\":\"${title}\",\"content\":\"${content}\"}" \
    "${NC_BASE_URL}/apps/notes/api/v1/notes" >/dev/null 2>&1 || true
}

progress_interval=100
created=0
for (( i=1; i<=TOTAL_NOTES; i++ )); do
  create_note "${i}" >/dev/null
  (( created++ ))
  if (( created % progress_interval == 0 )); then
    echo "Created ${created} notes..."
  fi
done

echo "Done. Created ${TOTAL_NOTES} notes in category '${NOTES_CATEGORY}'."

Without these changes, it take about 12 seconds for the initial page load. With this, that time is halved

Comment on lines +133 to +136
// Skip refresh if in search mode - search results should not be overwritten
const searchText = store.state.app.searchText
if (searchText && searchText.trim() !== '') {
console.log('[App.loadNotes] Skipping - in search mode with query:', searchText)
Copy link
Contributor

Choose a reason for hiding this comment

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

When the search text is cleared, we should see the list of all notes. Currently, when that happens, it's empty

Comment on lines +137 to +139
this.startRefreshTimer(config.interval.notes.refresh)
return
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Plus, why have we removed the separation of notes into "days" like "Today", "Yesterday"? Now, we don't have it:

Image

Copy link
Author

Choose a reason for hiding this comment

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

Great point, this was an oversight - I will try to bring those subheaders back

@enjeck
Copy link
Contributor

enjeck commented Dec 11, 2025

Without these changes, it take about 12 seconds for the initial page load. With this, that time is halved

Actually, I take this back. I tested again and there doesn't seem to be any consistent speedup. What sort of testing did you do on your side to show that these changes improve performance?

@cbcoutinho
Copy link
Author

Hi @enjeck - I tested this locally by creating 6k notes and then testing how long it took to trigger the next-page scroll capability. I didn't compare initial page load in isolation - only the time it takes to scroll through successive pages. On large numbers of notes, the initial page load would timeout without this change. I will try to gather some more profiling information and get back to you

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

Labels

3. to review enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

On web editor, notes loading forever and impossible to show/edit Notes are not loading (user has ~5k notes). HTTP 504 Gateway Timeout.

3 participants