Feat/pictique loading spinner - #652

Merged
coodos merged 6 commits into
mainfrom
feat/pictique-loading-spinner
Jan 5, 2026
Merged

Feat/pictique loading spinner#652
coodos merged 6 commits into
mainfrom
feat/pictique-loading-spinner

Conversation

@sosweetham

@sosweethamsosweetham commented Jan 5, 2026

Copy link
Copy Markdown
Member

Description of change

Adds a loading spinner at the bottom of loading text

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)

How the change has been tested

Manual

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added infinite scrolling to automatically load more posts as you scroll down the feed
    • Implemented optimistic like updates for instant visual feedback when liking posts
    • Enhanced loading indicators to clearly distinguish between initial feed loading and loading additional content

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Jan 5, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR implements infinite scrolling pagination for the feed using IntersectionObserver-based lazy loading. It introduces new pagination stores (currentPage, hasMore, isLoadingMore), refactors fetchFeed to support append mode for incremental loading, adds loadMoreFeed to fetch subsequent pages, and implements optimistic UI updates for post likes with rollback on error.

Changes

Cohort / File(s)Summary
Store Pagination Logic
platforms/pictique/src/lib/stores/posts.ts
Added observable stores for pagination state (isLoadingMore, currentPage, hasMore); refactored fetchFeed to support both initial and append modes with dynamic totalPages calculation; introduced resetFeed to clear state and loadMoreFeed to trigger next-page fetches; updated createPost to reset and re-fetch feed after creation.
Component Infinite Scrolling
platforms/pictique/src/routes/(protected)/home/+page.svelte
Replaced manual scroll handling with IntersectionObserver-based sentinel pattern; expanded store imports to include pagination controls; reworked onMount to reset and fetch initial page; implemented optimistic like updates with rollback on failure; added UI for loading indicators, sentinel element, and no-more-posts message.

Sequence Diagram(s)

sequenceDiagram
participant User
participant Component as home/+page.svelte
participant Observer as IntersectionObserver
participant Store as posts.ts
participant API as /posts API
Note over User,API: Initial Load
User->>Component: Visit home
Component->>Store: resetFeed()
Component->>Store: fetchFeed(1, 10, false)
Store->>API: GET /posts?page=1&limit=10
API-->>Store: posts[], totalPages
Store->>Component: Update posts, currentPage, hasMore
Note over User,API: Infinite Scroll
User->>Observer: Scroll near sentinel
Observer->>Component: Intersection observed
alt hasMore && !isLoadingMore
Component->>Store: loadMoreFeed()
Store->>API: GET /posts?page=2&limit=10
API-->>Store: posts[]
Store->>Component: Append posts, increment page
Component->>User: Render new posts
else No more data
Component->>User: Show "No more posts"
end
Note over User,API: Optimistic Like
User->>Component: Click like
Component->>Component: Toggle likedBy (optimistic)
Component->>Store: toggleLike(postId)
alt Success
Store->>API: PATCH /posts/:id/like
API-->>Store: Updated post
else Error
Component->>Component: Rollback likedBy
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • fix: pictique lazy scroll #648: Introduces identical pagination and infinite-scroll changes to platforms/pictique with the same new stores (isLoadingMore, currentPage, hasMore), pagination functions, and sentinel-based lazy loading in the home component.

Suggested reviewers

  • coodos
  • ananyayaya129

Poem

🐰 Hop, scroll, and load with infinite grace!
No more pages to chase,
Just sentinel eyes watching the screen,
Fetching posts in between,
Likes roll back if things go wrong—
A feed that bounces all day long! 🌿

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check nameStatusExplanationResolution
Description check⚠️ WarningThe PR description is incomplete and misleading. It states 'Adds a loading spinner at the bottom of loading text' but the changeset implements comprehensive pagination, infinite scrolling with IntersectionObserver, incremental loading, optimistic UI updates, and feed state management—far beyond just a loading spinner.Update the description to accurately reflect all major changes: pagination stores, infinite scroll implementation, optimistic like updates, and feed reset logic instead of describing only a loading spinner.
Title check❓ InconclusiveThe title 'Feat/pictique loading spinner' is partially related to the changeset but doesn't capture the main scope of changes which includes pagination, infinite scrolling, and incremental loading beyond just a loading spinner.Consider revising the title to better reflect the primary changes, such as 'Implement infinite scroll pagination for pictique feed' or 'Add pagination and infinite scroll to pictique home feed'.
✅ Passed checks (1 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
platforms/pictique/src/lib/stores/posts.ts (2)

35-48: Consider edge case in hasMore calculation.

The flexible response parsing on line 36 handles multiple API shapes, which is pragmatic. However, the hasMore condition on line 48 may have an edge case:

hasMore.set(newPosts.length===limit&&responsePage<totalPages);

If totalPages defaults to 0 (when total is 0 or undefined), responsePage < totalPages will be false, which is correct. But if the API returns total: 0 with actual posts (inconsistent response), hasMore will incorrectly be false.

🔎 Consider a more defensive approach
- hasMore.set(newPosts.length === limit && responsePage < totalPages);+ // Only set hasMore to false when we definitively know there's no more data+ const moreAvailable = newPosts.length === limit && (totalPages === 0 || responsePage < totalPages);+ hasMore.set(moreAvailable);

73-90: Redundant mapping on line 79.

The .map((img) => img) creates a shallow copy of the array but doesn't transform elements. If the intent is to create a copy, use spread syntax for clarity.

🔎 Simplify the images array
 const response = await apiClient.post("/api/posts", {
text,
- images: images.map((img) => img),+ images: [...images],
});

Or if a copy isn't needed:

 const response = await apiClient.post("/api/posts", {
text,
- images: images.map((img) => img),+ images,
});
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db6ea53 and 077289f.

📒 Files selected for processing (2)
  • platforms/pictique/src/lib/stores/posts.ts
  • platforms/pictique/src/routes/(protected)/home/+page.svelte
🧰 Additional context used
🧬 Code graph analysis (1)
platforms/pictique/src/lib/stores/posts.ts (1)
platforms/pictique/src/lib/types.ts (1)
  • Post (35-58)
🔇 Additional comments (9)
platforms/pictique/src/routes/(protected)/home/+page.svelte (5)

7-23: LGTM! Clean import organization.

The new imports for pagination stores (hasMore, isLoadingMore, loadMoreFeed, resetFeed) and get from svelte/store are correctly added to support the infinite scroll functionality.


31-56: Well-implemented IntersectionObserver with proper cleanup.

The sentinel action correctly implements infinite scroll detection. Good use of rootMargin: '200px' for pre-fetching before the user reaches the bottom, and proper cleanup in destroy().

Minor consideration: Using get() inside the callback reads the store value synchronously at callback time, which is correct here since the observer callback runs outside the Svelte reactive context.


111-115: LGTM! Clean initialization.

Calling resetFeed() before fetchFeed() ensures a clean state on component mount, which is important when navigating back to this page.


137-207: Solid optimistic update pattern with proper rollback.

The implementation correctly:

  1. Captures state before modification for reliable rollback
  2. Performs optimistic UI update immediately
  3. Calls the API in background
  4. Reverts on error with the saved original state

One minor edge case: if profile becomes null between the guard check (line 138) and the API response, the rollback would still work correctly since originalLikedBy is captured early.


223-236: Nice loading spinner implementation.

The conditional rendering properly handles all states:

  • Loading more indicator with spinner
  • End-of-feed message
  • Sentinel for triggering more loads

The Tailwind classes are valid for v4.

platforms/pictique/src/lib/stores/posts.ts (4)

1-11: LGTM! Clean store declarations.

The new pagination stores (isLoadingMore, currentPage, hasMore) are properly typed with appropriate initial values.


16-20: LGTM! Simple and correct reset function.

Properly resets all pagination state to initial values.


61-71: LGTM! Proper guard against concurrent fetches.

The loadMoreFeed function correctly checks both isLoading and isLoadingMore to prevent duplicate requests, and respects the hasMore flag.


92-101: LGTM! Simple and focused.

The toggleLike function correctly delegates optimistic updates to the caller (the component), keeping this function as a simple API wrapper that throws on failure for the caller to handle rollback.

@coodos
coodos merged commit 07cf29a into mainJan 5, 2026
4 checks passed
@coodos
coodos deleted the feat/pictique-loading-spinner branch January 5, 2026 20:22
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Feat/pictique loading spinner - #652

Merged
coodos merged 6 commits into
mainfrom
feat/pictique-loading-spinner
Jan 5, 2026
Merged

Feat/pictique loading spinner#652
coodos merged 6 commits into
mainfrom
feat/pictique-loading-spinner

Conversation

@sosweetham

@sosweethamsosweetham commented Jan 5, 2026

Copy link
Copy Markdown
Member

Description of change

Adds a loading spinner at the bottom of loading text

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)

