From 948bc83606463f7c5c614a93d5982afddc6925ac Mon Sep 17 00:00:00 2001 From: Amanat Ali Date: Sun, 16 Nov 2025 14:00:47 +0500 Subject: [PATCH 1/2] removed plans and added ruff cache in gitignore --- .gitignore | 3 +- plans/lazy_loading_plan.md | 149 +++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 plans/lazy_loading_plan.md diff --git a/.gitignore b/.gitignore index 4dc7ca6..15f987c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,4 @@ outputs/* tmp/* .pytest_cache/ - -plans/* \ No newline at end of file +.ruff_cache/ \ No newline at end of file diff --git a/plans/lazy_loading_plan.md b/plans/lazy_loading_plan.md new file mode 100644 index 0000000..02ef3dd --- /dev/null +++ b/plans/lazy_loading_plan.md @@ -0,0 +1,149 @@ +# Plan: Implement Lazy Loading for Blog Posts + +๐ŸŒŸ **Summary** + +Optimize application performance and improve user experience by implementing lazy loading (load-on-scroll) for blog posts on the homepage and other listing pages. + +๐Ÿงฉ **Problem or Motivation** + +As the number of blog posts grows, loading all posts at once on the homepage can lead to slower page load times and a degraded user experience. Implementing load-on-scroll will fetch posts only when they are needed, improving initial load performance and perceived speed. + +--- + +๐Ÿงฐ **Proposed Solution** + +## 1. Backend (Flask) + +### a. Paginated Post Retrieval + +The main `index` route in `app.py` will be modified to accept `page` and `per_page` query parameters. This will allow for paginated fetching of posts from the Supabase database. + +**Diagram: Backend Request Flow** + +```ascii ++-----------------+ +-------------------------+ +------------------------+ +| Client (Browser)|----->| Flask App (app.py) |----->| Supabase (PostgreSQL)| +| GET /?page=2 | | | | | ++-----------------+ | @app.route('/') | | SELECT * FROM posts | + | def index(): | | ORDER BY timestamp DESC| + | page = ... | | LIMIT 10 OFFSET 10; | + | per_page = ... | | | + | posts = db.query() | +-----------+------------+ + +-------------------------+ | + | | + +-------------------------+ | + | Rendered HTML |<-----------------+ + | (or JSON for API) | + +-------------------------+ + | ++-----------------+ +-------------------------+ +| Client (Browser)|<-----| Server Response | +| (Receives posts)| | | ++-----------------+ +-------------------------+ +``` + +### b. API Endpoint for AJAX Requests + +A new API endpoint, `/api/posts`, will be created to handle AJAX requests from the frontend. This endpoint will return a JSON object containing the posts for the requested page and information about whether more posts are available. + +**Example JSON Response from `/api/posts?page=2`:** + +```json +{ + "posts": [ + { + "id": 15, + "title": "Another Post", + "content_preview": "This is a short preview...", + "image": "/static/uploads/image.jpg", + "timestamp": "2025-11-15" + } + ], + "has_next": true +} +``` + +## 2. Frontend (JavaScript/Jinja2) + +### a. Scroll Detection + +JavaScript will be used to detect when the user scrolls near the bottom of the page. An event listener will trigger a function to load more posts. + +### b. AJAX Request and Dynamic Loading + +When the scroll threshold is reached, an AJAX `fetch` request will be sent to the `/api/posts` endpoint. A loading indicator will be shown to the user. Upon receiving the new posts, the JavaScript will render them as HTML elements and append them to the existing post list. + +**Diagram: Frontend Interaction Flow** + +```ascii ++----------------------+ +| User Scrolls | ++----------+-----------+ + | + v ++----------------------+ +| JS: Near bottom of | +| page? (Threshold) | ++----------+-----------+ + | Yes + v ++----------------------+ +| JS: Show Loading | +| Spinner | ++----------+-----------+ + | + v ++----------------------+ +----------------------+ +| JS: Fetch next page |----->| GET /api/posts?page=N | +| from API | +----------------------+ ++----------+-----------+ + | + v ++----------------------+ +----------------------+ +| JS: Receive JSON |<-----| Flask API Response | ++----------+-----------+ +----------------------+ + | + v ++----------------------+ +| JS: Hide Spinner | ++----------+-----------+ + | + v ++----------------------+ +| JS: Render new posts | +| & Append to DOM | ++----------+-----------+ + | + v ++----------------------+ +| JS: Increment page N | ++----------------------+ +``` + +### c. Graceful End of Content + +When the API response indicates `has_next: false`, the scroll listener will be disabled, and a message like "No more posts" will be displayed to the user. + +--- + +๐Ÿ“ฆ **Technical Considerations** + +- **Frontend:** Changes will be primarily in `static/js/script.js` and the main `templates/index.html` template. +- **Backend:** Changes will be in `app.py`. +- **Database:** The existing `posts` table schema is sufficient. The changes involve modifying the `SELECT` queries to use `LIMIT` and `OFFSET`. +- **UI/UX:** A subtle loading indicator (e.g., a spinner) will be added to provide feedback during post loading. The "No more posts" message should be clear but unobtrusive. + +--- + +๐Ÿง  **Alternatives** + +- **Traditional Pagination:** Implement "Next/Previous" buttons. This is simpler to implement but provides a less fluid user experience compared to infinite scrolling. +- **"Load More" Button:** A button at the bottom of the list that the user clicks to load more posts. This is a good compromise between pagination and infinite scroll, giving the user more control. + +For this project, the load-on-scroll approach is preferred as it aligns with a modern, seamless user experience. + +--- + +๐Ÿงพ **Additional Context** + +This feature is a key scalability improvement. It ensures the application remains fast and responsive, regardless of the number of posts in the database, which is crucial for long-term content growth. From aaa37e6f347e59edaa3053d9659e468b26ace6ca Mon Sep 17 00:00:00 2001 From: Amanat Ali Date: Sun, 16 Nov 2025 14:25:55 +0500 Subject: [PATCH 2/2] made a plan for the issue #26 --- plans/lazy_loading_plan.md | 328 +++++++++++++++++++++++-------------- 1 file changed, 203 insertions(+), 125 deletions(-) diff --git a/plans/lazy_loading_plan.md b/plans/lazy_loading_plan.md index 02ef3dd..8392058 100644 --- a/plans/lazy_loading_plan.md +++ b/plans/lazy_loading_plan.md @@ -1,149 +1,227 @@ -# Plan: Implement Lazy Loading for Blog Posts +# Technical Plan: Implement Lazy Loading for Blog Posts ๐ŸŒŸ **Summary** -Optimize application performance and improve user experience by implementing lazy loading (load-on-scroll) for blog posts on the homepage and other listing pages. +Optimize application performance by implementing server-side pagination and a client-side load-on-scroll mechanism for the blog post list. -๐Ÿงฉ **Problem or Motivation** +--- -As the number of blog posts grows, loading all posts at once on the homepage can lead to slower page load times and a degraded user experience. Implementing load-on-scroll will fetch posts only when they are needed, improving initial load performance and perceived speed. +๐Ÿ“Š **High-Level Flow Diagrams** ---- +### Backend Request Flow -๐Ÿงฐ **Proposed Solution** - -## 1. Backend (Flask) - -### a. Paginated Post Retrieval - -The main `index` route in `app.py` will be modified to accept `page` and `per_page` query parameters. This will allow for paginated fetching of posts from the Supabase database. - -**Diagram: Backend Request Flow** - -```ascii -+-----------------+ +-------------------------+ +------------------------+ -| Client (Browser)|----->| Flask App (app.py) |----->| Supabase (PostgreSQL)| -| GET /?page=2 | | | | | -+-----------------+ | @app.route('/') | | SELECT * FROM posts | - | def index(): | | ORDER BY timestamp DESC| - | page = ... | | LIMIT 10 OFFSET 10; | - | per_page = ... | | | - | posts = db.query() | +-----------+------------+ - +-------------------------+ | - | | - +-------------------------+ | - | Rendered HTML |<-----------------+ - | (or JSON for API) | - +-------------------------+ - | -+-----------------+ +-------------------------+ -| Client (Browser)|<-----| Server Response | -| (Receives posts)| | | -+-----------------+ +-------------------------+ +```mermaid +graph TD + A["Client (Browser)"] -->|GET /api/posts?page=N| B(Flask App); + B -->|Query posts with LIMIT/OFFSET| C[Supabase PostgreSQL]; + C -->|Paginated Posts Data| B; + B -->|JSON Response| A; ``` -### b. API Endpoint for AJAX Requests - -A new API endpoint, `/api/posts`, will be created to handle AJAX requests from the frontend. This endpoint will return a JSON object containing the posts for the requested page and information about whether more posts are available. - -**Example JSON Response from `/api/posts?page=2`:** - -```json -{ - "posts": [ - { - "id": 15, - "title": "Another Post", - "content_preview": "This is a short preview...", - "image": "/static/uploads/image.jpg", - "timestamp": "2025-11-15" - } - ], - "has_next": true -} +### Frontend Interaction Flow + +```mermaid +graph TD + A[User Scrolls] --> B{"JS: Near bottom of page?"}; + B -- Yes --> C[JS: Show Loading Spinner]; + C --> D[JS: Fetch next page from API]; + D --> E[GET /api/posts?page=N]; + E --> F[Flask API Response]; + F --> G[JS: Receive JSON]; + G --> H[JS: Hide Spinner]; + H --> I[JS: Render new posts & Append to DOM]; + I --> J[JS: Increment page number]; ``` -## 2. Frontend (JavaScript/Jinja2) - -### a. Scroll Detection - -JavaScript will be used to detect when the user scrolls near the bottom of the page. An event listener will trigger a function to load more posts. - -### b. AJAX Request and Dynamic Loading - -When the scroll threshold is reached, an AJAX `fetch` request will be sent to the `/api/posts` endpoint. A loading indicator will be shown to the user. Upon receiving the new posts, the JavaScript will render them as HTML elements and append them to the existing post list. - -**Diagram: Frontend Interaction Flow** - -```ascii -+----------------------+ -| User Scrolls | -+----------+-----------+ - | - v -+----------------------+ -| JS: Near bottom of | -| page? (Threshold) | -+----------+-----------+ - | Yes - v -+----------------------+ -| JS: Show Loading | -| Spinner | -+----------+-----------+ - | - v -+----------------------+ +----------------------+ -| JS: Fetch next page |----->| GET /api/posts?page=N | -| from API | +----------------------+ -+----------+-----------+ - | - v -+----------------------+ +----------------------+ -| JS: Receive JSON |<-----| Flask API Response | -+----------+-----------+ +----------------------+ - | - v -+----------------------+ -| JS: Hide Spinner | -+----------+-----------+ - | - v -+----------------------+ -| JS: Render new posts | -| & Append to DOM | -+----------+-----------+ - | - v -+----------------------+ -| JS: Increment page N | -+----------------------+ +--- + +๐Ÿงฐ **Implementation Details** + +## 1. Backend (`app.py`) + +### a. Modify Index Route for Initial Page Load + +The existing `index` route will be updated to fetch only the first page of posts. This reduces the initial data load. + +**File:** `app.py` + +```python +# app.py + +POSTS_PER_PAGE = 10 + +@app.route('/') +def index(): + # Fetch only the first page for the initial load + response = supabase.table('posts').select('*').order('timestamp', desc=True).limit(POSTS_PER_PAGE).execute() + posts = response.data + + # We can pass the initial page number and total posts if needed for the frontend + # For a simple infinite scroll, we just need the first batch of posts. + return render_template('index.html', posts=posts) ``` -### c. Graceful End of Content +### b. Create API Endpoint for Paginated Loading -When the API response indicates `has_next: false`, the scroll listener will be disabled, and a message like "No more posts" will be displayed to the user. +A new route, `/api/posts`, will be created to serve subsequent pages of posts as JSON. This endpoint will accept a `page` query parameter. ---- +**File:** `app.py` -๐Ÿ“ฆ **Technical Considerations** +```python +# app.py -- **Frontend:** Changes will be primarily in `static/js/script.js` and the main `templates/index.html` template. -- **Backend:** Changes will be in `app.py`. -- **Database:** The existing `posts` table schema is sufficient. The changes involve modifying the `SELECT` queries to use `LIMIT` and `OFFSET`. -- **UI/UX:** A subtle loading indicator (e.g., a spinner) will be added to provide feedback during post loading. The "No more posts" message should be clear but unobtrusive. +from flask import jsonify, request ---- +# ... (other imports and code) -๐Ÿง  **Alternatives** +POSTS_PER_PAGE = 10 -- **Traditional Pagination:** Implement "Next/Previous" buttons. This is simpler to implement but provides a less fluid user experience compared to infinite scrolling. -- **"Load More" Button:** A button at the bottom of the list that the user clicks to load more posts. This is a good compromise between pagination and infinite scroll, giving the user more control. +# ... (index route) ... -For this project, the load-on-scroll approach is preferred as it aligns with a modern, seamless user experience. +@app.route('/api/posts') +def api_posts(): + try: + page = int(request.args.get('page', 1)) + except (TypeError, ValueError): + page = 1 + + offset = (page - 1) * POSTS_PER_PAGE + + # Fetch posts for the requested page + posts_response = supabase.table('posts').select('*').order('timestamp', desc=True).limit(POSTS_PER_PAGE).offset(offset).execute() + posts_data = posts_response.data + + # To determine if there's a next page, we can try to fetch one more item than required + # A more efficient way is to get a total count once, but this avoids a separate COUNT query. + has_next_response = supabase.table('posts').select('id').limit(1).offset(offset + POSTS_PER_PAGE).execute() + has_next = bool(has_next_response.data) ---- + return jsonify({ + 'posts': posts_data, + 'has_next': has_next + }) -๐Ÿงพ **Additional Context** +``` + +## 2. Frontend (`index.html` and `script.js`) + +### a. Update HTML Structure + +The `index.html` template needs a container for the posts with a unique ID for easy selection by JavaScript. We also need elements to act as a loading indicator and an end-of-posts message. + +**File:** `templates/index.html` + +```html + + +... +
+ {% for post in posts %} +
+ +

{{ post.title }}

+ ... +
+ {% endfor %} +
+ + + + +... +``` + +### b. Implement Client-Side JavaScript Logic + +A script will be added to handle the scroll events, fetch data from the `/api/posts` endpoint, and inject the new posts into the DOM. + +**File:** `static/js/script.js` + +```javascript +// static/js/script.js + +document.addEventListener('DOMContentLoaded', () => { + const postsContainer = document.getElementById('posts-container'); + const loadingIndicator = document.getElementById('loading-indicator'); + const endOfPostsMessage = document.getElementById('end-of-posts-message'); + + if (!postsContainer) return; + + let page = 2; // Start with page 2, since page 1 is loaded initially + let isLoading = false; + let hasNext = true; + + const loadMorePosts = async () => { + if (isLoading || !hasNext) return; + + isLoading = true; + loadingIndicator.style.display = 'block'; + + try { + const response = await fetch(`/api/posts?page=${page}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + + if (data.posts && data.posts.length > 0) { + data.posts.forEach(post => { + const postElement = document.createElement('div'); + postElement.classList.add('post'); + // This HTML structure must match the one in index.html + postElement.innerHTML = ` +

${post.title}

+ + ${post.image ? `Post image` : ''} +
${post.content.substring(0, 200)}...
+ `; + postsContainer.appendChild(postElement); + }); + page++; + } + + hasNext = data.has_next; + if (!hasNext) { + endOfPostsMessage.style.display = 'block'; + } + + } catch (error) { + console.error("Failed to load more posts:", error); + // Optionally, display an error message to the user + } finally { + isLoading = false; + loadingIndicator.style.display = 'none'; + } + }; + + window.addEventListener('scroll', () => { + // Load more posts when the user is 100px from the bottom + if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 100) { + loadMorePosts(); + } + }); +}); +``` + +--- -This feature is a key scalability improvement. It ensures the application remains fast and responsive, regardless of the number of posts in the database, which is crucial for long-term content growth. +๐Ÿ“ฆ **Technical Summary** + +- **`app.py`**: + - Modify `index()` to fetch only the first page of posts (`LIMIT 10`). + - Add a new route `/api/posts` that accepts a `page` parameter. + - In `/api/posts`, use `limit` and `offset` in the Supabase query to fetch the correct slice of posts. + - The API will return JSON with `posts` and a `has_next` boolean. +- **`templates/index.html`**: + - Add `id="posts-container"` to the main posts `div`. + - Add `div` elements for `id="loading-indicator"` and `id="end-of-posts-message"`. +- **`static/js/script.js`**: + - Add a `scroll` event listener. + - Implement `loadMorePosts` function to `fetch` from `/api/posts`. + - Dynamically create post HTML elements from the JSON response and append them to the `#posts-container`. + - Manage state variables `page`, `isLoading`, and `hasNext` to control fetching. + - Show/hide the loading and end-of-posts indicators.