## AI Master Prompt: AdLess Reader MVP
**1. PROJECT OVERVIEW:**
AdLess Reader is a modern, highly efficient RSS feed aggregator designed to combat the overwhelming issue of intrusive ads, pop-ups, and bloated web pages prevalent on many online content sites. Users are tired of navigating through distracting elements just to access articles. AdLess Reader solves this by providing a clean, minimalist interface that fetches and displays article content in its purest form, stripping away all advertisements and unnecessary scripts. Our core value proposition is to deliver a fast, focused, and frustration-free reading experience, allowing users to consume information efficiently and enjoyably.
**2. TECH STACK:**
* **Framework:** Next.js (App Router)
* **Language:** TypeScript
* **Styling:** Tailwind CSS (v3+)
* **UI Components:** shadcn/ui (for accessible, reusable components)
* **ORM:** Drizzle ORM (with PostgreSQL via Vercel Postgres or Supabase)
* **Database:** PostgreSQL
* **Authentication:** NextAuth.js (using Email, Google, and GitHub providers)
* **State Management:** React Context API and Server Actions for data fetching/mutations.
* **RSS Parsing:** `react-rss-parser` or similar robust library.
* **Web Scraping (for content extraction):** `cheerio` (on the backend/API routes).
* **Deployment:** Vercel
* **Form Handling:** `react-hook-form` & `zod` for validation.
**3. DATABASE SCHEMA (Drizzle ORM - PostgreSQL):**
```typescript
// schema.ts
import { pgTable, uuid, text, timestamp, boolean, foreignKey, integer }
from 'drizzle-orm/pg-core';
import { sql } from 'drizzle-orm';
export const users = pgTable('users', {
id: uuid('id').primaryKey().default(sql`gen_random_uuid()`),
name: text('name'),
email: text('email').notNull().unique(),
emailVerified: timestamp('emailVerified', { mode: 'date' }),
image: text('image'),
createdAt: timestamp('createdAt').default(sql`now()`),
updatedAt: timestamp('updatedAt').default(sql`now()`)
});
export const accounts = pgTable(
'accounts',
{
userId: uuid('userId').notNull().references(() => users.id, { onDelete: 'cascade' }),
type: text('type').$type<AdapterAccount['type']>().notNull(),
provider: text('provider').notNull(),
providerAccountId: text('providerAccountId').notNull(),
refresh_token: text('refresh_token'),
access_token: text('access_token'),
expires_at: integer('expires_at'),
token_type: text('token_type'),
scope: text('scope'),
id_token: text('id_token'),
session_state: text('session_state'),
},
(account) => ({
compoundKey: [account.provider, account.providerAccountId],
})
);
export const sessions = pgTable('sessions', {
sessionToken: text('sessionToken').primaryKey(),
userId: uuid('userId').notNull().references(() => users.id, { onDelete: 'cascade' }),
expires: timestamp('expires', { mode: 'date' }).notNull(),
});
export const verificationTokens = pgTable(
'verificationTokens',
{
identifier: text('identifier').notNull(),
token: text('token').notNull(),
expires: timestamp('expires', { mode: 'date' }).notNull(),
},
(vt) => ({
compoundKey: [vt.identifier, vt.token],
})
);
export const feeds = pgTable('feeds', {
id: uuid('id').primaryKey().default(sql`gen_random_uuid()`),
userId: uuid('userId').notNull().references(() => users.id, { onDelete: 'cascade' }),
url: text('url').notNull().unique(), // Store the original RSS feed URL
title: text('title'), // Parsed title from feed, can be null initially
createdAt: timestamp('createdAt').default(sql`now()`),
updatedAt: timestamp('updatedAt').default(sql`now()`)
});
export const articles = pgTable('articles', {
id: uuid('id').primaryKey().default(sql`gen_random_uuid()`),
feedId: uuid('feedId').notNull().references(() => feeds.id, { onDelete: 'cascade' }),
url: text('url').notNull(), // Original URL of the article
title: text('title').notNull(),
content: text('content'), // Extracted clean content
author: text('author'),
publishedAt: timestamp('publishedAt', { mode: 'date' }),
read: boolean('read').default(false),
favorite: boolean('favorite').default(false),
createdAt: timestamp('createdAt').default(sql`now()`),
updatedAt: timestamp('updatedAt').default(sql`now()`)
});
// Relations (example for Drizzle queries)
export const feedRelations = {
articles: articles.feedId,
};
export const userRelations = {
feeds: feeds.userId,
};
```
**4. CORE FEATURES & USER FLOW:**
* **Authentication (NextAuth.js):
* **Flow:** User lands on the homepage. Clicks 'Sign In'. Presented with options: Email/Password, Google, GitHub. Upon successful auth via chosen provider, user is redirected to their personalized dashboard. Session is managed via cookies.
* **Edge Cases:** Invalid credentials, provider errors, email verification (if implementing email/password strictly).
* **Add RSS Feed:**
* **Flow:** Authenticated user navigates to 'Manage Feeds' page. Clicks 'Add Feed'. A modal or inline form appears prompting for the RSS feed URL. User submits the URL. Backend validates the URL, attempts to fetch and parse the feed. If successful, the feed URL and its parsed title are saved to the `feeds` table linked to the user. A background job/cron is initiated to periodically fetch and parse new articles for this feed.
* **Edge Cases:** Invalid URL format, non-existent feed, feed requires authentication, feed parsing errors, duplicate feed URL for the same user.
* **View Articles (Dashboard):
* **Flow:** Authenticated user lands on the dashboard. The page fetches a list of recent articles from all subscribed feeds for the logged-in user (from the `articles` table, ordered by `publishedAt` or `createdAt` descending). Articles are displayed in a list format (title, feed name, publication date). Clicking an article title opens the detailed view.
* **Read/Unread & Favorite:** In the list view, each article has icons/buttons to mark as read/unread or add/remove from favorites. In the detail view, similar actions are available. These actions update the `read` and `favorite` boolean fields in the `articles` table.
* **Edge Cases:** No feeds subscribed (empty state), no articles fetched yet (loading state/empty state), feed data unavailable.
* **Article Detail View:**
* **Flow:** User clicks an article title from the dashboard list. A new page/modal loads displaying the article. The backend uses the article's `url` to fetch the original webpage, then `cheerio` extracts the main content, stripping ads, scripts, and excessive HTML. The clean content is displayed in a readable format.
* **Edge Cases:** Original article URL is broken/dead, content extraction fails, dynamically loaded content issues, very large articles.
* **Manage Feeds:**
* **Flow:** User navigates to 'Manage Feeds'. A list of their currently subscribed feeds is displayed. Each feed item has options to delete or edit (e.g., rename). Clicking delete prompts for confirmation. Clicking edit allows changing the feed's display title. Deleting a feed removes it from the `feeds` table and potentially triggers a cleanup for associated articles (or keeps them, depending on desired behavior).
* **Edge Cases:** User tries to delete a non-existent feed (UI should prevent this), confirmation logic.
**5. API & DATA FETCHING:**
* **App Router Approach:** Utilize Server Components for initial data loading and Server Actions for mutations (adding/deleting feeds, marking read/favorite).
* **API Routes (e.g., `app/api/fetch-feed` - illustrative, likely handled by Server Actions/Components):**
* `POST /api/fetch-feed`: Accepts a feed URL, fetches and parses RSS, returns feed metadata and recent articles (primarily for initial feed addition validation).
* `POST /api/scrape-article`: Accepts an article URL, scrapes and cleans the content, returns the clean HTML/text.
* **Data Fetching Strategy:**
* Dashboard (`page.tsx`): Fetch list of articles using `getServerSideProps` or direct Server Component queries based on `session.user.id`.
* Article Detail (`/article/[id]` or similar): Fetch article details (including scraped content if not pre-fetched) from the `articles` table using Server Component queries.
* Mutations (Add Feed, Mark Read, etc.): Use Server Actions triggered from client components, passing necessary data (like feed URL, article ID) and performing DB operations.
* **Data Flow:** Client Components will call Server Actions. Server Actions interact with Drizzle ORM to perform database operations. Server Components can directly query the database.
* **Example Server Action (`addFeed`):**
```typescript
'use server';
import { db } from '@/lib/db'; // Your Drizzle DB instance
import { feeds } from '@/db/schema'; // Your schema
import { auth } from '@/auth'; // NextAuth auth instance
import { eq } from 'drizzle-orm';
// ... other imports (rss parser, content extractor)
export async function addFeed(formData: FormData) {
const session = await auth();
if (!session?.user?.id) {
throw new Error('Unauthorized');
}
const feedUrl = formData.get('url') as string;
// 1. Validate URL format
// 2. Fetch and parse RSS feed (e.g., using 'rss-parser')
// try {
// const feed = await parser.parseURL(feedUrl);
// const feedTitle = feed.title;
// const articlesData = feed.items.map(item => ({ ... })); // Prepare initial articles
//
// // 3. Save feed to DB
// await db.insert(feeds).values({ userId: session.user.id, url: feedUrl, title: feedTitle });
// // 4. Save initial articles (handle scraping here or via background job)
// // ... save articles ...
// } catch (error) {
// console.error('Failed to add feed:', error);
// throw new Error('Could not add or parse feed.');
// }
// ... actual implementation ...
return { success: true };
}
```
**6. COMPONENT BREAKDOWN (Next.js App Router):**
* **`app/layout.tsx`:** Root layout, includes `<html>`, `<body>`, global providers (e.g., ThemeProvider), Tailwind CSS setup, base `shadcn/ui` configuration.
* **`app/page.tsx`:** Landing Page (Public). Brief intro, value proposition, call to action (Sign Up/Sign In).
* **`app/dashboard/page.tsx`:** (Protected Route) User's main dashboard. Lists recent articles from subscribed feeds. Includes:
* `components/ArticleList`: Renders the list of articles.
* `components/ArticleListItem`: Individual article item in the list (title, date, feed name, read/favorite buttons).
* `components/FeedStatusIndicator`: Shows if feeds are up-to-date or loading.
* `components/EmptyState`: Displayed when no feeds are added or no articles are available.
* **`app/(auth)/sign-in/page.tsx`:** Sign-in page. Uses `components/SignInForm` (integrates with NextAuth.js). Includes OAuth buttons.
* **`app/(auth)/layout.tsx`:** Layout for auth pages (sign-in, sign-up).
* **`app/manage-feeds/page.tsx`:** (Protected Route) Page to add, view, and delete RSS feeds. Includes:
* `components/FeedList`: Displays currently subscribed feeds.
* `components/FeedListItem`: Renders individual feed item with delete/edit options.
* `components/AddFeedForm`: Modal or inline form to input new feed URL.
* **`app/article/[articleId]/page.tsx`:** (Protected Route) Displays the full, clean content of a single article. Fetches article data and potentially scrapes content on demand.
* `components/ArticleContent`: Renders the extracted text/HTML.
* `components/ArticleHeader`: Displays article title, feed, author, date.
* `components/ArticleActions`: Buttons for Mark as Read, Favorite, Go to Original.
* **`app/settings/page.tsx`:** (Protected Route) User settings page (e.g., profile management, potentially future subscription management).
* **`components/ui/`:** All `shadcn/ui` components used throughout the app (Button, Input, Card, Dialog, DropdownMenu, etc.).
* **`components/Layout.tsx`:** Main application shell including Header/Navbar and potentially Sidebar.
* **`components/Navbar.tsx`:** Top navigation bar with logo, links, and user auth status/menu.
* **`components/ThemeProvider.tsx`:** Handles theme toggling (e.g., light/dark mode) using Next Themes.
**7. UI/UX DESIGN & VISUAL IDENTITY:**
* **Design Style:** Minimalist Clean with a touch of Modern UI. Focus on readability and reducing cognitive load.
* **Color Palette:**
* **Primary:** A calming, deep blue (`#1E3A8A` - Slate 800) or dark gray (`#1F2937` - Gray 900) for backgrounds in dark mode.
* **Secondary:** Lighter shades for cards and content areas (e.g., `#F9FAFB` - Gray 50 for light mode, `#2D3748` - Gray 700 for dark mode).
* **Accent:** A subtle but clear accent color for calls to action, links, and active states (e.g., a muted teal `#06B6D4` or a soft cyan `#3B82F6`).
* **Text:** Dark gray/black for light mode (`#111827`), off-white for dark mode (`#F3F4F6`).
* **Typography:** Use a highly readable sans-serif font family like Inter or Poppins. Establish clear hierarchy with distinct font sizes and weights for headings, body text, and metadata.
* Headings: `font-bold`, `font-semibold` (e.g., 2rem, 1.5rem)
* Body: `font-regular` (e.g., 1.1rem)
* Metadata (dates, feed names): `font-medium`, smaller size (e.g., 0.9rem), slightly muted color.
* **Section Layout:** Consistent padding and margins. Use cards for distinct content blocks (e.g., individual articles in a list). Ensure ample whitespace.
* **Responsiveness:** Mobile-first approach. Utilize Tailwind's responsive modifiers (`sm:`, `md:`, `lg:`) to ensure layout adapts seamlessly to all screen sizes. Navigation should collapse into a burger menu on smaller screens.
**8. SAMPLE/MOCK DATA:**
* **Feed (`feeds` table):
* `{ id: 'uuid1', userId: 'user1', url: 'https://example.com/tech/rss.xml', title: 'Tech Innovations Daily', createdAt: '...', updatedAt: '...'}`
* `{ id: 'uuid2', userId: 'user1', url: 'https://example.com/science/feed', title: 'Science Today', createdAt: '...', updatedAt: '...'}`
* **Article (`articles` table):
* `{ id: 'art1', feedId: 'uuid1', url: 'https://example.com/tech/article1', title: 'New Quantum Chip Announced', content: '<p>Researchers have unveiled a groundbreaking quantum computing chip...</p>', author: 'Jane Doe', publishedAt: '2023-10-27T10:00:00Z', read: false, favorite: false, createdAt: '...', updatedAt: '...'}`
* `{ id: 'art2', feedId: 'uuid1', url: 'https://example.com/tech/article2', title: 'AI Ethics in the Next Decade', content: '<p>Experts discuss the evolving landscape of AI ethics...</p>', author: 'John Smith', publishedAt: '2023-10-26T15:30:00Z', read: true, favorite: false, createdAt: '...', updatedAt: '...'}`
* `{ id: 'art3', feedId: 'uuid2', url: 'https://example.com/science/articleA', title: 'Mars Rover Discoveries', content: '<p>The latest data from the Perseverance rover suggests...</p>', author: 'NASA', publishedAt: '2023-10-27T08:00:00Z', read: false, favorite: true, createdAt: '...', updatedAt: '...'}`
* `{ id: 'art4', feedId: 'uuid2', url: 'https://example.com/science/articleB', title: 'New Species Found in Deep Sea', content: '<p>An expedition has identified several previously unknown deep-sea organisms...</p>', author: 'Oceanographic Institute', publishedAt: '2023-10-25T11:00:00Z', read: false, favorite: false, createdAt: '...', updatedAt: '...'}`
* `{ id: 'art5', feedId: 'uuid1', url: 'https://example.com/tech/article3', title: 'Future of Renewable Energy', content: '<p>Solar and wind power continue to dominate...</p>', author: 'Jane Doe', publishedAt: '2023-10-27T09:15:00Z', read: false, favorite: false, createdAt: '...', updatedAt: '...'}`
**9. TURKISH TRANSLATIONS (Key UI Elements):**
* **App Title:** Reklamsız Okuyucu
* **Sign In:** Giriş Yap
* **Sign Out:** Çıkış Yap
* **Dashboard:** Pano
* **Manage Feeds:** Kaynakları Yönet
* **Add Feed:** Kaynak Ekle
* **URL:** URL
* **Title:** Başlık
* **Article:** Makale
* **Articles:** Makaleler
* **Feed:** Kaynak
* **Feeds:** Kaynaklar
* **All Feeds:** Tüm Kaynaklar
* **Newest:** En Yeni
* **Mark as Read:** Okundu İşaretle
* **Mark as Unread:** Okunmadı İşaretle
* **Favorite:** Favoriye Ekle
* **Unfavorite:** Favoriden Çıkar
* **Settings:** Ayarlar
* **Save:** Kaydet
* **Delete:** Sil
* **Confirm Delete:** Silmeyi Onayla
* **Are you sure?** Emin misiniz?
* **Loading...:** Yükleniyor...
* **No feeds yet.** Henüz kaynak yok.
* **Add your first feed to get started.** Başlamak için ilk kaynağınızı ekleyin.
* **Error fetching feed.** Kaynak getirilirken hata oluştu.
* **Invalid URL.** Geçersiz URL.
* **Published:** Yayınlanma
* **By:** Yazan
* **Go to Original:** Orijinaline Git
**10. ANIMATIONS:**
* **Page Transitions:** Subtle fade-in/out using Next.js's built-in support or a library like `Framer Motion` for smoother transitions between pages.
* **Button Hovers:** Slight scale-up or background color change on hover states.
* **Loading Indicators:** Use spinners (`shadcn/ui` or custom SVG) for data fetching operations. Skeleton loaders (`<ContentLoader>` style) for list items while data is being fetched.
* **Hover Effects on Article List:** Slight background highlight or shadow lift on `ArticleListItem` on hover.
* **Smooth Scrolling:** For internal page anchors if needed.
**11. EDGE CASES & VALIDATIONS:**
* **Authentication:** Handle unauthenticated access gracefully (redirect to sign-in). Display appropriate UI for logged-in vs. logged-out states.
* **Feed Validation:** Robust validation of provided RSS feed URLs (format, accessibility, valid XML/RSS structure). Provide clear error messages to the user.
* **Content Extraction:** Implement fallback mechanisms or error handling if `cheerio` fails to extract content from a specific URL. Display a message indicating content could not be retrieved cleanly.
* **Empty States:** Design and implement clear empty states for the dashboard (no feeds added) and feed management (no feeds subscribed).
* **Error Handling:** Use try-catch blocks extensively in Server Actions and API routes. Display user-friendly error messages (e.g., using toast notifications) without revealing sensitive backend information.
* **Rate Limiting:** Consider implementing rate limiting on feed fetching to avoid overwhelming external servers and to manage backend load, especially if scaling significantly.
* **Database Constraints:** Ensure unique constraints (like `feed.url` per `userId`) are correctly implemented and handled.
* **Network Errors:** Handle potential network failures during feed fetching or article scraping.
This prompt provides a comprehensive blueprint for building the AdLess Reader MVP. Ensure all parts are addressed to generate a functional, well-designed application.