How the change has been tested

Manual

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added infinite scrolling to automatically load more posts as you scroll down the feed
    • Implemented optimistic like updates for instant visual feedback when liking posts
    • Enhanced loading indicators to clearly distinguish between initial feed loading and loading additional content

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Jan 5, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR implements infinite scrolling pagination for the feed using IntersectionObserver-based lazy loading. It introduces new pagination stores (currentPage, hasMore, isLoadingMore), refactors fetchFeed to support append mode for incremental loading, adds loadMoreFeed to fetch subsequent pages, and implements optimistic UI updates for post likes with rollback on error.

Changes

Cohort / File(s)Summary
Store Pagination Logic
platforms/pictique/src/lib/stores/posts.ts
Added observable stores for pagination state (isLoadingMore, currentPage, hasMore); refactored fetchFeed to support both initial and append modes with dynamic totalPages calculation; introduced resetFeed to clear state and loadMoreFeed to trigger next-page fetches; updated createPost to reset and re-fetch feed after creation.
Component Infinite Scrolling
platforms/pictique/src/routes/(protected)/home/+page.svelte
Replaced manual scroll handling with IntersectionObserver-based sentinel pattern; expanded store imports to include pagination controls; reworked onMount to reset and fetch initial page; implemented optimistic like updates with rollback on failure; added UI for loading indicators, sentinel element, and no-more-posts message.

Sequence Diagram(s)

sequenceDiagram
participant User
participant Component as home/+page.svelte
participant Observer as IntersectionObserver
participant Store as posts.ts
participant API as /posts API
Note over User,API: Initial Load
User->>Component: Visit home
Component->>Store: resetFeed()
Component->>Store: fetchFeed(1, 10, false)
Store->>API: GET /posts?page=1&limit=10
API-->>Store: posts[], totalPages
Store->>Component: Update posts, currentPage, hasMore
Note over User,API: Infinite Scroll
User->>Observer: Scroll near sentinel
Observer->>Component: Intersection observed
alt hasMore && !isLoadingMore
Component->>Store: loadMoreFeed()
Store->>API: GET /posts?page=2&limit=10
API-->>Store: posts[]
Store->>Component: Append posts, increment page
Component->>User: Render new posts
else No more data
Component->>User: Show "No more posts"
end
Note over User,API: Optimistic Like
User->>Component: Click like
Component->>Component: Toggle likedBy (optimistic)
Component->>Store: toggleLike(postId)
alt Success
Store->>API: PATCH /posts/:id/like
API-->>Store: Updated post
else Error
Component->>Component: Rollback likedBy
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • fix: pictique lazy scroll #648: Introduces identical pagination and infinite-scroll changes to platforms/pictique with the same new stores (isLoadingMore, currentPage, hasMore), pagination functions, and sentinel-based lazy loading in the home component.

Suggested reviewers

  • coodos
  • ananyayaya129

Poem

🐰 Hop, scroll, and load with infinite grace!
No more pages to chase,
Just sentinel eyes watching the screen,
Fetching posts in between,
Likes roll back if things go wrong—
A feed that bounces all day long! 🌿

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check nameStatusExplanationResolution
Description check⚠️ WarningThe PR description is incomplete and misleading. It states 'Adds a loading spinner at the bottom of loading text' but the changeset implements comprehensive pagination, infinite scrolling with IntersectionObserver, incremental loading, optimistic UI updates, and feed state management—far beyond just a loading spinner.Update the description to accurately reflect all major changes: pagination stores, infinite scroll implementation, optimistic like updates, and feed reset logic instead of describing only a loading spinner.
Title check❓ InconclusiveThe title 'Feat/pictique loading spinner' is partially related to the changeset but doesn't capture the main scope of changes which includes pagination, infinite scrolling, and incremental loading beyond just a loading spinner.Consider revising the title to better reflect the primary changes, such as 'Implement infinite scroll pagination for pictique feed' or 'Add pagination and infinite scroll to pictique home feed'.
✅ Passed checks (1 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
platforms/pictique/src/lib/stores/posts.ts (2)

35-48: Consider edge case in hasMore calculation.

The flexible response parsing on line 36 handles multiple API shapes, which is pragmatic. However, the hasMore condition on line 48 may have an edge case:

hasMore.set(newPosts.length===limit&&responsePage<totalPages);

If totalPages defaults to 0 (when total is 0 or undefined), responsePage < totalPages will be false, which is correct. But if the API returns total: 0 with actual posts (inconsistent response), hasMore will incorrectly be false.

🔎 Consider a more defensive approach
- hasMore.set(newPosts.length === limit && responsePage < totalPages);+ // Only set hasMore to false when we definitively know there's no more data+ const moreAvailable = newPosts.length === limit && (totalPages === 0 || responsePage < totalPages);+ hasMore.set(moreAvailable);

73-90: Redundant mapping on line 79.

The .map((img) => img) creates a shallow copy of the array but doesn't transform elements. If the intent is to create a copy, use spread syntax for clarity.

🔎 Simplify the images array
 const response = await apiClient.post("/api/posts", {
text,
- images: images.map((img) => img),+ images: [...images],
});

Or if a copy isn't needed:

 const response = await apiClient.post("/api/posts", {
text,
- images: images.map((img) => img),+ images,
});
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db6ea53 and 077289f.

📒 Files selected for processing (2)
  • platforms/pictique/src/lib/stores/posts.ts
  • platforms/pictique/src/routes/(protected)/home/+page.svelte
🧰 Additional context used
🧬 Code graph analysis (1)
platforms/pictique/src/lib/stores/posts.ts (1)
platforms/pictique/src/lib/types.ts (1)
  • Post (35-58)
🔇 Additional comments (9)
platforms/pictique/src/routes/(protected)/home/+page.svelte (5)

7-23: LGTM! Clean import organization.

The new imports for pagination stores (hasMore, isLoadingMore, loadMoreFeed, resetFeed) and get from svelte/store are correctly added to support the infinite scroll functionality.


31-56: Well-implemented IntersectionObserver with proper cleanup.

The sentinel action correctly implements infinite scroll detection. Good use of rootMargin: '200px' for pre-fetching before the user reaches the bottom, and proper cleanup in destroy().

Minor consideration: Using get() inside the callback reads the store value synchronously at callback time, which is correct here since the observer callback runs outside the Svelte reactive context.


111-115: LGTM! Clean initialization.

Calling resetFeed() before fetchFeed() ensures a clean state on component mount, which is important when navigating back to this page.


137-207: Solid optimistic update pattern with proper rollback.

The implementation correctly:

  1. Captures state before modification for reliable rollback
  2. Performs optimistic UI update immediately
  3. Calls the API in background
  4. Reverts on error with the saved original state

One minor edge case: if profile becomes null between the guard check (line 138) and the API response, the rollback would still work correctly since originalLikedBy is captured early.


223-236: Nice loading spinner implementation.

The conditional rendering properly handles all states:

  • Loading more indicator with spinner
  • End-of-feed message
  • Sentinel for triggering more loads

The Tailwind classes are valid for v4.

platforms/pictique/src/lib/stores/posts.ts (4)

1-11: LGTM! Clean store declarations.

The new pagination stores (isLoadingMore, currentPage, hasMore) are properly typed with appropriate initial values.


16-20: LGTM! Simple and correct reset function.

Properly resets all pagination state to initial values.


61-71: LGTM! Proper guard against concurrent fetches.

The loadMoreFeed function correctly checks both isLoading and isLoadingMore to prevent duplicate requests, and respects the hasMore flag.


92-101: LGTM! Simple and focused.

The toggleLike function correctly delegates optimistic updates to the caller (the component), keeping this function as a simple API wrapper that throws on failure for the caller to handle rollback.

@coodos
coodos merged commit 07cf29a into mainJan 5, 2026
4 checks passed
@coodos
coodos deleted the feat/pictique-loading-spinner branch January 5, 2026 20:22
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Feat/pictique loading spinner - #652

Merged
coodos merged 6 commits into
mainfrom
feat/pictique-loading-spinner
Jan 5, 2026
Merged

Feat/pictique loading spinner#652
coodos merged 6 commits into
mainfrom
feat/pictique-loading-spinner

Conversation

@sosweetham

@sosweethamsosweetham commented Jan 5, 2026

Copy link
Copy Markdown
Member

Description of change

Adds a loading spinner at the bottom of loading text

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)

