# HARDCODED RULES — DO NOT BREAK
## Single Source of Truth for MarichWellness/maggyafftng

> **Last Updated:** 2026-06-27
> **Purpose:** This document consolidates every critical rule, fix, and pattern from `memory/change-log.md`, `CLAUDE.md`, `AGENTS.md`, and `memory/create-product-complete-reference.md` into one authoritative reference. Any code change that violates these rules WILL cause breakages.

---

## TABLE OF CONTENTS
1. [TipTap / RichTextToolbar Rules](#1-tiptap--richtexttoolbar-rules)
2. [Admin Products Page Rules](#2-admin-products-page-rules)
3. [Settings Panel Rules](#3-settings-panel-rules)
4. [Visitor Chat Interface Rules](#4-visitor-chat-interface-rules)
5. [Workout Planner Rules](#5-workout-planner-rules)
6. [Shop / Product Preview Rules](#6-shop--product-preview-rules)
7. [Analytics & Tracking Rules](#7-analytics--tracking-rules)
8. [Admin Layout & Auth Rules](#8-admin-layout--auth-rules)
9. [Database & Migration Rules](#9-database--migration-rules)
10. [Build & Dev Server Rules](#10-build--dev-server-rules)
11. [CSS & Styling Rules](#11-css--styling-rules)
12. [Import & Module Rules](#12-import--module-rules)
13. [API Route Rules](#13-api-route-rules)
14. [Content Intelligence Rules](#14-content-intelligence-rules)
15. [Fitness Planner Rules](#15-fitness-planner-rules)

---

## 1. TipTap / RichTextToolbar Rules

### 1.1 No Duplicate Extension Registrations (CRITICAL)
`@tiptap/starter-kit@3.24.0` already includes these extensions natively:
- `underline`, `italic`, `bold`, `code`, `heading`, `blockquote`
- `bulletList`, `orderedList`, `codeBlock`, `horizontalRule`
- `history` (undo/redo), `listItem`, `paragraph`, `text`, `doc`, `hardBreak`

**NEVER** import and register a standalone extension that is already in StarterKit. Doing so creates a **duplicate extension registration** that breaks other formatting features (e.g., duplicate `underline` broke `italic`).

### 1.2 Safe to Import (NOT in StarterKit)
- `@tiptap/extension-link` — LinkExtension
- `@tiptap/extension-image` — ImageExtension (or custom extensions extending it)
- `@tiptap/extension-placeholder` — PlaceholderExtension
- `@tiptap/extension-text-align` — TextAlignExtension
- `@tiptap/extension-table` — TableExtension (if needed)
- `@tiptap/extension-iframe` — IframeExtension (if needed)

### 1.3 DO NOT Import (already in StarterKit)
- `@tiptap/extension-underline` — **BROKE italic when imported**
- `@tiptap/extension-italic`
- `@tiptap/extension-bold`
- `@tiptap/extension-code`
- `@tiptap/extension-heading`
- `@tiptap/extension-blockquote`
- `@tiptap/extension-bullet-list`
- `@tiptap/extension-ordered-list`
- `@tiptap/extension-code-block`
- `@tiptap/extension-history`
- `@tiptap/extension-horizontal-rule`
- `@tiptap/extension-document`
- `@tiptap/extension-paragraph`
- `@tiptap/extension-text`

### 1.4 RichTextToolbar Props
- **`uploadedImages` prop** MUST be passed to ALL RichTextToolbar instances (Create modal, Edit modal, Preview modal) for the image picker to work.
- **`rows` prop** sets editor min-height via `Math.max(rows * 24, 300)px`.
- **`value`/`onChange`** are the correct props (NOT `onInsert` which doesn't exist).

### 1.5 Image Picker
- Must be a floating dropdown (not a horizontal bar between toolbar and editor).
- Images must be `draggable` with `cursor-grab`/`cursor-grabbing`.
- Drag-and-drop must use `editor.view.posAtCoords()` for precise positioning.
- Must auto-close when clicking outside via `mousedown` event listener.

### 1.6 Resize Image
- `ResizableImage` extension extends `ImageExtension` with `width` attribute.
- Resize handle: emerald square at bottom-right corner of images.
- Resize slider dropdown must use `right-0` positioning (not `left-0` which gets clipped).

### 1.7 Blockquote
- Use StarterKit's built-in blockquote (NOT a custom extension).
- The `>` pipe character is a ProseMirror editor feature — it's part of the editor view, not a CSS issue.
- CSS: `border-left: 3px solid #10b981` (NOT background linear-gradient which renders as pipe).
- Quotation marks via CSS `quotes` property with `::before { content: open-quote }`.

---

## 2. Admin Products Page Rules

### 2.1 State Variables (DO NOT RENAME/REMOVE)
- `localCreateSeoDesc` / `localEditSeoDesc` — Exist to prevent parent re-renders on every keystroke. They sync to `newProduct.productDescription` / `editingProduct.productDescription` ONLY on save/preview/generate.
- `uploadedImages` — Must be populated by BOTH local blob URL AND server upload.
- `showCreatePreview` / `showEditPreview` — Control preview visibility.
- `articleSaved` — Per-product article save status map.
- `previewProductId` — ID of product being previewed.
- `previewMode` — `'card' | 'detail'` toggle.

### 2.2 Preview Flow (CRITICAL)
1. `handlePreview()` uses **timestamped slugs** (`slugify(name) + '-' + Date.now()`) to avoid UNIQUE constraint violations on repeated preview clicks.
2. Preview checks `newProduct.id` — if it exists, uses **PUT** (UPDATE) instead of **POST** (INSERT) to avoid duplicate slug errors.
3. PUT route MUST return `{ product: { ...product, id: result.lastInsertRowid } }` so the client receives the product `id`.

### 2.3 Image Upload (CRITICAL)
1. `handleImageUpload()` creates BOTH:
   - **Local blob URL** via `URL.createObjectURL(file)` for instant preview
   - **Server upload** via `uploadImage(file, isEdit)` so image appears in RichTextToolbar picker
2. Both paths MUST work — never remove the server upload call.
3. Image processing: `fit: 'inside'` with `withoutEnlargement: true` (NOT `fit: 'cover'` which crops).
4. Image preview display: `object-contain p-2` (NOT `object-cover` which crops).
5. Sharp fallback: If Sharp fails, save original file with original extension instead of returning 500.

### 2.4 Generate Article (CRITICAL)
- "Generate Article" button uses **Content Intelligence API** (DeepSeek + Tavily), NOT the legacy `generateSEOBlogPost()` template function.
- `generateArticleWithAI()` sends POST to `/api/content-intelligence/generate`.
- Error handling: MUST check `!res.ok` before `res.json()` to prevent `SyntaxError` crashes on non-JSON error responses.

### 2.5 Modal Layout (CRITICAL)
- Outer container: `overflow: hidden` (prevents double scrollbar).
- Inner form content: `overflow-y-auto` (scrollable).
- Action buttons: Sticky bottom bar **OUTSIDE** the scrollable area.
- Form elements have `id="create-product-form"` / `id="edit-product-form"` so submit buttons (outside forms) can reference them via `form` attribute.

### 2.6 FAQ Section
- Textarea parses one question per line into `{ question, answer: '' }` array.
- Individual Q&A input fields were **removed** to prevent layout shift.
- DO NOT re-add individual Q&A fields.

### 2.7 onClose Handler
- Create modal `onClose` MUST reset `newProduct` to `emptyProduct`, clear `imagePreview` and `localCreateSeoDesc` to prevent stale data.

### 2.8 Catalog Intelligence Table
- **Product column**: Shows `p.image` as `<img>` with `w-10 h-14 object-contain rounded-lg` if image exists. Falls back to colored initials square.
- **Category label**: `p.categoryId === 'cat_supps' ? 'Supplement' : 'Equipment'` — NOT product name or brand.
- **Product Description column**: Line 1 = Product Name (`p.name`), Line 2 = Brand Name (`p.brand`). DO NOT render `p.productDescription` or `p.description` here (they contain raw HTML).
- **Revenue column**: **COMPUTED** value `((p.price || 0) * ((p.commission || 0) / 100)).toFixed(2)` — NOT static `p.revenue`.
- **Pagination separator**: Must be plain ASCII hyphen (` - `), never Unicode en-dash (`–`), em-dash (`—`), or any Unicode escape sequence.

### 2.9 Error Handling
- Always check `!res.ok` before `res.json()` in ALL API calls.
- On non-OK response: try JSON parse first, fall back to text, throw meaningful error with HTTP status code.

### 2.10 Key Words Textarea
- Must use `rows={3}` (not `rows={1}`) so users can see multiple keywords without scrolling.
- Label: "KEY Words" (not "SEO Description").
- Placeholder: "Enter keywords separated by commas".

### 2.11 Partner Link
- Label: "Link" (not "Partner Link").
- Input type: `url` (not `text`).

---

## 3. Settings Panel Rules

### 3.1 Tab Structure
- Must maintain exactly **7 tabs**: General, Profile, Security, Notifications, Analytics, API & Integrations, Billing.
- Never add/remove tabs without updating all corresponding state/UI.

### 3.2 Notifications Tab — Toggles Embedded in Cards (CRITICAL)
- **NO separate "Notification Preferences" section**: The 5 notification toggles must NEVER exist as a standalone section below the metric cards.
- **Toggle-to-Card mapping (DO NOT REORDER)**:
  - Card 1 (Visitor Engagement, blue) → "New Visitor Messages" toggle
  - Card 2 (Issue Management, amber) → "Product Submissions" toggle
  - Card 3 (Security Monitoring, red) → "Token Budget Alerts" toggle
  - Card 4 (AI Token Consumption, purple) → "Revenue Thresholds" toggle
  - Card 5 (Scraping Operations, emerald) → "Scraping Quota Alerts" toggle
- **Toggle placement**: Each toggle sits at the bottom of its card, separated by a `border-t border-zinc-200` divider, inside a `pt-4` wrapper.
- **Toggle size**: Must use compact `w-10 h-5` switch size — never use full-size toggles.
- **Toggle accent colors**: Each toggle's active state must match its card's accent color (blue-600, amber-500, red-500, purple-600, emerald-500).
- **Toggle functionality**: `saveSetting()` calls must remain identical — never change the setting keys or save logic.
- **Card structure**: Each card must have `flex flex-col` with `mt-auto` on the toggle wrapper to push it to the bottom of the card.

### 3.3 MiniChart (CRITICAL)
- Always use `Math.max(data.length - 1, 1)` — never `data.length - 1` directly, as it causes NaN when data has only 1 point.

### 3.4 Token Daily Breakdown (CRITICAL)
- `tokenDailyBreakdown` state MUST be declared: `const [tokenDailyBreakdown, setTokenDailyBreakdown] = useState<any[]>([]);`
- `applyData()` MUST map `tokenDailyBreakdown`: `setTokenDailyBreakdown(data.tokenDailyBreakdown || []);`
- Chart MUST use `day.inputTokens` and `day.outputTokens` — never use hardcoded ratios (like 40/60) to split total tokens.

### 3.5 Plugin Management (Security Tab)
- Plugin Management is in the **Security tab only** — never move it to another tab.
- Plugin state variables MUST be loaded in `applyData()`.
- **Master toggle** disables all sub-toggles when turned off.
- **Feature toggles** are visually disabled (greyed out) when master toggle is `false`.
- **Squid SVG icon** must remain — it's the brand identifier.
- **Status badges**: Security → "Protected", Speed → "Optimized", SEO → "Active", Analytics → "Collecting".
- Only **4 features**: Security, Speed, SEO, Analytics.

### 3.6 Issue Tracker (Security Tab)
- Issue Tracker is in the **Security tab's right column only** — never move it.
- All 12 issue-related state variables must be declared at the top of the SettingsPanel component.
- Issue form validation requires `issueForm.title.trim()` before submitting.
- Issue status updates MUST refresh the list via GET.
- System Health Matrix period buttons must re-fetch chart data when changed.
- Issue Log filters MUST reset pagination to page 1 when any filter changes.
- Plugin Management must remain in the **left column**.
- Grid container must use `items-start` to keep both columns aligned.

### 3.7 Profile & Account Tab
- Profile CRUD API is at `/api/admin/admins` (GET/POST/PUT/DELETE).
- Profile pagination uses `limit=1` (one profile at a time).
- Navigation buttons: First/Previous/Next/Last — all four must remain.
- Edit Profile enables Email and Role fields (`disabled={!editing}`).
- Save Profile has **dual behavior**: `editing=true` → `PUT /api/admin/admins`, `editing=false` → `PUT /api/admin/settings/profile`.
- Delete Profile prevents self-deletion (API checks `targetAdmin[0].email === session.email` → 403).
- Delete Profile refreshes pagination after deletion.
- **Role sanitization**: `role === 'superadmin' ? 'superadmin' : role === 'user' ? 'user' : 'admin'` in both POST and PUT handlers.
- **Role badge colors**: `superadmin` = purple, `user` = blue, `admin` = emerald.
- Add Profile modal role dropdown order: User first, Admin second, Super Admin third (superadmin only).
- Session role fetched from `/api/admin/auth/verify` on component mount.

---

## 4. Visitor Chat Interface Rules

### 4.1 Column Borders
- Column dividers: `border-r-2` / `border-l-2` with `zinc-300/70` — never use `zinc-100` or `zinc-200`.

### 4.2 Section Separators
- Must use `border-b-2` / `border-t-2` with `zinc-300/70` — never use single-pixel borders (`border-b`).

### 4.3 Input/Textfield Borders
- Must use `border-2` with `zinc-300` — never use `border` (1px) or `zinc-200`.

### 4.4 Message Bubbles
- Must have `shadow-sm` — both admin and visitor message bubbles.

### 4.5 Avatars
- Must have `ring-2 ring-white shadow-sm`.

### 4.6 Action Buttons
- Must have visible borders: `border border-emerald-300` for primary, `border border-zinc-300` for secondary.

### 4.7 Draft Area
- Must have `bg-zinc-50/30` background to visually separate from message thread.

### 4.8 Error/Info Banners
- Must use `border-2` with `shadow-sm`.

---

## 5. Workout Planner Rules

### 5.1 Card Visibility (CRITICAL)
- Card borders: `border-zinc-400` or stronger — never `border-zinc-300` or lighter.
- Card description text: `text-zinc-800` — never `text-zinc-700` or lighter.
- Card stats: `text-zinc-700` — never `text-zinc-600` or lighter.
- Goal tags: `bg-emerald-100 text-emerald-800` with `border-emerald-300` — never `bg-emerald-50` or `text-emerald-700`.
- Card padding: `p-5` with `space-y-3` — never `p-8` or `space-y-6`.
- Grid gap: `gap-5` — never `gap-8`.

### 5.2 GSAP Animation
- Must use `useRef` + `querySelectorAll` — never `gsap.from(".program-card", ...)` as it may miss cards below the fold.
- Always use `gridRef.current.querySelectorAll(".program-card")`.

### 5.3 API Routes
- Must use shared `getDb()` from `@/lib/workout-planner/db` — never import raw `sqlite3` directly in fitness API routes.
- Dashboard must use `/api/fitness/programs?userId=...&status=active` — never `/api/fitness/programs/active`.
- Dashboard must NOT import from `next-auth/react` — uses sessionStorage-based auth.

### 5.4 My Plans Page — No GSAP Re-animation (CRITICAL)
- **NEVER add a `useEffect` with `gsap.from(".plan-card", ...)` that depends on `[programs]`** — Every time state changes (e.g., clicking "Set Active"), GSAP re-runs the entrance animation on all cards, causing visible glitching/jumping.
- **GSAP animation on page load is fine** — Only use `useEffect` with `[]` dependency (run once on mount), or remove GSAP entirely.
- **Removed imports**: `useRouter` and `gsap` should not be imported if not used.

### 5.5 Program Structure Page — Exercise Table Formatting (CRITICAL)
- **Use proper HTML `<table>` with `<thead>` and `<tbody>`** — Never use CSS grid (`grid-cols-13`) for exercise tables as column spans mismatch between headers and rows.
- **Table columns**: Select (checkbox) | Exercise | Muscle Group | Sets | Reps | Rest (sec) | Equipment
- **Input fields**: Each input (Sets, Reps, Rest) must be in its own `<td>` with fixed width (`w-14`, `w-16`) and centered text.
- **Responsive**: Muscle Group and Equipment columns should use `hidden md:table-cell` to hide on mobile.
- **Container**: Add `overflow-x-auto` to the table wrapper for horizontal scroll on narrow screens.

### 5.6 Program Structure Page — Workout Complete Overlay (CRITICAL)
- **`showWorkoutComplete` state** (line 116): `const [showWorkoutComplete, setShowWorkoutComplete] = useState(false);` — DO NOT rename or remove this state variable.
- **`handleCompleteSet()` trigger** (line 398-425): When `currentSet >= totalSets` AND `currentExerciseIndex >= exercises.length - 1`, the function MUST set `setShowWorkoutComplete(true)` — DO NOT change this logic.
- **Workout Complete Overlay** (lines 814-843): A full-screen overlay (`fixed inset-0 z-50`) that displays:
  - A green `check_circle` Material Symbol icon (`text-7xl text-emerald-400`)
  - "Workout progress saved" heading (`text-4xl md:text-5xl font-serif italic`)
  - "Great work! Your progress has been recorded." message
  - **Dashboard button**: Links to `/fitness-planner/dashboard` with `bg-emerald-900 text-white` styling
  - **Workout Plans button**: Links to `/fitness-planner/my-plans` with `bg-zinc-100 dark:bg-zinc-800` styling
- **DO NOT** remove, rename, or restructure the overlay. DO NOT change the navigation links or button labels.
- **DO NOT** add additional buttons or options to the overlay without updating this rule.

### 5.7 Program Structure Page — Pointing Hand / Exercise Selection Prompt (CRITICAL)

#### 5.7.1 State Variable
- **`hasClickedExercise`** (line 156): `const [hasClickedExercise, setHasClickedExercise] = useState(false);` — DO NOT rename or remove this state variable.
- Initialized to `false` — the pointing hand shows until user clicks an exercise.

#### 5.7.2 Trigger
- **`handleSelectExercise()`** (lines 385-397) MUST call `setHasClickedExercise(true)` when any exercise is clicked — DO NOT remove this call.
- The function signature is: `function handleSelectExercise(dayId: string, exerciseId: string)`

#### 5.7.3 Conditional Render Block (lines 1349-1359)
- The pointing hand 👈 and instructional text render when `!hasClickedExercise`:
```tsx
{!hasClickedExercise ? (
  <div className="flex items-center gap-2 animate-pointing-hand mr-2">
    <span className="text-lg md:text-xl" style={{ display: 'inline-block', animation: 'pointLeft 1s ease-in-out infinite' }}>
      👈
    </span>
    <span className="text-[8px] font-bold uppercase tracking-widest text-emerald-600 dark:text-emerald-400 whitespace-nowrap max-w-[160px] leading-tight">
      First choose a starting exercise on the left panel then return here to start
    </span>
  </div>
) : null}
```
- **DO NOT** remove, rename, or restructure this conditional block.
- **DO NOT** re-add the old checkbox in place of the pointing hand.
- The `animate-pointing-hand` class and inline `pointLeft` animation must remain.

#### 5.7.4 CSS Animation (`src/app/globals.css` lines 405-413)
```css
@keyframes pointLeft {
  0%, 100% { transform: translateX(0); }
  50% { transform: translateX(-8px); }
}
```
- **DO NOT** remove or rename the `@keyframes pointLeft` animation.
- **DO NOT** remove the `.animate-pointing-hand` class usage.

### 5.8 My Plans Page Font Sizes
- Subtitle: `text-5xl md:text-6xl text-black dark:text-white font-serif italic`
- Bookmark icon: `text-3xl text-black dark:text-white`
- Plans count: `text-lg font-bold uppercase tracking-widest text-black dark:text-white`
- Saved Programs heading: `text-lg font-bold uppercase tracking-widest text-black dark:text-white`
- Saved program descriptions: `text-3xl text-black dark:text-white leading-relaxed line-clamp-2`
- Active program description: `text-black dark:text-white font-serif italic text-2xl`

---

## 6. Shop / Product Preview Rules

### 6.1 Product Listing Card (`src/app/shop/[category]/page.tsx`)
- **Compact spacing**: Card info area uses `p-5`, `mt-0.5`, `pt-1.5`, `pt-2`, `gap-2`, `py-3`.
- **DO NOT** increase padding/margins on: `.p-5` (card), `.mt-0.5` (brand), `.pt-1.5` (price), `.pt-2` (button wrapper), `.gap-2` (button gap), `.py-3` (button).
- **DO NOT** change font sizes, weights, or colors on card elements.
- **Uniform heights**: Cards use `flex-1` + `mt-auto` to maintain equal height regardless of text length.
- **Button label**: "View Details" (not "Shop at Partner").

### 6.2 Product Preview Modal
- **Wide modal**: `max-w-[1600px]`, `w-[98vw]`, `max-h-[92vh]` — DO NOT narrow these values.
- **No inner wrapper**: The `max-w-6xl mx-auto` wrapper was removed — DO NOT re-add it.
- **Column split**: Image `md:w-[30%]`, Article `md:w-[70%]` — DO NOT reduce article width.
- **Content height**: `maxHeight: '700px'` on both columns — DO NOT reduce this.
- **Padding**: Modal wrapper `p-6`, article column `p-6` — DO NOT increase these.
- **CTA label**: "Shop Now" (not "Shop at Partner").

### 6.3 Preview Article Typography (`.preview-article-content`)
- **Base font**: `20px` with `line-height: 1.7` — DO NOT reduce font size below 18px.
- **H1**: `34px` with `line-height: 1.3`.
- **H2**: `28px` with `line-height: 1.35`.
- **H3**: `24px` with `line-height: 1.4`.
- **Paragraph/List**: `line-height: 1.7`.
- All preview typography is scoped to `.preview-article-content` — no global impact.

### 6.4 Price Range Filter
- Default `priceRange`: `1000` (not `500`).
- Slider `max`: `1000` (not `500`).

---

## 7. Analytics & Tracking Rules

### 7.1 Token Tracking
- MUST log after every DeepSeek API call in `deepseek-service.ts`.
- MUST log after every AI draft generation in `draft/route.ts`.
- `getDailyTokenBreakdown()` in `token-tracker.ts` must return `inputTokens`/`outputTokens` from SQL SUM of `input_tokens` and `output_tokens` columns.

### 7.2 Scraping Tracking
- MUST log after every Tavily API call in `content-research.ts`.

### 7.3 AnalyticsProvider Placement
- MUST be placed **inside** `<Providers>` wrapper in `src/app/layout.tsx` — never outside.

---

## 8. Admin Layout & Auth Rules

### 8.1 Sidebar
- All `AdminNavLink` components must have `prefetch={true}` — never omit prefetch on admin nav links.

### 8.2 Auth
- Admin uses JWT via `jose` library, stored in `admin_session` cookie.
- Fitness users use sessionStorage-based auth (NOT NextAuth).
- Admin login flow: email/password → OTP verification → JWT session.

### 8.3 Import Paths
- All imports in `src/app/admin/visitors/page.tsx` must use `@/` alias path — never use relative imports (`./`) for same-directory modules.
- `VisitorChatInterface` must use `@/components/admin/VisitorChatInterface`.
- `RegisteredMembersSection` must use `@/app/admin/visitors/RegisteredMembersSection`.

---

## 9. Database & Migration Rules

### 9.1 SQLite Schema
- Dual-mode DB: SQLite primary (no MySQL currently active).
- All queries use `@/lib/db` helper.
- `Product` table has `faqs` as TEXT (JSON stringified).
- `ProductArticle` table has `richTextState` as TEXT (JSON serialized).
- `user_profiles` uses `user_email` as PK (not `id`).

### 9.2 Migration Pattern
- Migrations are SQL files in `migrations/` directory.
- Run via `node scratch/run_*_migration.js` scripts.
- ALTER TABLE must use `IF NOT EXISTS` for idempotency.

### 9.3 Admin Credentials
- `mngarii@gmail.com` / `admin32i`
- `magdah785@gmail.com` / `admin32i`
- Passwords are bcrypt-hashed via `setup_admin.js`.

---

## 10. Build & Dev Server Rules

### 10.1 Dev Server
- Use `next dev --turbopack` (NOT plain `next dev` which has webpack persistent cache races on Windows).
- If Turbopack cache corrupts: clear `.next-dev` directory and restart.
- If webpack dev path is ever needed: add project directory to Windows Defender exclusions to avoid file-lock race.

### 10.2 Build Verification
- `next build` must compile with zero errors.
- `tsc --noEmit` must produce zero type errors.

### 10.3 VS Code Settings
- `css.lint.unknownAtRules`: `"ignore"` — suppresses false positive warnings for `@tailwind` directives.
- `js/ts.preferences.importModuleSpecifier` — use this (NOT deprecated `typescript.preferences.importModuleSpecifier`).

---

## 11. CSS & Styling Rules

### 11.1 globals.css
- `.ProseMirror` styles handle editor content (NOT `prose prose-sm` class from `@tailwindcss/typography`).
- Blockquote: `border-left: 3px solid #10b981` with CSS `quotes` property for quotation marks.
- `em`, `u`, `strong` must have `!important` to prevent Tailwind CSS reset overrides.
- `.article-content` class provides premium typography for product articles.

### 11.2 Tailwind Config
- `@tailwindcss/typography` plugin is installed and configured.
- Custom colors: `primary`, `surface`, `on-surface`, `primary-container`, `on-primary-container`.

### 11.3 Color Scheme
- Primary: `#064e3b` (emerald-900).
- Secondary: `#059669` (emerald-600).
- Border radius: 12px (`rounded-xl`) and 32px (`rounded-[2rem]`).

---

## 12. Import & Module Rules

### 12.1 CSS Side-effect Imports
- `global.d.ts` at project root declares `declare module '*.css'` plus common stylesheet extensions.
- `tsconfig.json` includes `**/*.d.ts` so the declaration is recognized.

### 12.2 Next.js Config
- Use default `.next` output directory (NOT custom `.next-dev`).
- `next-env.d.ts` points at `./.next/types/routes.d.ts`.

### 12.3 Package.json
- Dev script: `"dev": "next dev --turbopack"`.

---

## 13. API Route Rules

### 13.1 Products API (`/api/products`)
- **GET**: Joins `Product p` with `Category c`, supports `category`, `affiliate`, `page`, `limit`, `published` query params.
- **POST**: UUID v4 ID generation, inserts all fields including `commission`, `revenue`, `cookieLife`.
- **PUT**: Updates all fields, returns `{ product: { ...product, id: result.lastInsertRowid } }`.
- **DELETE**: Deletes by `id` query param.
- Error handling: Use `req.clone().text()` instead of `req.text()` to avoid "body stream already read" error.

### 13.2 Products Upload API (`/api/products/upload`)
- Accepts multipart form upload via `formidable`.
- Sharp pipeline: resize to 800×800 (`fit: 'inside'`), convert to WebP (quality 75).
- Sharp fallback: save original file with original extension if Sharp fails.
- Returns `{ success: true, url, filename, size }`.

### 13.3 Products Article API (`/api/products/article`)
- **POST**: Save article content with `productId`, `productDescription`, `richTextState`.
- **GET**: Retrieve saved article by `productId`. Falls back to `Product.productDescription` if no `ProductArticle` record exists.

### 13.4 Content Intelligence Generate API (`/api/content-intelligence/generate`)
- Pipeline: DeepSeek crafts research query → 1 Tavily call → DeepSeek generates article.
- **3 Tavily calls per generation was reduced to 1** via DeepSeek query crafting.
- Returns `{ content: { articleHtml, ... } }`.

### 13.5 Settings API (`/api/admin/settings`)
- **GET**: Returns all settings including `tokenDailyBreakdown` from `getDailyTokenBreakdown()`.
- **PUT**: Updates settings in `admin_settings` table.

### 13.6 Issues API (`/api/admin/issues`)
- **GET**: Returns issues with optional `period` query param. Returns `issues`, `severityTotals`, `statusCounts`, `categoryCounts`, `totalIssues`.
- **POST**: Creates new issue with title, category, severity, description, steps_to_reproduce.
- **PUT**: Updates issue status.

### 13.7 Admins API (`/api/admin/admins`)
- **GET**: Paginated list with `page` and `limit` params.
- **POST**: Create admin with email, username, password, role. Validates email uniqueness, password min 6.
- **PUT**: Update admin. Email uniqueness check excludes current admin. Only superadmin can change roles.
- **DELETE**: Delete admin. Prevents self-deletion (403 if `targetAdmin[0].email === session.email`).

### 13.8 Fitness API Routes
- All must use shared `getDb()` from `@/lib/workout-planner/db` — never raw `sqlite3`.
- Programs: `/api/fitness/programs?userId=...&status=active` (NOT `/api/fitness/programs/active`).

---

## 14. Content Intelligence Rules

### 14.1 DeepSeek + Tavily Pipeline
- **Step 1**: DeepSeek `generateResearchQuery()` crafts ONE optimized Tavily search query from product data.
- **Step 2**: Tavily `singleResearchCall()` executes the query (1 call total).
- **Step 3**: DeepSeek `generateContent()` produces final SEO-optimized article.
- Total: **1 Tavily call per generation** (reduced from 3).

### 14.2 Legacy Functions
- `searchProductResearch()`, `searchComparisonData()`, `consolidatedFAQResearch()`, `batchSearchFAQs()` — kept for backward compatibility but NOT used by the generate route.
- `generateSEOBlogPost()` — legacy client-side template function, NOT used by "Generate Article" button.

### 14.3 Error Handling
- Always check `!res.ok` before `res.json()` in ALL Content Intelligence API calls.
- Fallback query generation if DeepSeek API fails.

---

## 15. Fitness Planner Rules

### 15.1 Profile Save
- SQLite schema migration runs on every POST request via `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` block.
- Idempotent: gracefully skips columns that already exist.

### 15.2 Form Field Mapping
- `mapFormDataToApi()` function maps form field names to API expectations:
  - `formData.goal` → `apiData.goals`
  - `formData.level` → `apiData.fitness_level`
  - `formData.days` → `apiData.days_per_week`

### 15.3 Progress Display
- `visibleSteps` computed based on `focus` to exclude irrelevant steps (dietary, calories, etc. for "workout" focus).
- Progress bar shows correct fraction (e.g., "6 out of 6 steps").

### 15.4 Dashboard Workout Plan Extraction
- Use `(workoutPlan?.dailyWorkouts?.[0]?.exercises || fallbackWorkoutPlan).slice(...)` — NOT `(workoutPlan || fallbackWorkoutPlan).slice(...)` which crashes when workoutPlan is an object.

### 15.5 Calorie Tracker / Meal Planner
- Must show "Coming Soon" badge with `isComingSoon` flag.
- Coming Soon text and icon must be legible (increased size, padding, shadow).

### 15.6 Program Library Page (`src/app/fitness-planner/library/page.tsx`)
- **Inline navigation**: Uses its own inline nav with brand link + back arrow — NOT `WorkoutNav` component. Do NOT replace with WorkoutNav unless layout is intentionally redesigned.
- **GSAP animation pattern**: Cards use `gsap.fromTo()` with `{ opacity: 0, y: 30 }` → `{ opacity: 1, y: 0, stagger: 0.06, duration: 0.5, ease: "power3.out" }` triggered by `filteredTemplates` change.
- **Save Plan flow**: `handleSaveProgram()` first checks user profile (`/api/user/profile`), alerts and redirects to `/fitness-planner` if no email found, then POSTs to `/api/fitness/programs`.
- **Set Active flow**: `handleSetActive()` first checks if program is already saved; if not, saves with `is_active: true`; if saved, finds user program by `template_id` and PUTs `is_active: true`, then redirects to `/fitness-planner/dashboard`.
- **LEVEL_BADGE_STYLES**: beginner = `bg-emerald-600 text-white`, intermediate = `bg-blue-600 text-white`, advanced = `bg-orange-600 text-white`.
- **Card footer**: Always has "Select Workout" Link (left) + conditional button: "Active" (disabled green), "Set Active" (clickable), or "Save Plan" (clickable).

### 15.7 WorkoutNav Component (`src/components/WorkoutNav.tsx`)
- **currentPage prop**: Accepts `'dashboard' | 'my-plans' | 'program' | 'library'`.
- **Used by**: `my-plans/page.tsx` (currentPage="my-plans"), `program/[programId]/page.tsx` (currentPage="program").
- **NOT used by**: `library/page.tsx` (uses inline nav).
- **Link styling**: `text-[18px] font-bold uppercase tracking-widest` with emerald-600 active state.
- **Links**: Dashboard → `/fitness-planner/dashboard`, Workout Plans → `/fitness-planner/my-plans`, Change Workout Goal → `/fitness-planner`.

### 15.8 Program Structure Page (`src/app/fitness-planner/program/[programId]/page.tsx`)
- **File size**: 1855 lines, 86,152 bytes — EXTREME CAUTION required for modifications.
- **WorkoutNav**: Uses `<WorkoutNav currentPage="program" />` at line 865.
- **Pointing hand state**: `hasClickedExercise` (line 156) is set to true in `handleSelectExercise()` . **CRITICAL**: Renders when `!hasClickedExercise`.
- **Set tracking**: Auto-saves every 10 seconds during workout mode via `setInterval` in useEffect (lines 195-200).
- **Rest timer capture fix (CRITICAL)**: `captureRestTime()` uses `lastCompletedSetRef.current` (NOT `currentSet - 1`) to determine which set's rest data to save. When completing the last set of an exercise, `currentSet` gets reset to 1 for the next exercise before the rest timer starts. Using `lastCompletedSetRef` (set in `handleCompleteSet()`) ensures rest time is always saved to the correct set. DO NOT revert to `currentSet - 1` -- this broke rest capture for the last set of each exercise.
- **Two-column layout**: Left panel (collapsible, `w-[35%]`) + Right panel (exercise details + set tracking table) during workout mode.



---

## APPENDIX: Quick Reference — Files That Must NOT Be Modified Without Full Understanding

| File | Risk Level | Reason |
|------|-----------|--------|
| `src/app/admin/products/page.tsx` | 🔴 CRITICAL | 2143 lines, 20+ state vars, complex preview/edit/create flow |
| `src/components/RichTextToolbar.tsx` | 🔴 CRITICAL | TipTap extension conflicts, image picker, resize, drag-drop |
| `src/components/admin/SettingsPanel.tsx` | 🔴 CRITICAL | 7 tabs, 12+ issue state vars, plugin management, token charts |
| `src/app/shop/[category]/page.tsx` | 🟡 HIGH | Card spacing, preview modal dimensions, typography |
| `src/app/globals.css` | 🟡 HIGH | ProseMirror, preview-article-content, blockquote styles |
| `src/app/layout.tsx` | 🟡 HIGH | AnalyticsProvider placement, editor bridge script |
| `src/app/api/products/route.ts` | 🟡 HIGH | PUT returns id, error handling, body stream fix |
| `src/lib/content-intelligence/deepseek-service.ts` | 🟡 HIGH | Token tracking hook, research query generation |
| `src/lib/content-intelligence/content-research.ts` | 🟡 HIGH | Scraping tracking hook, singleResearchCall |
| `src/components/admin/VisitorChatInterface.tsx` | 🟡 HIGH | Border rules, column structure, draft area |
| `src/app/fitness-planner/library/page.tsx` | 🟡 HIGH | 391 lines, GSAP card animation, inline nav (no WorkoutNav), save/active program flow, filter tabs, level badge color rules |
| `src/app/fitness-planner/dashboard/page.tsx` | 🟡 HIGH | Workout plan extraction, no next-auth import |
| `src/app/admin/layout.tsx` | 🟡 HIGH | Sidebar prefetch, auth gate |
| `src/app/admin/visitors/page.tsx` | 🟢 MEDIUM | Import path rules |
| `src/lib/workout-planner/db.ts` | 🟢 MEDIUM | Shared DB module for fitness routes |
| `src/app/api/admin/admins/route.ts` | 🟢 MEDIUM | Role sanitization, self-deletion prevention |
| `src/app/api/admin/issues/route.ts` | 🟢 MEDIUM | Issue CRUD with period aggregation |
| `src/app/api/admin/settings/route.ts` | 🟢 MEDIUM | Token daily breakdown in response |
| `src/app/api/products/upload/route.ts` | 🟢 MEDIUM | Sharp fallback, fit: 'inside' |
| `src/app/api/content-intelligence/generate/route.ts` | 🟢 MEDIUM | 1 Tavily call pipeline |
| `src/app/fitness-planner/program/[programId]/page.tsx` | 🔴 CRITICAL | 1855 lines / 86KB, pointing hand state, WorkoutNav integration, set tracking, auto-save timer, two-column workout layout |
| `src/app/fitness-planner/my-plans/page.tsx` | 🟢 MEDIUM | Font size rules |
| `src/app/fitness-planner/page.tsx` | 🟢 MEDIUM | Form mapping, progress display, coming soon |
| `src/app/api/user/profile/route.ts` | 🟢 MEDIUM | SQLite schema migration on POST |
| `src/components/WorkoutNav.tsx` | 🟢 MEDIUM | 35 lines, currentPage prop type, used by my-plans + program pages (NOT library page) |
| `src/components/admin/AffiliateDashboard.tsx` | 🟢 MEDIUM | Print/PDF/Word export functions |
| `src/app/api/publish/article-data/[productId]/route.ts` | 🟢 MEDIUM | Promise<{ productId }> params type |


### 15.9 Email Verification System
- **verification_tokens table**: Stores tokens with 5-min expiry. Tokens are one-time use (used flag).
- **src/lib/verify.ts**: Uses crypto.randomBytes(24) for tokens. NEVER change to Math.random().
- **src/app/verify-email/page.tsx**: useSearchParams MUST be wrapped in Suspense boundary.
- **SMTP config**: Uses existing .env vars (SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS).

### 15.10 Subscription Dev Testing
- **API mode override**: Add ?mode=trial|premium|expired|bypass to test subscription states.
- **Dev page**: public/dev-subscription.html - DELETE before production deployment.
- **Default behavior**: No mode param = normal subscription logic from profile creation date.

### 15.11 Template Generator
- **scripts/seed-templates-all.py**: Safe to re-run (INSERT OR IGNORE). Generates 105 templates.
- **Exercise pools**: Maps muscle groups x difficulty. Edit to add/modify exercises.
- **Level scaling**: beginner=3x10-12, intermed=4x8-10, advanced=5x5-8.

