diff --git a/backend/igny8_core/api/pagination.py b/backend/igny8_core/api/pagination.py
index c471a27c..ead790dc 100644
--- a/backend/igny8_core/api/pagination.py
+++ b/backend/igny8_core/api/pagination.py
@@ -47,3 +47,19 @@ class CustomPageNumberPagination(PageNumberPagination):
return Response(response_data)
+
+class LargeTablePagination(CustomPageNumberPagination):
+ """
+ Pagination class for large reference tables (e.g., SeedKeywords).
+
+ Default page size: 25
+ Max page size: 500
+
+ Important: Server-side sorting/filtering is applied to ALL records first,
+ then only the requested page is returned. This ensures:
+ - Sorting by volume returns the 500 highest/lowest volume records globally
+ - Filters apply to all records, not just the visible page
+ - Pagination shows accurate total counts
+ """
+ page_size = 25
+ max_page_size = 500
diff --git a/backend/igny8_core/auth/views.py b/backend/igny8_core/auth/views.py
index 4fb13e44..a46c2c64 100644
--- a/backend/igny8_core/auth/views.py
+++ b/backend/igny8_core/auth/views.py
@@ -15,7 +15,7 @@ from igny8_core.api.base import AccountModelViewSet
from igny8_core.api.authentication import JWTAuthentication, CSRFExemptSessionAuthentication
from igny8_core.api.response import success_response, error_response
from igny8_core.api.throttles import DebugScopedRateThrottle
-from igny8_core.api.pagination import CustomPageNumberPagination
+from igny8_core.api.pagination import CustomPageNumberPagination, LargeTablePagination
from igny8_core.api.permissions import IsAuthenticatedAndActive, HasTenantAccess
from .models import User, Account, Plan, Subscription, Site, Sector, SiteUserAccess, Industry, IndustrySector, SeedKeyword
from .serializers import (
@@ -827,11 +827,15 @@ class SeedKeywordViewSet(viewsets.ReadOnlyModelViewSet):
"""
ViewSet for SeedKeyword - Global reference data (read-only for non-admins).
Unified API Standard v1.0 compliant
+
+ Sorting and filtering is applied server-side to ALL records, then paginated.
+ This ensures operations like "sort by volume DESC" return the globally highest
+ volume keywords, not just the highest within the current page.
"""
queryset = SeedKeyword.objects.filter(is_active=True).select_related('industry', 'sector')
serializer_class = SeedKeywordSerializer
permission_classes = [permissions.AllowAny] # Read-only, allow any authenticated user
- pagination_class = CustomPageNumberPagination
+ pagination_class = LargeTablePagination # Supports up to 500 records per page
throttle_scope = 'auth'
throttle_classes = [DebugScopedRateThrottle]
@@ -855,12 +859,14 @@ class SeedKeywordViewSet(viewsets.ReadOnlyModelViewSet):
)
def get_queryset(self):
- """Filter by industry and sector if provided."""
+ """Filter by industry, sector, and difficulty range if provided."""
queryset = super().get_queryset()
industry_id = self.request.query_params.get('industry_id')
industry_name = self.request.query_params.get('industry_name')
sector_id = self.request.query_params.get('sector_id')
sector_name = self.request.query_params.get('sector_name')
+ difficulty_min = self.request.query_params.get('difficulty_min')
+ difficulty_max = self.request.query_params.get('difficulty_max')
if industry_id:
queryset = queryset.filter(industry_id=industry_id)
@@ -871,6 +877,18 @@ class SeedKeywordViewSet(viewsets.ReadOnlyModelViewSet):
if sector_name:
queryset = queryset.filter(sector__name__icontains=sector_name)
+ # Difficulty range filtering
+ if difficulty_min is not None:
+ try:
+ queryset = queryset.filter(difficulty__gte=int(difficulty_min))
+ except (ValueError, TypeError):
+ pass
+ if difficulty_max is not None:
+ try:
+ queryset = queryset.filter(difficulty__lte=int(difficulty_max))
+ except (ValueError, TypeError):
+ pass
+
return queryset
@action(detail=False, methods=['get'], url_path='stats', url_name='stats')
diff --git a/docs/plans/KEYWORDS-LIBRARY-REDESIGN-PLAN.md b/docs/plans/KEYWORDS-LIBRARY-REDESIGN-PLAN.md
new file mode 100644
index 00000000..406cef3e
--- /dev/null
+++ b/docs/plans/KEYWORDS-LIBRARY-REDESIGN-PLAN.md
@@ -0,0 +1,850 @@
+# Keywords Library Page Redesign Plan
+
+**Created:** January 18, 2026
+**Updated:** January 18, 2026
+**Status:** APPROVED - READY FOR IMPLEMENTATION
+**Page:** `/setup/add-keywords` â `/keywords-library`
+**Priority:** đĄ HIGH - UX Improvement
+
+---
+
+## Executive Summary
+
+Comprehensive redesign of the Keywords Library page to:
+1. **Standardize naming** to "Keywords Library" across frontend, backend, URLs, menus, and admin
+2. **Implement cascading filters** like Planner pages (Keywords, Clusters, Ideas)
+3. **Remove sector selector** from AppHeader, use site-only selector
+4. **Add Sector Metric Cards** with clickable filtering + 5-6 bulk add stat options
+5. **Redesign Smart Suggestions** to appear after search/filter with breathing indicator
+6. **Center filter bar** with sector filter added, matching Planner page styling
+7. **Show table data by default** (remove "Browse" toggle)
+8. **No backward compatibility** - single source of truth
+
+---
+
+## Part 1: Terminology Standardization
+
+### Current State (Inconsistent)
+
+| Location | Current Term | Usage |
+|----------|--------------|-------|
+| Sidebar | "Keyword Library" | â Should be "Keywords Library" |
+| Page Title | "Keyword Library" | â Should be "Keywords Library" |
+| URL | `/setup/add-keywords` | â Legacy |
+| Backend Model | `SeedKeyword` | â
Keep internal |
+| Backend URL | `/v1/auth/seed-keywords/` | â Legacy |
+| Backend Admin | "Industry Sector Keywords" | â Should be "Keywords Library" |
+| Frontend API | `fetchSeedKeywords()` | â Legacy |
+| High Opportunity | "Quick-Start Keywords" | â Different term |
+| Search Modal | Mixed terms | â Inconsistent |
+| Onboarding | "Add Keywords" | â Legacy |
+| Marketing Pages | "Seed Keywords", "High-Opportunity" | â Mixed |
+
+### Target State (Unified)
+
+**Standardized Term:** "Keywords Library" (plural "Keywords")
+**Internal/Code:** Keep `SeedKeyword` model name internally
+
+| Location | New Term |
+|----------|----------|
+| Sidebar | "Keywords Library" |
+| Page Title | "Keywords Library" |
+| URL | `/keywords-library` (**NO** redirect from legacy) |
+| Backend URL | `/v1/keywords-library/` (**NO** legacy alias) |
+| Backend Admin | "Keywords Library" |
+| Frontend API | `fetchKeywordsLibrary()` (replace `fetchSeedKeywords`) |
+| Smart Suggestions | "Smart Suggestions" |
+| Search Modal | "Keywords Library" |
+| Onboarding Step | "Browse Keywords Library" |
+
+### Files to Update
+
+```
+Frontend:
+- src/layout/AppSidebar.tsx - Menu label â "Keywords Library"
+- src/layout/AppHeader.tsx - Route patterns
+- src/App.tsx - Route definition (NO redirect, direct change)
+- src/components/common/SearchModal.tsx - Search references
+- src/components/onboarding/OnboardingWizard.tsx - Step 4 name
+- src/components/onboarding/steps/Step4AddKeywords.tsx - Rename to Step4KeywordsLibrary.tsx
+- src/components/sites/SiteSetupChecklist.tsx - Link text
+- src/services/api.ts - Rename function to fetchKeywordsLibrary()
+- src/marketing/pages/Pricing.tsx - Feature descriptions
+- src/marketing/pages/Solutions.tsx - Feature descriptions
+
+Backend:
+- igny8_core/auth/urls.py - Change URL to /keywords-library/
+- igny8_core/admin/ - Change admin menu label to "Keywords Library"
+- Keep model name as SeedKeyword (internal only)
+```
+
+---
+
+## Part 2: Filter System Redesign
+
+### Current Filters (Keywords Library)
+
+```
+[Search] [Country âŧ] [Difficulty âŧ] [Status âŧ]
+```
+
+**Issues:**
+1. â No "Clear Filters" button near active filter indicator
+2. â No Volume filter (exists on Planner Keywords page)
+3. â No Sector filter in filter bar
+4. â Hardcoded country/difficulty options (not cascading)
+5. â Filters don't show only available options based on current data
+6. â Active/Clear button positioning doesn't match Planner pages
+
+### Target Filters (Match Planner Pages Pattern)
+
+```
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â [đ Search] [Sector âŧ] [Volume âŧ] [Difficulty âŧ] [Country âŧ] [Status âŧ] â
+â â
+â Active: 2 filters [Clear] â Same position as Planner pages â
+â â
+â âšī¸ Search 54,180 keywords. Filter by volume or difficulty to find your â
+â perfect keywords! â
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+```
+
+**Improvements:**
+1. â
Active count + Clear button positioned **same as Planner pages**
+2. â
Add Volume range filter (min/max popup like Planner pages)
+3. â
Add Sector filter dropdown
+4. â
Keep "Not Yet Added" status filter (in_workflow filter)
+5. â
Implement cascading filters - options reflect available data
+6. â
Center filter bar with explainer text below
+
+### Filter: Status Options
+
+| Value | Label | Description |
+|-------|-------|-------------|
+| `all` | All Keywords | Show all keywords |
+| `available` | Not Yet Added | Keywords not in any workflow |
+| `in_workflow` | In Workflow | Keywords already added to site |
+
+### Backend API Changes
+
+**New Endpoint:** `GET /v1/keywords-library/filter_options/`
+
+Returns available filter options based on current filters (cascading):
+
+```python
+# Example Request
+GET /v1/keywords-library/filter_options/?industry=5§or=12&difficulty_max=40
+
+# Response
+{
+ "sectors": [
+ {"value": 12, "label": "Technology", "count": 1250},
+ {"value": 15, "label": "Mobile Apps", "count": 340},
+ ...
+ ],
+ "countries": [
+ {"value": "US", "label": "United States", "count": 1250},
+ {"value": "GB", "label": "United Kingdom", "count": 340},
+ ...
+ ],
+ "difficulties": [
+ {"value": "1", "label": "Very Easy (1-20)", "count": 890},
+ {"value": "2", "label": "Easy (21-40)", "count": 450},
+ {"value": "3", "label": "Medium (41-60)", "count": 320},
+ {"value": "4", "label": "Hard (61-80)", "count": 180},
+ {"value": "5", "label": "Very Hard (81-100)", "count": 90},
+ ],
+ "volume_stats": {
+ "min": 0,
+ "max": 421000,
+ "avg": 5420,
+ "p50": 1200,
+ "p90": 15000
+ },
+ "total_matching": 1590
+}
+```
+
+### Difficulty Mapping (Backend 1-100 â Frontend 1-5)
+
+| Frontend Level | Label | Backend Range |
+|----------------|-------|---------------|
+| 1 | Very Easy | 1-20 |
+| 2 | Easy | 21-40 |
+| 3 | Medium | 41-60 |
+| 4 | Hard | 61-80 |
+| 5 | Very Hard | 81-100 |
+
+**Low Difficulty = Level 1 and 2 (backend ⤠40)**
+
+### Frontend Changes
+
+1. **Add `fetchKeywordsLibraryFilterOptions()` API function**
+2. **Add Sector filter dropdown** (populated from site's sectors)
+3. **Add Volume filter component** (reuse from Keywords.tsx)
+4. **Position Active/Clear same as Planner pages**
+5. **Load filter options dynamically** based on industry/sector selection
+6. **Re-load options when any filter changes** (cascading)
+
+---
+
+## Part 3: App Header Selector Change
+
+### Current Behavior
+
+- Keywords Library page shows `SiteAndSectorSelector` (Site + Sector dropdowns)
+- This is redundant since page will have sector cards and sector filter
+
+### Target Behavior
+
+- Show `SingleSiteSelector` (Site only, like Automation pages)
+- Remove from `SITE_AND_SECTOR_ROUTES` array
+- Add to `SINGLE_SITE_ROUTES` array
+
+### Code Change (AppHeader.tsx)
+
+```diff
+const SITE_AND_SECTOR_ROUTES = [
+ '/planner',
+ '/writer',
+- '/setup/add-keywords',
+];
+
+const SINGLE_SITE_ROUTES = [
+ '/automation',
+ '/publisher',
+ '/account/content-settings',
+ '/sites',
++ '/keywords-library',
+];
+```
+
+---
+
+## Part 4: Sector Metric Cards
+
+### Design
+
+Clickable sector cards that filter the table when clicked. Each card shows stats and has 5-6 bulk add options.
+
+```
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â Your Site Sectors [Add Sector]â
+â â
+â âââââââââââââââââââââââ âââââââââââââââââââââââ âââââââââââââââââââââââ â
+â â đģ Technology â â đą Mobile Apps â â đ Web Dev â â
+â â âââââââââââââââââââ â â âââââââââââââââââââ â â âââââââââââââââââââ â â
+â â â â â â â â
+â â Total: 1,245 â â Total: 892 â â Total: 456 â â
+â â Available: 1,200 â â Available: 880 â â Available: 456 â â
+â â In Workflow: 45 â â In Workflow: 12 â â In Workflow: 0 â â
+â â â â â â â â
+â â âââ Quick Add âââââ â â âââ Quick Add âââââ â â âââ Quick Add âââââ â â
+â â â â â â â â
+â â đ High Volume â â đ High Volume â â đ High Volume â â
+â â 125 kw (>8.5K) â â 67 kw (>6.2K) â â 50 kw (>3.4K) â â
+â â [+50] [+100] â â [+50] [+67] â â [+50] â â
+â â â â â â â â
+â â đ¯ Low Difficulty â â đ¯ Low Difficulty â â đ¯ Low Difficulty â â
+â â 340 kw (â¤40) â â 212 kw (â¤40) â â 89 kw (â¤40) â â
+â â [+50] [+100] â â [+50] [+100] â â [+50] [+89] â â
+â â â â â â â â
+â â đĨ High Vol + Easy â â đĨ High Vol + Easy â â đĨ High Vol + Easy â â
+â â 45 kw â â 28 kw â â 12 kw â â
+â â [+45] â â [+28] â â [+12] â â
+â â â â â â â â
+â â đ Premium (>50K) â â đ Premium (>50K) â â đ Premium (>50K) â â
+â â 12 kw â â 8 kw â â 3 kw â â
+â â [+12] â â [+8] â â [+3] â â
+â â â â â â â â
+â â đą Long Tail â â đą Long Tail â â đą Long Tail â â
+â â 890 kw (4+ words)â â 654 kw (4+ words)â â 312 kw (4+ words)â â
+â â [+50] [+100] â â [+50] [+100] â â [+50] [+100] â â
+â â â â â â â â
+â â [Browse All â] â â [Browse All â] â â [Browse All â] â â
+â âââââââââââââââââââââââ âââââââââââââââââââââââ âââââââââââââââââââââââ â
+â â
+â đĄ Click any card to filter the table by that sector â
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+```
+
+### Click Behavior
+
+- **Click on sector card** â Filter table by that sector
+- **Click on Quick Add button** â Add keywords matching that criteria
+
+### Stats Per Sector Card (6 Options)
+
+Based on actual keyword data analysis (volume ranges 50-281,000, difficulty 0-99).
+**Dynamic fallback thresholds** ensure every sector shows useful data:
+
+| Stat | Criteria | Fallback Logic | Example |
+|------|----------|----------------|---------|
+| đ **High Volume** | Top 50/100 by volume | Dynamic threshold based on available data | "125 kw (>8.5K)" |
+| đ¯ **Low Difficulty** | Difficulty ⤠40 (levels 1-2) | Fixed threshold | "340 kw (â¤40)" |
+| đĨ **Best Opportunities** | High vol + Difficulty ⤠40 | Combines above two criteria | "45 kw" |
+| đ **Premium Traffic** | Volume > 50,000 | If 0 â try >25,000 â if 0 â try >10,000 | "12 kw (>50K)" |
+| đą **Long Tail** | Words âĨ 4 AND Volume > 1,000 | If 0 â try >500 â if 0 â try >200 | "890 kw (4+ words, >1K)" |
+| ⥠**Quick Wins** | Difficulty ⤠20 AND Volume > 1,000 + Available | If 0 â try >500 â if 0 â try >200 | "67 kw" |
+
+### Dynamic Threshold Fallback Logic
+
+```python
+# Premium Traffic - cascading volume thresholds
+def get_premium_traffic(keywords):
+ for threshold in [50000, 25000, 10000]:
+ count = keywords.filter(volume__gt=threshold).count()
+ if count > 0:
+ return {"count": count, "threshold": threshold}
+ return {"count": 0, "threshold": 10000}
+
+# Long Tail - cascading volume with word count
+def get_long_tail(keywords):
+ base = keywords.filter(word_count__gte=4)
+ for threshold in [1000, 500, 200]:
+ count = base.filter(volume__gt=threshold).count()
+ if count > 0:
+ return {"count": count, "min_volume": threshold}
+ return {"count": 0, "min_volume": 200}
+
+# Quick Wins - cascading volume with low difficulty + available
+def get_quick_wins(keywords, attached_ids):
+ base = keywords.filter(difficulty__lte=20).exclude(id__in=attached_ids)
+ for threshold in [1000, 500, 200]:
+ count = base.filter(volume__gt=threshold).count()
+ if count > 0:
+ return {"count": count, "min_volume": threshold}
+ return {"count": 0, "min_volume": 200}
+```
+
+This ensures **every sector shows meaningful data** regardless of size, making the feature always useful and operational.
+
+### High Volume Dynamic Threshold
+
+Instead of fixed 10,000 threshold:
+- Get top 50 keywords by volume for sector
+- Show threshold as ">X" where X = lowest volume in top 50
+- If < 50 keywords available, use top 100 threshold
+- Example: "125 kw (>8,540)" means 125 keywords with volume > 8,540
+
+### Bulk Add Options
+
+Each stat shows add buttons based on availability:
+- `[+50]` - Add top 50
+- `[+100]` - Add top 100 (if > 50 available)
+- `[+200]` - Add top 200 (if > 100 available)
+- `[+N]` - Add all N (if N ⤠200)
+
+**Always show confirmation modal before adding.**
+
+### Backend API
+
+**Endpoint:** `GET /v1/keywords-library/sector_stats/`
+
+```python
+# Request
+GET /v1/keywords-library/sector_stats/?site_id=21
+
+# Response
+{
+ "sectors": [
+ {
+ "sector_id": 12,
+ "sector_name": "Technology",
+ "sector_slug": "technology",
+ "total_keywords": 1245,
+ "available": 1200,
+ "in_workflow": 45,
+ "stats": {
+ "high_volume": {
+ "count": 125,
+ "threshold": 8540, // Dynamic: lowest volume in top 50/100
+ "total_volume": 1200000
+ },
+ "low_difficulty": {
+ "count": 340,
+ "threshold": 40, // Backend value (maps to frontend level 2)
+ "total_volume": 450000
+ },
+ "high_vol_easy": {
+ "count": 45,
+ "description": "High volume + Low difficulty"
+ },
+ "premium": {
+ "count": 12,
+ "threshold": 50000, // Dynamic: 50K â 25K â 10K fallback
+ "used_fallback": false
+ },
+ "long_tail": {
+ "count": 890,
+ "min_words": 4,
+ "min_volume": 1000, // Dynamic: 1K â 500 â 200 fallback
+ "used_fallback": false
+ },
+ "quick_wins": {
+ "count": 67,
+ "difficulty_max": 20, // Level 1 only
+ "min_volume": 1000, // Dynamic: 1K â 500 â 200 fallback
+ "used_fallback": false
+ }
+ }
+ },
+ // ... more sectors
+ ]
+}
+```
+
+### Styling
+
+Use **subtle accents** matching IGNY8 design style:
+- Light/dark mode compatible
+- Muted colors from dashboard/billing/usage pages
+- No bright/colorful backgrounds
+- Subtle borders and shadows
+- Consistent with existing card components
+
+---
+
+## Part 5: Smart Suggestions Section
+
+### Concept
+
+Show **Smart Suggestions** dynamically based on current filters/search. Display a **breathing circle indicator** until user interacts with filters or search.
+
+### Initial State (Before Search/Filter)
+
+```
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â đĄ Smart Suggestions â
+â â
+â â Ready-to-use keywords waiting for you! Search for a keyword or apply â
+â any filter to see smart suggestions... â
+â â â
+â Breathing/pulsing circle indicator (subtle animation) â
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+```
+
+### After Search/Filter Applied
+
+```
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â đĄ Smart Suggestions for "seo tools" â
+â â
+â ââââââââââââââââââââââââââââââââââââââ âââââââââââââââââââââââââââââââââââââ â
+â â đ High Volume Keywords â â đ¯ Easy Wins â â
+â â â â â â
+â â 89 keywords with >4,200 volume â â 156 keywords (difficulty ⤠40) â â
+â â Combined: 2.4M searches/month â â Combined: 890K searches/month â â
+â â â â â â
+â â Top 5: seo tools, seo audit, ... â â Top 5: local seo, seo tips, ... â â
+â â â â â â
+â â [+50] [+100] [+All 89] â â [+50] [+100] [+All 156] â â
+â ââââââââââââââââââââââââââââââââââââââ âââââââââââââââââââââââââââââââââââââ â
+â â
+â ââââââââââââââââââââââââââââââââââââââ âââââââââââââââââââââââââââââââââââââ â
+â â đĨ Best Opportunities â â đą Long Tail Keywords â â
+â â â â â â
+â â 45 kw (high volume + easy) â â 234 keywords (3+ words) â â
+â â Combined: 1.2M searches/month â â Lower competition phrases â â
+â â â â â â
+â â [+45] â â [+50] [+100] [+200] â â
+â ââââââââââââââââââââââââââââââââââââââ âââââââââââââââââââââââââââââââââââââ â
+â â
+â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â
+â â đ All matching "seo tools" - 234 keywords â â
+â â â â
+â â [+50 by Volume] [+50 by Difficulty] [+100] [+200] [+All 234] â â
+â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+```
+
+### Breathing Circle Indicator
+
+CSS animation for pulsing circle:
+```css
+.breathing-indicator {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ background: var(--muted-foreground);
+ animation: breathe 2s ease-in-out infinite;
+}
+
+@keyframes breathe {
+ 0%, 100% { opacity: 0.3; transform: scale(0.9); }
+ 50% { opacity: 0.8; transform: scale(1.1); }
+}
+```
+
+### Suggestion Categories
+
+| Category | Criteria | Fallback Logic |
+|----------|----------|----------------|
+| đ High Volume | Top 50/100 by volume | Dynamic threshold |
+| đ¯ Easy Wins | Difficulty ⤠40 | Fixed threshold |
+| đĨ Best Opportunities | High vol + Easy | Combines criteria |
+| đ Premium | Volume > 50K | 50K â 25K â 10K fallback |
+| đą Long Tail | Words âĨ 4 + Vol > 1K | Vol: 1K â 500 â 200 fallback |
+| ⥠Quick Wins | Diff ⤠20 + Vol > 1K + Available | Vol: 1K â 500 â 200 fallback |
+
+### Bulk Add Buttons
+
+| Button | Action |
+|--------|--------|
+| `[+50]` | Add top 50 sorted by relevant criteria |
+| `[+100]` | Add top 100 |
+| `[+200]` | Add top 200 |
+| `[+All N]` | Add all N matching (shown when N ⤠200) |
+| `[+50 by Volume]` | Add top 50 sorted by volume DESC |
+| `[+50 by Difficulty]` | Add top 50 sorted by difficulty ASC |
+
+**Always show confirmation modal before any bulk add.**
+
+### Styling
+
+Match IGNY8 dashboard/billing/usage styling:
+- Subtle card backgrounds (muted)
+- Light borders
+- Dark mode compatible colors
+- No bright/colorful accents
+- Consistent typography
+
+---
+
+## Part 6: Page Layout Redesign
+
+### Current Layout
+
+```
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â [Header with Site+Sector selector] â
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¤
+â â
+â Quick-Start Keywords Section (always shown first) â
+â â
+â [Browse All Keywords] button â
+â â
+â Table (only shown after clicking Browse) â
+â â
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+```
+
+### New Layout
+
+```
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â [Header with Site-only selector] â
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¤
+â â
+â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â â Sector Metric Cards (clickable - filters table by sector) ââ
+â â - 5-6 bulk add stat options per card ââ
+â â - [+50] [+100] [+200] buttons ââ
+â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â â
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¤
+â â
+â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â
+â â [đ Search] [Sector âŧ] [Volume âŧ] [Difficulty âŧ] [Country âŧ] â â
+â â â â
+â â Active: 2 filters [Clear] â â
+â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â
+â â
+â âšī¸ Search 54,180 keywords. Filter by volume or difficulty â
+â to find your perfect keywords! â
+â â
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¤
+â â
+â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â â đĄ Smart Suggestions ââ
+â â ââ
+â â â Search or filter keywords to see personalized suggestions... ââ
+â â â breathing indicator ââ
+â â ââ
+â â (After search/filter: shows suggestion cards with [+50] [+100] buttons) ââ
+â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â â
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¤
+â â
+â Keywords Table (always shown, data loaded by default) â
+â - Shows first 500 records (with server-side sorting/filtering) â
+â - Pagination shows total count â
+â - Sorted by volume DESC by default â
+â â
+âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+```
+
+### Key Layout Changes
+
+1. **Site-only selector** in header (no sector dropdown)
+2. **Sector cards at top** - clickable to filter table
+3. **Centered filter bar** with sector filter + active/clear like Planner
+4. **Smart Suggestions** - breathing indicator until search/filter
+5. **Table always visible** - no "Browse" toggle
+6. **Confirmation modal** for all bulk adds
+
+---
+
+## Part 7: Implementation Tasks
+
+### Phase 1: Backend API Updates
+
+| Task | File | Priority |
+|------|------|----------|
+| Add `/filter_options/` endpoint | `auth/views.py` | High |
+| Add `/sector_stats/` endpoint with 6 stat types | `auth/views.py` | High |
+| Change URL to `/keywords-library/` (no alias) | `auth/urls.py` | High |
+| Update admin menu to "Keywords Library" | `admin/` | High |
+| Support volume_min/max filters | `auth/views.py` | High |
+| Support sector filter | `auth/views.py` | High |
+| Implement cascading filter logic | `auth/views.py` | High |
+| Add bulk add endpoint with confirmation | `auth/views.py` | High |
+
+### Phase 2: Frontend Filter System
+
+| Task | File | Priority |
+|------|------|----------|
+| Rename `fetchSeedKeywords` â `fetchKeywordsLibrary` | `services/api.ts` | High |
+| Add `fetchKeywordsLibraryFilterOptions()` | `services/api.ts` | High |
+| Add `fetchKeywordsLibrarySectorStats()` | `services/api.ts` | High |
+| Add Sector filter dropdown | `KeywordsLibrary.tsx` | High |
+| Add Volume filter component | `KeywordsLibrary.tsx` | High |
+| Position Active/Clear same as Planner | `KeywordsLibrary.tsx` | High |
+| Implement cascading filter loading | `KeywordsLibrary.tsx` | High |
+| Center filter bar + add explainer | `KeywordsLibrary.tsx` | Medium |
+
+### Phase 3: Selector & Layout Changes
+
+| Task | File | Priority |
+|------|------|----------|
+| Move route to `SINGLE_SITE_ROUTES` | `AppHeader.tsx` | High |
+| Change route to `/keywords-library` | `App.tsx` | High |
+| Remove "Browse" toggle, show table by default | `KeywordsLibrary.tsx` | High |
+| Remove High Opportunity section | `KeywordsLibrary.tsx` | Medium |
+
+### Phase 4: Sector Metric Cards
+
+| Task | File | Priority |
+|------|------|----------|
+| Create SectorMetricCard component | `components/keywords-library/` | High |
+| Implement 6 stat types per card | `SectorMetricCard.tsx` | High |
+| Add click-to-filter behavior | `KeywordsLibrary.tsx` | High |
+| Add [+50] [+100] [+200] buttons | `SectorMetricCard.tsx` | High |
+| Add confirmation modal for bulk add | `KeywordsLibrary.tsx` | High |
+
+### Phase 5: Smart Suggestions
+
+| Task | File | Priority |
+|------|------|----------|
+| Create SmartSuggestions component | `components/keywords-library/` | High |
+| Add breathing circle indicator | `SmartSuggestions.tsx` | High |
+| Show suggestions after search/filter | `KeywordsLibrary.tsx` | High |
+| Add bulk add buttons [+50] [+100] [+200] | `SmartSuggestions.tsx` | High |
+| Implement 6 suggestion categories | `SmartSuggestions.tsx` | Medium |
+
+### Phase 6: Naming Standardization
+
+| Task | File | Priority |
+|------|------|----------|
+| Update sidebar label â "Keywords Library" | `AppSidebar.tsx` | High |
+| Update SearchModal references | `SearchModal.tsx` | Medium |
+| Rename Step4AddKeywords â Step4KeywordsLibrary | `onboarding/steps/` | Medium |
+| Update marketing pages | `Pricing.tsx`, `Solutions.tsx` | Low |
+
+### Phase 7: Confirmation Modal
+
+| Task | File | Priority |
+|------|------|----------|
+| Create BulkAddConfirmation modal | `components/keywords-library/` | High |
+| Show count and criteria being added | `BulkAddConfirmation.tsx` | High |
+| Always require confirmation for bulk add | `KeywordsLibrary.tsx` | High |
+
+---
+
+## Part 8: No Backward Compatibility
+
+### Single Source of Truth
+
+**No legacy URLs, no aliases, no fallbacks.**
+
+| Item | Action |
+|------|--------|
+| `/setup/add-keywords` URL | **REMOVE** - change to `/keywords-library` |
+| `/v1/auth/seed-keywords/` API | **REMOVE** - change to `/v1/keywords-library/` |
+| `fetchSeedKeywords()` function | **RENAME** to `fetchKeywordsLibrary()` |
+| Backend admin "Industry Sector Keywords" | **RENAME** to "Keywords Library" |
+| All references to old terms | **UPDATE** to new terms |
+
+### Route Change (App.tsx)
+
+```tsx
+// BEFORE
+} />
+
+// AFTER (no redirect, direct change)
+} />
+```
+
+### API Change (auth/urls.py)
+
+```python
+# BEFORE
+path('seed-keywords/', SeedKeywordViewSet.as_view({'get': 'list'}), name='seed-keyword-list'),
+
+# AFTER (no alias)
+urlpatterns = [
+ path('keywords-library/', include([
+ path('', SeedKeywordViewSet.as_view({'get': 'list'}), name='keywords-library-list'),
+ path('filter_options/', SeedKeywordViewSet.as_view({'get': 'filter_options'}), name='keywords-library-filter-options'),
+ path('sector_stats/', SeedKeywordViewSet.as_view({'get': 'sector_stats'}), name='keywords-library-sector-stats'),
+ path('bulk_add/', SeedKeywordViewSet.as_view({'post': 'bulk_add'}), name='keywords-library-bulk-add'),
+ ])),
+]
+```
+
+### File Renames
+
+| From | To |
+|------|-----|
+| `IndustriesSectorsKeywords.tsx` | `KeywordsLibrary.tsx` |
+| `Step4AddKeywords.tsx` | `Step4KeywordsLibrary.tsx` |
+
+---
+
+## Part 9: Success Metrics
+
+| Metric | Current | Target |
+|--------|---------|--------|
+| Filter usage rate | Low (no volume/sector filter) | High (all filters used) |
+| Keywords added per session | ~50 (quick-start only) | 100+ (smart bulk adds) |
+| Bulk add usage | N/A | Track [+50], [+100], [+200] clicks |
+| Sector card clicks | N/A | Track filtering by sector |
+| Smart suggestions engagement | N/A | Track after search/filter |
+| Time to first keyword add | Unknown | < 30 seconds |
+
+---
+
+## Part 10: Design Decisions Summary
+
+| Decision | Choice | Rationale |
+|----------|--------|-----------|
+| **URL** | `/keywords-library` | Clean, no legacy compat |
+| **Naming** | "Keywords Library" (plural) | Consistent across all surfaces |
+| **Backward Compat** | None | Single source of truth |
+| **Sector cards click** | Filters table | Intuitive UX |
+| **High Volume threshold** | Dynamic (top 50/100 lowest) | Adapts to actual data |
+| **Low Difficulty** | ⤠40 (levels 1-2 mapped) | "Easy" keywords |
+| **Bulk add options** | 50, 100, 200, All | Flexible choices |
+| **Confirmation modal** | Always | Prevent accidental adds |
+| **Smart suggestions** | Breathing indicator until search | Encourages interaction |
+| **Filter styling** | Match Planner pages | Consistency |
+
+---
+
+## Files Summary
+
+### Files to Modify
+
+| File | Changes |
+|------|---------|
+| `frontend/src/pages/Setup/IndustriesSectorsKeywords.tsx` | Major rewrite â rename to KeywordsLibrary.tsx |
+| `frontend/src/services/api.ts` | Rename + add new API functions |
+| `frontend/src/layout/AppHeader.tsx` | Move route category |
+| `frontend/src/layout/AppSidebar.tsx` | Update label to "Keywords Library" |
+| `frontend/src/App.tsx` | Change route (no redirect) |
+| `frontend/src/components/common/SearchModal.tsx` | Update references |
+| `backend/igny8_core/auth/views.py` | Add new endpoints, update existing |
+| `backend/igny8_core/auth/urls.py` | Change URL (no alias) |
+| `backend/igny8_core/admin/` | Update menu label |
+
+### Files to Create
+
+| File | Purpose |
+|------|---------|
+| `frontend/src/components/keywords-library/SectorMetricCard.tsx` | Sector stats card with 6 stat options |
+| `frontend/src/components/keywords-library/SmartSuggestions.tsx` | Smart suggestions with breathing indicator |
+| `frontend/src/components/keywords-library/BulkAddConfirmation.tsx` | Confirmation modal for bulk adds |
+
+### Files to Rename
+
+| From | To |
+|------|-----|
+| `IndustriesSectorsKeywords.tsx` | `KeywordsLibrary.tsx` |
+| `Step4AddKeywords.tsx` | `Step4KeywordsLibrary.tsx` |
+
+---
+
+## Approval Checklist
+
+- [x] Review by Product Owner - **APPROVED**
+- [x] Design decisions finalized
+- [x] Backend API design approved
+- [x] Frontend component design approved
+- [x] No backward compatibility confirmed
+- [ ] Implementation started
+
+---
+
+## Appendix A: Complete List of Files to Refactor
+
+### Frontend Source Files (Must Change)
+
+| File | Changes Required |
+|------|------------------|
+| `frontend/src/App.tsx` | Change route `/setup/add-keywords` â `/keywords-library`, remove `IndustriesSectorsKeywords` import |
+| `frontend/src/layout/AppHeader.tsx` | Remove `/setup/add-keywords` from `SITE_AND_SECTOR_ROUTES`, add `/keywords-library` to `SINGLE_SITE_ROUTES` |
+| `frontend/src/layout/AppSidebar.tsx` | Change label "Keyword Library" â "Keywords Library", path `/setup/add-keywords` â `/keywords-library` |
+| `frontend/src/services/api.ts` | Rename `fetchSeedKeywords()` â `fetchKeywordsLibrary()`, rename `SeedKeyword` interface (user-facing), update API URL |
+| `frontend/src/components/common/SearchModal.tsx` | Change all `/setup/add-keywords` â `/keywords-library`, "Keyword Library" â "Keywords Library" |
+| `frontend/src/components/sites/SiteSetupChecklist.tsx` | Change href `/setup/add-keywords` â `/keywords-library` |
+| `frontend/src/components/dashboard/QuickActionsWidget.tsx` | Change "Keyword Library" â "Keywords Library" |
+| `frontend/src/components/onboarding/steps/Step4AddKeywords.tsx` | **RENAME FILE** â `Step4KeywordsLibrary.tsx`, update imports |
+| `frontend/src/components/onboarding/WorkflowGuide.tsx` | Update comment reference to `IndustriesSectorsKeywords` |
+| `frontend/src/pages/Setup/IndustriesSectorsKeywords.tsx` | **RENAME FILE** â `KeywordsLibrary.tsx`, **MAJOR REWRITE** |
+| `frontend/src/pages/Reference/SeedKeywords.tsx` | Update if needed for consistency |
+
+### Backend Files (Must Change)
+
+| File | Changes Required |
+|------|------------------|
+| `backend/igny8_core/auth/urls.py` | Change route `seed-keywords/` â `keywords-library/` |
+| `backend/igny8_core/auth/views.py` | Keep `SeedKeywordViewSet` class name (internal), add new endpoints |
+| `backend/igny8_core/auth/serializers.py` | Keep `SeedKeywordSerializer` name (internal) |
+| `backend/igny8_core/auth/admin.py` | Update admin label to "Keywords Library" |
+| `backend/igny8_core/settings.py` | Change sidebar menu "Seed Keywords" â "Keywords Library" |
+| `backend/igny8_core/urls.py` | Update `seedkeyword_csv_template` and `seedkeyword_csv_import` URL paths |
+
+### Backend Files (Keep Internal - SeedKeyword Model)
+
+| File | Status |
+|------|--------|
+| `backend/igny8_core/auth/models.py` | **KEEP** `SeedKeyword` class name (internal) |
+| `backend/igny8_core/business/planning/models.py` | **KEEP** `seed_keyword` field reference (internal) |
+| `backend/scripts/import_seed_keywords_single.py` | **KEEP** internal script name |
+| `backend/scripts/import_all_seed_keywords.py` | **KEEP** internal script name |
+
+### Documentation Files (Must Update)
+
+| File | Changes Required |
+|------|------------------|
+| `docs/INDEX.md` | Update route `/setup/add-keywords` â `/keywords-library` |
+| `docs/20-API/ENDPOINTS.md` | Update endpoint `/seed-keywords/` â `/keywords-library/` |
+| `docs/90-REFERENCE/SEED-KEYWORDS-IMPORT-GUIDE.md` | Update user-facing terminology, keep internal references |
+| `docs/90-REFERENCE/MODELS.md` | Keep `SeedKeyword` model reference (internal) |
+| `docs/plans/NAVIGATION_REFACOTR/NAVIGATION_FINAL_COMPLETION_SUMMARY.md` | Update "Keyword Library" â "Keywords Library" |
+| `docs/plans/NAVIGATION_REFACOTR/NAVIGATION_REFACTOR_PLAN.md` | Update "Keyword Library" â "Keywords Library" |
+
+### Other Files (May Need Review)
+
+| File | Notes |
+|------|-------|
+| `CHANGELOG.md` | Historical references - may keep as-is |
+| `.rules` | Keep `SeedKeyword` (internal model reference) |
+| `frontend/dist/` | **REBUILD** after changes |
+| `frontend/audit-results/` | Re-run audits after changes |
+
+---
+
+**Status:** READY FOR IMPLEMENTATION
+
+**Next Steps:** Begin Phase 1 - Backend API Updates
diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts
index bb6a5739..b87b51cb 100644
--- a/frontend/src/services/api.ts
+++ b/frontend/src/services/api.ts
@@ -2277,6 +2277,9 @@ export async function fetchSeedKeywords(filters?: {
search?: string;
page?: number;
page_size?: number;
+ ordering?: string;
+ difficulty_min?: number;
+ difficulty_max?: number;
}): Promise {
const params = new URLSearchParams();
// Use industry_id and sector_id as per backend get_queryset, but also try industry/sector for filterset_fields
@@ -2292,6 +2295,11 @@ export async function fetchSeedKeywords(filters?: {
if (filters?.search) params.append('search', filters.search);
if (filters?.page) params.append('page', filters.page.toString());
if (filters?.page_size) params.append('page_size', filters.page_size.toString());
+ // Server-side sorting - critical for proper pagination
+ if (filters?.ordering) params.append('ordering', filters.ordering);
+ // Difficulty range filtering
+ if (filters?.difficulty_min !== undefined) params.append('difficulty_min', filters.difficulty_min.toString());
+ if (filters?.difficulty_max !== undefined) params.append('difficulty_max', filters.difficulty_max.toString());
const queryString = params.toString();
return fetchAPI(`/v1/auth/seed-keywords/${queryString ? `?${queryString}` : ''}`);