How the change has been tested

Manual

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added infinite scrolling to automatically load more posts as you scroll down the feed
    • Implemented optimistic like updates for instant visual feedback when liking posts
    • Enhanced loading indicators to clearly distinguish between initial feed loading and loading additional content

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Jan 5, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR implements infinite scrolling pagination for the feed using IntersectionObserver-based lazy loading. It introduces new pagination stores (currentPage, hasMore, isLoadingMore), refactors fetchFeed to support append mode for incremental loading, adds loadMoreFeed to fetch subsequent pages, and implements optimistic UI updates for post likes with rollback on error.

Changes

Cohort / File(s)Summary
Store Pagination Logic
platforms/pictique/src/lib/stores/posts.ts
Added observable stores for pagination state (isLoadingMore, currentPage, hasMore); refactored fetchFeed to support both initial and append modes with dynamic totalPages calculation; introduced resetFeed to clear state and loadMoreFeed to trigger next-page fetches; updated createPost to reset and re-fetch feed after creation.
Component Infinite Scrolling
platforms/pictique/src/routes/(protected)/home/+page.svelte
Replaced manual scroll handling with IntersectionObserver-based sentinel pattern; expanded store imports to include pagination controls; reworked onMount to reset and fetch initial page; implemented optimistic like updates with rollback on failure; added UI for loading indicators, sentinel element, and no-more-posts message.

Sequence Diagram(s)

sequenceDiagram
participant User
participant Component as home/+page.svelte
participant Observer as IntersectionObserver
participant Store as posts.ts
participant API as /posts API
Note over User,API: Initial Load
User->>Component: Visit home
Component->>Store: resetFeed()
Component->>Store: fetchFeed(1, 10, false)
Store->>API: GET /posts?page=1&limit=10
API-->>Store: posts[], totalPages
Store->>Component: Update posts, currentPage, hasMore
Note over User,API: Infinite Scroll
User->>Observer: Scroll near sentinel
Observer->>Component: Intersection observed
alt hasMore && !isLoadingMore
Component->>Store: loadMoreFeed()
Store->>API: GET /posts?page=2&limit=10
API-->>Store: posts[]
Store->>Component: Append posts, increment page
Component->>User: Render new posts
else No more data
Component->>User: Show "No more posts"
end
Note over User,API: Optimistic Like
User->>Component: Click like
Component->>Component: Toggle likedBy (optimistic)
Component->>Store: toggleLike(postId)
alt Success
Store->>API: PATCH /posts/:id/like
API-->>Store: Updated post
else Error
Component->>Component: Rollback likedBy
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • fix: pictique lazy scroll #648: Introduces identical pagination and infinite-scroll changes to platforms/pictique with the same new stores (isLoadingMore, currentPage, hasMore), pagination functions, and sentinel-based lazy loading in the home component.

Suggested reviewers

  • coodos
  • ananyayaya129

Poem

🐰 Hop, scroll, and load with infinite grace!
No more pages to chase,
Just sentinel eyes watching the screen,
Fetching posts in between,
Likes roll back if things go wrong—
A feed that bounces all day long! 🌿

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check nameStatusExplanationResolution
Description check⚠️ WarningThe PR description is incomplete and misleading. It states 'Adds a loading spinner at the bottom of loading text' but the changeset implements comprehensive pagination, infinite scrolling with IntersectionObserver, incremental loading, optimistic UI updates, and feed state management—far beyond just a loading spinner.Update the description to accurately reflect all major changes: pagination stores, infinite scroll implementation, optimistic like updates, and feed reset logic instead of describing only a loading spinner.
Title check❓ InconclusiveThe title 'Feat/pictique loading spinner' is partially related to the changeset but doesn't capture the main scope of changes which includes pagination, infinite scrolling, and incremental loading beyond just a loading spinner.Consider revising the title to better reflect the primary changes, such as 'Implement infinite scroll pagination for pictique feed' or 'Add pagination and infinite scroll to pictique home feed'.
✅ Passed checks (1 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
platforms/pictique/src/lib/stores/posts.ts (2)

35-48: Consider edge case in hasMore calculation.

The flexible response parsing on line 36 handles multiple API shapes, which is pragmatic. However, the hasMore condition on line 48 may have an edge case:

hasMore.set(newPosts.length===limit&&responsePage<totalPages);

If totalPages defaults to 0 (when total is 0 or undefined), responsePage < totalPages will be false, which is correct. But if the API returns total: 0 with actual posts (inconsistent response), hasMore will incorrectly be false.

🔎 Consider a more defensive approach
- hasMore.set(newPosts.length === limit && responsePage < totalPages);+ // Only set hasMore to false when we definitively know there's no more data+ const moreAvailable = newPosts.length === limit && (totalPages === 0 || responsePage < totalPages);+ hasMore.set(moreAvailable);

73-90: Redundant mapping on line 79.

The .map((img) => img) creates a shallow copy of the array but doesn't transform elements. If the intent is to create a copy, use spread syntax for clarity.

🔎 Simplify the images array
 const response = await apiClient.post("/api/posts", {
text,
- images: images.map((img) => img),+ images: [...images],
});

Or if a copy isn't needed:

 const response = await apiClient.post("/api/posts", {
text,
- images: images.map((img) => img),+ images,
});
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db6ea53 and 077289f.

📒 Files selected for processing (2)
  • platforms/pictique/src/lib/stores/posts.ts
  • platforms/pictique/src/routes/(protected)/home/+page.svelte
🧰 Additional context used
🧬 Code graph analysis (1)
platforms/pictique/src/lib/stores/posts.ts (1)
platforms/pictique/src/lib/types.ts (1)
  • Post (35-58)
🔇 Additional comments (9)
platforms/pictique/src/routes/(protected)/home/+page.svelte (5)

7-23: LGTM! Clean import organization.

The new imports for pagination stores (hasMore, isLoadingMore, loadMoreFeed, resetFeed) and get from svelte/store are correctly added to support the infinite scroll functionality.


31-56: Well-implemented IntersectionObserver with proper cleanup.

The sentinel action correctly implements infinite scroll detection. Good use of rootMargin: '200px' for pre-fetching before the user reaches the bottom, and proper cleanup in destroy().

Minor consideration: Using get() inside the callback reads the store value synchronously at callback time, which is correct here since the observer callback runs outside the Svelte reactive context.


111-115: LGTM! Clean initialization.

Calling resetFeed() before fetchFeed() ensures a clean state on component mount, which is important when navigating back to this page.


137-207: Solid optimistic update pattern with proper rollback.

The implementation correctly:

  1. Captures state before modification for reliable rollback
  2. Performs optimistic UI update immediately
  3. Calls the API in background
  4. Reverts on error with the saved original state

One minor edge case: if profile becomes null between the guard check (line 138) and the API response, the rollback would still work correctly since originalLikedBy is captured early.


223-236: Nice loading spinner implementation.

The conditional rendering properly handles all states:

  • Loading more indicator with spinner
  • End-of-feed message
  • Sentinel for triggering more loads

The Tailwind classes are valid for v4.

platforms/pictique/src/lib/stores/posts.ts (4)

1-11: LGTM! Clean store declarations.

The new pagination stores (isLoadingMore, currentPage, hasMore) are properly typed with appropriate initial values.


16-20: LGTM! Simple and correct reset function.

Properly resets all pagination state to initial values.


61-71: LGTM! Proper guard against concurrent fetches.

The loadMoreFeed function correctly checks both isLoading and isLoadingMore to prevent duplicate requests, and respects the hasMore flag.


92-101: LGTM! Simple and focused.

The toggleLike function correctly delegates optimistic updates to the caller (the component), keeping this function as a simple API wrapper that throws on failure for the caller to handle rollback.

@coodos
coodos merged commit 07cf29a into mainJan 5, 2026
4 checks passed
@coodos
coodos deleted the feat/pictique-loading-spinner branch January 5, 2026 20:22
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Feat/pictique loading spinner - #652

Merged
coodos merged 6 commits into
mainfrom
feat/pictique-loading-spinner
Jan 5, 2026
Merged

Feat/pictique loading spinner#652
coodos merged 6 commits into
mainfrom
feat/pictique-loading-spinner

Conversation

@sosweetham

@sosweethamsosweetham commented Jan 5, 2026

Copy link
Copy Markdown
Member

Description of change

Adds a loading spinner at the bottom of loading text

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)

How the change has been tested

Manual

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added infinite scrolling to automatically load more posts as you scroll down the feed
    • Implemented optimistic like updates for instant visual feedback when liking posts
    • Enhanced loading indicators to clearly distinguish between initial feed loading and loading additional content

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Jan 5, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR implements infinite scrolling pagination for the feed using IntersectionObserver-based lazy loading. It introduces new pagination stores (currentPage, hasMore, isLoadingMore), refactors fetchFeed to support append mode for incremental loading, adds loadMoreFeed to fetch subsequent pages, and implements optimistic UI updates for post likes with rollback on error.

Changes

Cohort / File(s)Summary
Store Pagination Logic
platforms/pictique/src/lib/stores/posts.ts
Added observable stores for pagination state (isLoadingMore, currentPage, hasMore); refactored fetchFeed to support both initial and append modes with dynamic totalPages calculation; introduced resetFeed to clear state and loadMoreFeed to trigger next-page fetches; updated createPost to reset and re-fetch feed after creation.
Component Infinite Scrolling
platforms/pictique/src/routes/(protected)/home/+page.svelte
Replaced manual scroll handling with IntersectionObserver-based sentinel pattern; expanded store imports to include pagination controls; reworked onMount to reset and fetch initial page; implemented optimistic like updates with rollback on failure; added UI for loading indicators, sentinel element, and no-more-posts message.

Sequence Diagram(s)

sequenceDiagram
participant User
participant Component as home/+page.svelte
participant Observer as IntersectionObserver
participant Store as posts.ts
participant API as /posts API
Note over User,API: Initial Load
User->>Component: Visit home
Component->>Store: resetFeed()
Component->>Store: fetchFeed(1, 10, false)
Store->>API: GET /posts?page=1&limit=10
API-->>Store: posts[], totalPages
Store->>Component: Update posts, currentPage, hasMore
Note over User,API: Infinite Scroll
User->>Observer: Scroll near sentinel
Observer->>Component: Intersection observed
alt hasMore && !isLoadingMore
Component->>Store: loadMoreFeed()
Store->>API: GET /posts?page=2&limit=10
API-->>Store: posts[]
Store->>Component: Append posts, increment page
Component->>User: Render new posts
else No more data
Component->>User: Show "No more posts"
end
Note over User,API: Optimistic Like
User->>Component: Click like
Component->>Component: Toggle likedBy (optimistic)
Component->>Store: toggleLike(postId)
alt Success
Store->>API: PATCH /posts/:id/like
API-->>Store: Updated post
else Error
Component->>Component: Rollback likedBy
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • fix: pictique lazy scroll #648: Introduces identical pagination and infinite-scroll changes to platforms/pictique with the same new stores (isLoadingMore, currentPage, hasMore), pagination functions, and sentinel-based lazy loading in the home component.

Suggested reviewers

  • coodos
  • ananyayaya129

Poem

🐰 Hop, scroll, and load with infinite grace!
No more pages to chase,
Just sentinel eyes watching the screen,
Fetching posts in between,
Likes roll back if things go wrong—
A feed that bounces all day long! 🌿

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check nameStatusExplanationResolution
Description check⚠️ WarningThe PR description is incomplete and misleading. It states 'Adds a loading spinner at the bottom of loading text' but the changeset implements comprehensive pagination, infinite scrolling with IntersectionObserver, incremental loading, optimistic UI updates, and feed state management—far beyond just a loading spinner.Update the description to accurately reflect all major changes: pagination stores, infinite scroll implementation, optimistic like updates, and feed reset logic instead of describing only a loading spinner.
Title check❓ InconclusiveThe title 'Feat/pictique loading spinner' is partially related to the changeset but doesn't capture the main scope of changes which includes pagination, infinite scrolling, and incremental loading beyond just a loading spinner.Consider revising the title to better reflect the primary changes, such as 'Implement infinite scroll pagination for pictique feed' or 'Add pagination and infinite scroll to pictique home feed'.
✅ Passed checks (1 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
platforms/pictique/src/lib/stores/posts.ts (2)

35-48: Consider edge case in hasMore calculation.

The flexible response parsing on line 36 handles multiple API shapes, which is pragmatic. However, the hasMore condition on line 48 may have an edge case:

hasMore.set(newPosts.length===limit&&responsePage<totalPages);

If totalPages defaults to 0 (when total is 0 or undefined), responsePage < totalPages will be false, which is correct. But if the API returns total: 0 with actual posts (inconsistent response), hasMore will incorrectly be false.

🔎 Consider a more defensive approach
- hasMore.set(newPosts.length === limit && responsePage < totalPages);+ // Only set hasMore to false when we definitively know there's no more data+ const moreAvailable = newPosts.length === limit && (totalPages === 0 || responsePage < totalPages);+ hasMore.set(moreAvailable);

73-90: Redundant mapping on line 79.

The .map((img) => img) creates a shallow copy of the array but doesn't transform elements. If the intent is to create a copy, use spread syntax for clarity.

🔎 Simplify the images array
 const response = await apiClient.post("/api/posts", {
text,
- images: images.map((img) => img),+ images: [...images],
});

Or if a copy isn't needed:

 const response = await apiClient.post("/api/posts", {
text,
- images: images.map((img) => img),+ images,
});
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db6ea53 and 077289f.

📒 Files selected for processing (2)
  • platforms/pictique/src/lib/stores/posts.ts
  • platforms/pictique/src/routes/(protected)/home/+page.svelte
🧰 Additional context used
🧬 Code graph analysis (1)
platforms/pictique/src/lib/stores/posts.ts (1)
platforms/pictique/src/lib/types.ts (1)
  • Post (35-58)
🔇 Additional comments (9)
platforms/pictique/src/routes/(protected)/home/+page.svelte (5)

7-23: LGTM! Clean import organization.

The new imports for pagination stores (hasMore, isLoadingMore, loadMoreFeed, resetFeed) and get from svelte/store are correctly added to support the infinite scroll functionality.


31-56: Well-implemented IntersectionObserver with proper cleanup.

The sentinel action correctly implements infinite scroll detection. Good use of rootMargin: '200px' for pre-fetching before the user reaches the bottom, and proper cleanup in destroy().

Minor consideration: Using get() inside the callback reads the store value synchronously at callback time, which is correct here since the observer callback runs outside the Svelte reactive context.


111-115: LGTM! Clean initialization.

Calling resetFeed() before fetchFeed() ensures a clean state on component mount, which is important when navigating back to this page.


137-207: Solid optimistic update pattern with proper rollback.

The implementation correctly:

  1. Captures state before modification for reliable rollback
  2. Performs optimistic UI update immediately
  3. Calls the API in background
  4. Reverts on error with the saved original state

One minor edge case: if profile becomes null between the guard check (line 138) and the API response, the rollback would still work correctly since originalLikedBy is captured early.


223-236: Nice loading spinner implementation.

The conditional rendering properly handles all states:

  • Loading more indicator with spinner
  • End-of-feed message
  • Sentinel for triggering more loads

The Tailwind classes are valid for v4.

platforms/pictique/src/lib/stores/posts.ts (4)

1-11: LGTM! Clean store declarations.

The new pagination stores (isLoadingMore, currentPage, hasMore) are properly typed with appropriate initial values.


16-20: LGTM! Simple and correct reset function.

Properly resets all pagination state to initial values.


61-71: LGTM! Proper guard against concurrent fetches.

The loadMoreFeed function correctly checks both isLoading and isLoadingMore to prevent duplicate requests, and respects the hasMore flag.


92-101: LGTM! Simple and focused.

The toggleLike function correctly delegates optimistic updates to the caller (the component), keeping this function as a simple API wrapper that throws on failure for the caller to handle rollback.

@coodos
coodos merged commit 07cf29a into mainJan 5, 2026
4 checks passed
@coodos
coodos deleted the feat/pictique-loading-spinner branch January 5, 2026 20:22
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Feat/pictique loading spinner - #652

Merged
coodos merged 6 commits into
mainfrom
feat/pictique-loading-spinner
Jan 5, 2026
Merged

Feat/pictique loading spinner#652
coodos merged 6 commits into
mainfrom
feat/pictique-loading-spinner

Conversation

@sosweetham

@sosweethamsosweetham commented Jan 5, 2026

Copy link
Copy Markdown
Member

Description of change

Adds a loading spinner at the bottom of loading text

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)

How the change has been tested

Manual

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added infinite scrolling to automatically load more posts as you scroll down the feed
    • Implemented optimistic like updates for instant visual feedback when liking posts
    • Enhanced loading indicators to clearly distinguish between initial feed loading and loading additional content

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Jan 5, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR implements infinite scrolling pagination for the feed using IntersectionObserver-based lazy loading. It introduces new pagination stores (currentPage, hasMore, isLoadingMore), refactors fetchFeed to support append mode for incremental loading, adds loadMoreFeed to fetch subsequent pages, and implements optimistic UI updates for post likes with rollback on error.

Changes

Cohort / File(s)Summary
Store Pagination Logic
platforms/pictique/src/lib/stores/posts.ts
Added observable stores for pagination state (isLoadingMore, currentPage, hasMore); refactored fetchFeed to support both initial and append modes with dynamic totalPages calculation; introduced resetFeed to clear state and loadMoreFeed to trigger next-page fetches; updated createPost to reset and re-fetch feed after creation.
Component Infinite Scrolling
platforms/pictique/src/routes/(protected)/home/+page.svelte
Replaced manual scroll handling with IntersectionObserver-based sentinel pattern; expanded store imports to include pagination controls; reworked onMount to reset and fetch initial page; implemented optimistic like updates with rollback on failure; added UI for loading indicators, sentinel element, and no-more-posts message.

Sequence Diagram(s)

sequenceDiagram
participant User
participant Component as home/+page.svelte
participant Observer as IntersectionObserver
participant Store as posts.ts
participant API as /posts API
Note over User,API: Initial Load
User->>Component: Visit home
Component->>Store: resetFeed()
Component->>Store: fetchFeed(1, 10, false)
Store->>API: GET /posts?page=1&limit=10
API-->>Store: posts[], totalPages
Store->>Component: Update posts, currentPage, hasMore
Note over User,API: Infinite Scroll
User->>Observer: Scroll near sentinel
Observer->>Component: Intersection observed
alt hasMore && !isLoadingMore
Component->>Store: loadMoreFeed()
Store->>API: GET /posts?page=2&limit=10
API-->>Store: posts[]
Store->>Component: Append posts, increment page
Component->>User: Render new posts
else No more data
Component->>User: Show "No more posts"
end
Note over User,API: Optimistic Like
User->>Component: Click like
Component->>Component: Toggle likedBy (optimistic)
Component->>Store: toggleLike(postId)
alt Success
Store->>API: PATCH /posts/:id/like
API-->>Store: Updated post
else Error
Component->>Component: Rollback likedBy
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • fix: pictique lazy scroll #648: Introduces identical pagination and infinite-scroll changes to platforms/pictique with the same new stores (isLoadingMore, currentPage, hasMore), pagination functions, and sentinel-based lazy loading in the home component.

Suggested reviewers

  • coodos
  • ananyayaya129

Poem

🐰 Hop, scroll, and load with infinite grace!
No more pages to chase,
Just sentinel eyes watching the screen,
Fetching posts in between,
Likes roll back if things go wrong—
A feed that bounces all day long! 🌿

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check nameStatusExplanationResolution
Description check⚠️ WarningThe PR description is incomplete and misleading. It states 'Adds a loading spinner at the bottom of loading text' but the changeset implements comprehensive pagination, infinite scrolling with IntersectionObserver, incremental loading, optimistic UI updates, and feed state management—far beyond just a loading spinner.Update the description to accurately reflect all major changes: pagination stores, infinite scroll implementation, optimistic like updates, and feed reset logic instead of describing only a loading spinner.
Title check❓ InconclusiveThe title 'Feat/pictique loading spinner' is partially related to the changeset but doesn't capture the main scope of changes which includes pagination, infinite scrolling, and incremental loading beyond just a loading spinner.Consider revising the title to better reflect the primary changes, such as 'Implement infinite scroll pagination for pictique feed' or 'Add pagination and infinite scroll to pictique home feed'.
✅ Passed checks (1 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
platforms/pictique/src/lib/stores/posts.ts (2)

35-48: Consider edge case in hasMore calculation.

The flexible response parsing on line 36 handles multiple API shapes, which is pragmatic. However, the hasMore condition on line 48 may have an edge case:

hasMore.set(newPosts.length===limit&&responsePage<totalPages);

If totalPages defaults to 0 (when total is 0 or undefined), responsePage < totalPages will be false, which is correct. But if the API returns total: 0 with actual posts (inconsistent response), hasMore will incorrectly be false.

🔎 Consider a more defensive approach
- hasMore.set(newPosts.length === limit && responsePage < totalPages);+ // Only set hasMore to false when we definitively know there's no more data+ const moreAvailable = newPosts.length === limit && (totalPages === 0 || responsePage < totalPages);+ hasMore.set(moreAvailable);

73-90: Redundant mapping on line 79.

The .map((img) => img) creates a shallow copy of the array but doesn't transform elements. If the intent is to create a copy, use spread syntax for clarity.

🔎 Simplify the images array
 const response = await apiClient.post("/api/posts", {
text,
- images: images.map((img) => img),+ images: [...images],
});

Or if a copy isn't needed:

 const response = await apiClient.post("/api/posts", {
text,
- images: images.map((img) => img),+ images,
});
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db6ea53 and 077289f.

📒 Files selected for processing (2)
  • platforms/pictique/src/lib/stores/posts.ts
  • platforms/pictique/src/routes/(protected)/home/+page.svelte
🧰 Additional context used
🧬 Code graph analysis (1)
platforms/pictique/src/lib/stores/posts.ts (1)
platforms/pictique/src/lib/types.ts (1)
  • Post (35-58)
🔇 Additional comments (9)
platforms/pictique/src/routes/(protected)/home/+page.svelte (5)

7-23: LGTM! Clean import organization.

The new imports for pagination stores (hasMore, isLoadingMore, loadMoreFeed, resetFeed) and get from svelte/store are correctly added to support the infinite scroll functionality.


31-56: Well-implemented IntersectionObserver with proper cleanup.

The sentinel action correctly implements infinite scroll detection. Good use of rootMargin: '200px' for pre-fetching before the user reaches the bottom, and proper cleanup in destroy().

Minor consideration: Using get() inside the callback reads the store value synchronously at callback time, which is correct here since the observer callback runs outside the Svelte reactive context.


111-115: LGTM! Clean initialization.

Calling resetFeed() before fetchFeed() ensures a clean state on component mount, which is important when navigating back to this page.


137-207: Solid optimistic update pattern with proper rollback.

The implementation correctly:

  1. Captures state before modification for reliable rollback
  2. Performs optimistic UI update immediately
  3. Calls the API in background
  4. Reverts on error with the saved original state

One minor edge case: if profile becomes null between the guard check (line 138) and the API response, the rollback would still work correctly since originalLikedBy is captured early.


223-236: Nice loading spinner implementation.

The conditional rendering properly handles all states:

  • Loading more indicator with spinner
  • End-of-feed message
  • Sentinel for triggering more loads

The Tailwind classes are valid for v4.

platforms/pictique/src/lib/stores/posts.ts (4)

1-11: LGTM! Clean store declarations.

The new pagination stores (isLoadingMore, currentPage, hasMore) are properly typed with appropriate initial values.


16-20: LGTM! Simple and correct reset function.

Properly resets all pagination state to initial values.


61-71: LGTM! Proper guard against concurrent fetches.

The loadMoreFeed function correctly checks both isLoading and isLoadingMore to prevent duplicate requests, and respects the hasMore flag.


92-101: LGTM! Simple and focused.

The toggleLike function correctly delegates optimistic updates to the caller (the component), keeping this function as a simple API wrapper that throws on failure for the caller to handle rollback.

@coodos
coodos merged commit 07cf29a into mainJan 5, 2026
4 checks passed
@coodos
coodos deleted the feat/pictique-loading-spinner branch January 5, 2026 20:22
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Feat/pictique loading spinner - #652

Merged
coodos merged 6 commits into
mainfrom
feat/pictique-loading-spinner
Jan 5, 2026
Merged

Feat/pictique loading spinner#652
coodos merged 6 commits into
mainfrom
feat/pictique-loading-spinner

Conversation

@sosweetham

@sosweethamsosweetham commented Jan 5, 2026

Copy link
Copy Markdown
Member

Description of change

Adds a loading spinner at the bottom of loading text

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)

How the change has been tested

Manual

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added infinite scrolling to automatically load more posts as you scroll down the feed
    • Implemented optimistic like updates for instant visual feedback when liking posts
    • Enhanced loading indicators to clearly distinguish between initial feed loading and loading additional content

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Jan 5, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR implements infinite scrolling pagination for the feed using IntersectionObserver-based lazy loading. It introduces new pagination stores (currentPage, hasMore, isLoadingMore), refactors fetchFeed to support append mode for incremental loading, adds loadMoreFeed to fetch subsequent pages, and implements optimistic UI updates for post likes with rollback on error.

Changes

Cohort / File(s)Summary
Store Pagination Logic
platforms/pictique/src/lib/stores/posts.ts
Added observable stores for pagination state (isLoadingMore, currentPage, hasMore); refactored fetchFeed to support both initial and append modes with dynamic totalPages calculation; introduced resetFeed to clear state and loadMoreFeed to trigger next-page fetches; updated createPost to reset and re-fetch feed after creation.
Component Infinite Scrolling
platforms/pictique/src/routes/(protected)/home/+page.svelte
Replaced manual scroll handling with IntersectionObserver-based sentinel pattern; expanded store imports to include pagination controls; reworked onMount to reset and fetch initial page; implemented optimistic like updates with rollback on failure; added UI for loading indicators, sentinel element, and no-more-posts message.

Sequence Diagram(s)

sequenceDiagram
participant User
participant Component as home/+page.svelte
participant Observer as IntersectionObserver
participant Store as posts.ts
participant API as /posts API
Note over User,API: Initial Load
User->>Component: Visit home
Component->>Store: resetFeed()
Component->>Store: fetchFeed(1, 10, false)
Store->>API: GET /posts?page=1&limit=10
API-->>Store: posts[], totalPages
Store->>Component: Update posts, currentPage, hasMore
Note over User,API: Infinite Scroll
User->>Observer: Scroll near sentinel
Observer->>Component: Intersection observed
alt hasMore && !isLoadingMore
Component->>Store: loadMoreFeed()
Store->>API: GET /posts?page=2&limit=10
API-->>Store: posts[]
Store->>Component: Append posts, increment page
Component->>User: Render new posts
else No more data
Component->>User: Show "No more posts"
end
Note over User,API: Optimistic Like
User->>Component: Click like
Component->>Component: Toggle likedBy (optimistic)
Component->>Store: toggleLike(postId)
alt Success
Store->>API: PATCH /posts/:id/like
API-->>Store: Updated post
else Error
Component->>Component: Rollback likedBy
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • fix: pictique lazy scroll #648: Introduces identical pagination and infinite-scroll changes to platforms/pictique with the same new stores (isLoadingMore, currentPage, hasMore), pagination functions, and sentinel-based lazy loading in the home component.

Suggested reviewers

  • coodos
  • ananyayaya129

Poem

🐰 Hop, scroll, and load with infinite grace!
No more pages to chase,
Just sentinel eyes watching the screen,
Fetching posts in between,
Likes roll back if things go wrong—
A feed that bounces all day long! 🌿

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check nameStatusExplanationResolution
Description check⚠️ WarningThe PR description is incomplete and misleading. It states 'Adds a loading spinner at the bottom of loading text' but the changeset implements comprehensive pagination, infinite scrolling with IntersectionObserver, incremental loading, optimistic UI updates, and feed state management—far beyond just a loading spinner.Update the description to accurately reflect all major changes: pagination stores, infinite scroll implementation, optimistic like updates, and feed reset logic instead of describing only a loading spinner.
Title check❓ InconclusiveThe title 'Feat/pictique loading spinner' is partially related to the changeset but doesn't capture the main scope of changes which includes pagination, infinite scrolling, and incremental loading beyond just a loading spinner.Consider revising the title to better reflect the primary changes, such as 'Implement infinite scroll pagination for pictique feed' or 'Add pagination and infinite scroll to pictique home feed'.
✅ Passed checks (1 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
platforms/pictique/src/lib/stores/posts.ts (2)

35-48: Consider edge case in hasMore calculation.

The flexible response parsing on line 36 handles multiple API shapes, which is pragmatic. However, the hasMore condition on line 48 may have an edge case:

hasMore.set(newPosts.length===limit&&responsePage<totalPages);

If totalPages defaults to 0 (when total is 0 or undefined), responsePage < totalPages will be false, which is correct. But if the API returns total: 0 with actual posts (inconsistent response), hasMore will incorrectly be false.

🔎 Consider a more defensive approach
- hasMore.set(newPosts.length === limit && responsePage < totalPages);+ // Only set hasMore to false when we definitively know there's no more data+ const moreAvailable = newPosts.length === limit && (totalPages === 0 || responsePage < totalPages);+ hasMore.set(moreAvailable);

73-90: Redundant mapping on line 79.

The .map((img) => img) creates a shallow copy of the array but doesn't transform elements. If the intent is to create a copy, use spread syntax for clarity.

🔎 Simplify the images array
 const response = await apiClient.post("/api/posts", {
text,
- images: images.map((img) => img),+ images: [...images],
});

Or if a copy isn't needed:

 const response = await apiClient.post("/api/posts", {
text,
- images: images.map((img) => img),+ images,
});
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db6ea53 and 077289f.

📒 Files selected for processing (2)
  • platforms/pictique/src/lib/stores/posts.ts
  • platforms/pictique/src/routes/(protected)/home/+page.svelte
🧰 Additional context used
🧬 Code graph analysis (1)
platforms/pictique/src/lib/stores/posts.ts (1)
platforms/pictique/src/lib/types.ts (1)
  • Post (35-58)
🔇 Additional comments (9)
platforms/pictique/src/routes/(protected)/home/+page.svelte (5)

7-23: LGTM! Clean import organization.

The new imports for pagination stores (hasMore, isLoadingMore, loadMoreFeed, resetFeed) and get from svelte/store are correctly added to support the infinite scroll functionality.


31-56: Well-implemented IntersectionObserver with proper cleanup.

The sentinel action correctly implements infinite scroll detection. Good use of rootMargin: '200px' for pre-fetching before the user reaches the bottom, and proper cleanup in destroy().

Minor consideration: Using get() inside the callback reads the store value synchronously at callback time, which is correct here since the observer callback runs outside the Svelte reactive context.


111-115: LGTM! Clean initialization.

Calling resetFeed() before fetchFeed() ensures a clean state on component mount, which is important when navigating back to this page.


137-207: Solid optimistic update pattern with proper rollback.

The implementation correctly:

  1. Captures state before modification for reliable rollback
  2. Performs optimistic UI update immediately
  3. Calls the API in background
  4. Reverts on error with the saved original state

One minor edge case: if profile becomes null between the guard check (line 138) and the API response, the rollback would still work correctly since originalLikedBy is captured early.


223-236: Nice loading spinner implementation.

The conditional rendering properly handles all states:

  • Loading more indicator with spinner
  • End-of-feed message
  • Sentinel for triggering more loads

The Tailwind classes are valid for v4.

platforms/pictique/src/lib/stores/posts.ts (4)

1-11: LGTM! Clean store declarations.

The new pagination stores (isLoadingMore, currentPage, hasMore) are properly typed with appropriate initial values.


16-20: LGTM! Simple and correct reset function.

Properly resets all pagination state to initial values.


61-71: LGTM! Proper guard against concurrent fetches.

The loadMoreFeed function correctly checks both isLoading and isLoadingMore to prevent duplicate requests, and respects the hasMore flag.


92-101: LGTM! Simple and focused.

The toggleLike function correctly delegates optimistic updates to the caller (the component), keeping this function as a simple API wrapper that throws on failure for the caller to handle rollback.

@coodos
coodos merged commit 07cf29a into mainJan 5, 2026
4 checks passed
@coodos
coodos deleted the feat/pictique-loading-spinner branch January 5, 2026 20:22
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Feat/pictique loading spinner - #652

Merged
coodos merged 6 commits into
mainfrom
feat/pictique-loading-spinner
Jan 5, 2026
Merged

Feat/pictique loading spinner#652
coodos merged 6 commits into
mainfrom
feat/pictique-loading-spinner

Conversation

@sosweetham

@sosweethamsosweetham commented Jan 5, 2026

Copy link
Copy Markdown
Member

Description of change

Adds a loading spinner at the bottom of loading text

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)

How the change has been tested

Manual

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added infinite scrolling to automatically load more posts as you scroll down the feed
    • Implemented optimistic like updates for instant visual feedback when liking posts
    • Enhanced loading indicators to clearly distinguish between initial feed loading and loading additional content

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Jan 5, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR implements infinite scrolling pagination for the feed using IntersectionObserver-based lazy loading. It introduces new pagination stores (currentPage, hasMore, isLoadingMore), refactors fetchFeed to support append mode for incremental loading, adds loadMoreFeed to fetch subsequent pages, and implements optimistic UI updates for post likes with rollback on error.

Changes

Cohort / File(s)Summary
Store Pagination Logic
platforms/pictique/src/lib/stores/posts.ts
Added observable stores for pagination state (isLoadingMore, currentPage, hasMore); refactored fetchFeed to support both initial and append modes with dynamic totalPages calculation; introduced resetFeed to clear state and loadMoreFeed to trigger next-page fetches; updated createPost to reset and re-fetch feed after creation.
Component Infinite Scrolling
platforms/pictique/src/routes/(protected)/home/+page.svelte
Replaced manual scroll handling with IntersectionObserver-based sentinel pattern; expanded store imports to include pagination controls; reworked onMount to reset and fetch initial page; implemented optimistic like updates with rollback on failure; added UI for loading indicators, sentinel element, and no-more-posts message.

Sequence Diagram(s)

sequenceDiagram
participant User
participant Component as home/+page.svelte
participant Observer as IntersectionObserver
participant Store as posts.ts
participant API as /posts API
Note over User,API: Initial Load
User->>Component: Visit home
Component->>Store: resetFeed()
Component->>Store: fetchFeed(1, 10, false)
Store->>API: GET /posts?page=1&limit=10
API-->>Store: posts[], totalPages
Store->>Component: Update posts, currentPage, hasMore
Note over User,API: Infinite Scroll
User->>Observer: Scroll near sentinel
Observer->>Component: Intersection observed
alt hasMore && !isLoadingMore
Component->>Store: loadMoreFeed()
Store->>API: GET /posts?page=2&limit=10
API-->>Store: posts[]
Store->>Component: Append posts, increment page
Component->>User: Render new posts
else No more data
Component->>User: Show "No more posts"
end
Note over User,API: Optimistic Like
User->>Component: Click like
Component->>Component: Toggle likedBy (optimistic)
Component->>Store: toggleLike(postId)
alt Success
Store->>API: PATCH /posts/:id/like
API-->>Store: Updated post
else Error
Component->>Component: Rollback likedBy
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • fix: pictique lazy scroll #648: Introduces identical pagination and infinite-scroll changes to platforms/pictique with the same new stores (isLoadingMore, currentPage, hasMore), pagination functions, and sentinel-based lazy loading in the home component.

Suggested reviewers

  • coodos
  • ananyayaya129

Poem

🐰 Hop, scroll, and load with infinite grace!
No more pages to chase,
Just sentinel eyes watching the screen,
Fetching posts in between,
Likes roll back if things go wrong—
A feed that bounces all day long! 🌿

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check nameStatusExplanationResolution
Description check⚠️ WarningThe PR description is incomplete and misleading. It states 'Adds a loading spinner at the bottom of loading text' but the changeset implements comprehensive pagination, infinite scrolling with IntersectionObserver, incremental loading, optimistic UI updates, and feed state management—far beyond just a loading spinner.Update the description to accurately reflect all major changes: pagination stores, infinite scroll implementation, optimistic like updates, and feed reset logic instead of describing only a loading spinner.
Title check❓ InconclusiveThe title 'Feat/pictique loading spinner' is partially related to the changeset but doesn't capture the main scope of changes which includes pagination, infinite scrolling, and incremental loading beyond just a loading spinner.Consider revising the title to better reflect the primary changes, such as 'Implement infinite scroll pagination for pictique feed' or 'Add pagination and infinite scroll to pictique home feed'.
✅ Passed checks (1 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
platforms/pictique/src/lib/stores/posts.ts (2)

35-48: Consider edge case in hasMore calculation.

The flexible response parsing on line 36 handles multiple API shapes, which is pragmatic. However, the hasMore condition on line 48 may have an edge case:

hasMore.set(newPosts.length===limit&&responsePage<totalPages);

If totalPages defaults to 0 (when total is 0 or undefined), responsePage < totalPages will be false, which is correct. But if the API returns total: 0 with actual posts (inconsistent response), hasMore will incorrectly be false.

🔎 Consider a more defensive approach
- hasMore.set(newPosts.length === limit && responsePage < totalPages);+ // Only set hasMore to false when we definitively know there's no more data+ const moreAvailable = newPosts.length === limit && (totalPages === 0 || responsePage < totalPages);+ hasMore.set(moreAvailable);

73-90: Redundant mapping on line 79.

The .map((img) => img) creates a shallow copy of the array but doesn't transform elements. If the intent is to create a copy, use spread syntax for clarity.

🔎 Simplify the images array
 const response = await apiClient.post("/api/posts", {
text,
- images: images.map((img) => img),+ images: [...images],
});

Or if a copy isn't needed:

 const response = await apiClient.post("/api/posts", {
text,
- images: images.map((img) => img),+ images,
});
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db6ea53 and 077289f.

📒 Files selected for processing (2)
  • platforms/pictique/src/lib/stores/posts.ts
  • platforms/pictique/src/routes/(protected)/home/+page.svelte
🧰 Additional context used
🧬 Code graph analysis (1)
platforms/pictique/src/lib/stores/posts.ts (1)
platforms/pictique/src/lib/types.ts (1)
  • Post (35-58)
🔇 Additional comments (9)
platforms/pictique/src/routes/(protected)/home/+page.svelte (5)

7-23: LGTM! Clean import organization.

The new imports for pagination stores (hasMore, isLoadingMore, loadMoreFeed, resetFeed) and get from svelte/store are correctly added to support the infinite scroll functionality.


31-56: Well-implemented IntersectionObserver with proper cleanup.

The sentinel action correctly implements infinite scroll detection. Good use of rootMargin: '200px' for pre-fetching before the user reaches the bottom, and proper cleanup in destroy().

Minor consideration: Using get() inside the callback reads the store value synchronously at callback time, which is correct here since the observer callback runs outside the Svelte reactive context.


111-115: LGTM! Clean initialization.

Calling resetFeed() before fetchFeed() ensures a clean state on component mount, which is important when navigating back to this page.


137-207: Solid optimistic update pattern with proper rollback.

The implementation correctly:

  1. Captures state before modification for reliable rollback
  2. Performs optimistic UI update immediately
  3. Calls the API in background
  4. Reverts on error with the saved original state

One minor edge case: if profile becomes null between the guard check (line 138) and the API response, the rollback would still work correctly since originalLikedBy is captured early.


223-236: Nice loading spinner implementation.

The conditional rendering properly handles all states:

  • Loading more indicator with spinner
  • End-of-feed message
  • Sentinel for triggering more loads

The Tailwind classes are valid for v4.

platforms/pictique/src/lib/stores/posts.ts (4)

1-11: LGTM! Clean store declarations.

The new pagination stores (isLoadingMore, currentPage, hasMore) are properly typed with appropriate initial values.


16-20: LGTM! Simple and correct reset function.

Properly resets all pagination state to initial values.


61-71: LGTM! Proper guard against concurrent fetches.

The loadMoreFeed function correctly checks both isLoading and isLoadingMore to prevent duplicate requests, and respects the hasMore flag.


92-101: LGTM! Simple and focused.

The toggleLike function correctly delegates optimistic updates to the caller (the component), keeping this function as a simple API wrapper that throws on failure for the caller to handle rollback.

@coodos
coodos merged commit 07cf29a into mainJan 5, 2026
4 checks passed
@coodos
coodos deleted the feat/pictique-loading-spinner branch January 5, 2026 20:22
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Feat/pictique loading spinner - #652

Merged
coodos merged 6 commits into
mainfrom
feat/pictique-loading-spinner
Jan 5, 2026
Merged

Feat/pictique loading spinner#652
coodos merged 6 commits into
mainfrom
feat/pictique-loading-spinner

Conversation

@sosweetham

@sosweethamsosweetham commented Jan 5, 2026

Copy link
Copy Markdown
Member

Description of change

Adds a loading spinner at the bottom of loading text

Issue Number

n/a

Type of change

  • New (a change which implements a new feature)

How the change has been tested

Manual

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features
    • Added infinite scrolling to automatically load more posts as you scroll down the feed
    • Implemented optimistic like updates for instant visual feedback when liking posts
    • Enhanced loading indicators to clearly distinguish between initial feed loading and loading additional content

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitaiBot commented Jan 5, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR implements infinite scrolling pagination for the feed using IntersectionObserver-based lazy loading. It introduces new pagination stores (currentPage, hasMore, isLoadingMore), refactors fetchFeed to support append mode for incremental loading, adds loadMoreFeed to fetch subsequent pages, and implements optimistic UI updates for post likes with rollback on error.

Changes

Cohort / File(s)Summary
Store Pagination Logic
platforms/pictique/src/lib/stores/posts.ts
Added observable stores for pagination state (isLoadingMore, currentPage, hasMore); refactored fetchFeed to support both initial and append modes with dynamic totalPages calculation; introduced resetFeed to clear state and loadMoreFeed to trigger next-page fetches; updated createPost to reset and re-fetch feed after creation.
Component Infinite Scrolling
platforms/pictique/src/routes/(protected)/home/+page.svelte
Replaced manual scroll handling with IntersectionObserver-based sentinel pattern; expanded store imports to include pagination controls; reworked onMount to reset and fetch initial page; implemented optimistic like updates with rollback on failure; added UI for loading indicators, sentinel element, and no-more-posts message.

Sequence Diagram(s)

sequenceDiagram
participant User
participant Component as home/+page.svelte
participant Observer as IntersectionObserver
participant Store as posts.ts
participant API as /posts API
Note over User,API: Initial Load
User->>Component: Visit home
Component->>Store: resetFeed()
Component->>Store: fetchFeed(1, 10, false)
Store->>API: GET /posts?page=1&limit=10
API-->>Store: posts[], totalPages
Store->>Component: Update posts, currentPage, hasMore
Note over User,API: Infinite Scroll
User->>Observer: Scroll near sentinel
Observer->>Component: Intersection observed
alt hasMore && !isLoadingMore
Component->>Store: loadMoreFeed()
Store->>API: GET /posts?page=2&limit=10
API-->>Store: posts[]
Store->>Component: Append posts, increment page
Component->>User: Render new posts
else No more data
Component->>User: Show "No more posts"
end
Note over User,API: Optimistic Like
User->>Component: Click like
Component->>Component: Toggle likedBy (optimistic)
Component->>Store: toggleLike(postId)
alt Success
Store->>API: PATCH /posts/:id/like
API-->>Store: Updated post
else Error
Component->>Component: Rollback likedBy
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • fix: pictique lazy scroll #648: Introduces identical pagination and infinite-scroll changes to platforms/pictique with the same new stores (isLoadingMore, currentPage, hasMore), pagination functions, and sentinel-based lazy loading in the home component.

Suggested reviewers

  • coodos
  • ananyayaya129

Poem

🐰 Hop, scroll, and load with infinite grace!
No more pages to chase,
Just sentinel eyes watching the screen,
Fetching posts in between,
Likes roll back if things go wrong—
A feed that bounces all day long! 🌿

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check nameStatusExplanationResolution
Description check⚠️ WarningThe PR description is incomplete and misleading. It states 'Adds a loading spinner at the bottom of loading text' but the changeset implements comprehensive pagination, infinite scrolling with IntersectionObserver, incremental loading, optimistic UI updates, and feed state management—far beyond just a loading spinner.Update the description to accurately reflect all major changes: pagination stores, infinite scroll implementation, optimistic like updates, and feed reset logic instead of describing only a loading spinner.
Title check❓ InconclusiveThe title 'Feat/pictique loading spinner' is partially related to the changeset but doesn't capture the main scope of changes which includes pagination, infinite scrolling, and incremental loading beyond just a loading spinner.Consider revising the title to better reflect the primary changes, such as 'Implement infinite scroll pagination for pictique feed' or 'Add pagination and infinite scroll to pictique home feed'.
✅ Passed checks (1 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings

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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
platforms/pictique/src/lib/stores/posts.ts (2)

35-48: Consider edge case in hasMore calculation.

The flexible response parsing on line 36 handles multiple API shapes, which is pragmatic. However, the hasMore condition on line 48 may have an edge case:

hasMore.set(newPosts.length===limit&&responsePage<totalPages);

If totalPages defaults to 0 (when total is 0 or undefined), responsePage < totalPages will be false, which is correct. But if the API returns total: 0 with actual posts (inconsistent response), hasMore will incorrectly be false.

🔎 Consider a more defensive approach
- hasMore.set(newPosts.length === limit && responsePage < totalPages);+ // Only set hasMore to false when we definitively know there's no more data+ const moreAvailable = newPosts.length === limit && (totalPages === 0 || responsePage < totalPages);+ hasMore.set(moreAvailable);

73-90: Redundant mapping on line 79.

The .map((img) => img) creates a shallow copy of the array but doesn't transform elements. If the intent is to create a copy, use spread syntax for clarity.

🔎 Simplify the images array
 const response = await apiClient.post("/api/posts", {
text,
- images: images.map((img) => img),+ images: [...images],
});

Or if a copy isn't needed:

 const response = await apiClient.post("/api/posts", {
text,
- images: images.map((img) => img),+ images,
});
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db6ea53 and 077289f.

📒 Files selected for processing (2)
  • platforms/pictique/src/lib/stores/posts.ts
  • platforms/pictique/src/routes/(protected)/home/+page.svelte
🧰 Additional context used
🧬 Code graph analysis (1)
platforms/pictique/src/lib/stores/posts.ts (1)
platforms/pictique/src/lib/types.ts (1)
  • Post (35-58)
🔇 Additional comments (9)
platforms/pictique/src/routes/(protected)/home/+page.svelte (5)

7-23: LGTM! Clean import organization.

The new imports for pagination stores (hasMore, isLoadingMore, loadMoreFeed, resetFeed) and get from svelte/store are correctly added to support the infinite scroll functionality.


31-56: Well-implemented IntersectionObserver with proper cleanup.

The sentinel action correctly implements infinite scroll detection. Good use of rootMargin: '200px' for pre-fetching before the user reaches the bottom, and proper cleanup in destroy().

Minor consideration: Using get() inside the callback reads the store value synchronously at callback time, which is correct here since the observer callback runs outside the Svelte reactive context.


111-115: LGTM! Clean initialization.

Calling resetFeed() before fetchFeed() ensures a clean state on component mount, which is important when navigating back to this page.


137-207: Solid optimistic update pattern with proper rollback.

The implementation correctly:

  1. Captures state before modification for reliable rollback
  2. Performs optimistic UI update immediately
  3. Calls the API in background
  4. Reverts on error with the saved original state

One minor edge case: if profile becomes null between the guard check (line 138) and the API response, the rollback would still work correctly since originalLikedBy is captured early.


223-236: Nice loading spinner implementation.

The conditional rendering properly handles all states:

  • Loading more indicator with spinner
  • End-of-feed message
  • Sentinel for triggering more loads

The Tailwind classes are valid for v4.

platforms/pictique/src/lib/stores/posts.ts (4)

1-11: LGTM! Clean store declarations.

The new pagination stores (isLoadingMore, currentPage, hasMore) are properly typed with appropriate initial values.


16-20: LGTM! Simple and correct reset function.

Properly resets all pagination state to initial values.


61-71: LGTM! Proper guard against concurrent fetches.

The loadMoreFeed function correctly checks both isLoading and isLoadingMore to prevent duplicate requests, and respects the hasMore flag.


92-101: LGTM! Simple and focused.

The toggleLike function correctly delegates optimistic updates to the caller (the component), keeping this function as a simple API wrapper that throws on failure for the caller to handle rollback.

@coodos
coodos merged commit 07cf29a into mainJan 5, 2026
4 checks passed
@coodos
coodos deleted the feat/pictique-loading-spinner branch January 5, 2026 20:22
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@sosweetham@coodos