Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
833 lines
36 KiB
Markdown
833 lines
36 KiB
Markdown
# Phase 9 — Content Modernization, Publication Features & SEO Enrichment
|
||
|
||
## Status: Implemented; Linux validation pending
|
||
|
||
Implemented in this pass:
|
||
|
||
- Additive Prisma models/fields for SEO metadata, article view logs, newsletter campaigns, article series, editorial reviews, comment likes, and reading lists.
|
||
- Backend APIs for related articles, public series pages, newsletter unsubscribe, admin analytics, review workflow, newsletter campaigns, series management, comment likes, and authenticated reading lists.
|
||
- Frontend API clients and visible admin panels for analytics, reviews, newsletter campaigns, and series management.
|
||
- Article page support for comment likes, API-backed related articles, author/profile navigation, and richer SEO metadata consumption.
|
||
- Seed/reference content now finalizes 23 published articles, refreshes old dates/slugs, promotes former draft/review items, and adds Phase 9 SEO fields.
|
||
- Sitemap/RSS generation now uses canonical paths, `dateModified`, `metaDescription`, and full-content RSS payloads.
|
||
|
||
Not run locally by request: frontend/backend builds and Prisma generation. Validate on Linux with the commands in `ARCH.md`/`AGENTS.md`.
|
||
|
||
All work targets the `SERVER/` directory. Content research uses web search and Context7 for technical accuracy. Schema changes are additive (nullable/defaulted) and backward-compatible.
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
Phase 9 has three pillars:
|
||
|
||
1. **Content modernization.** 17 of 18 articles have placeholder content. All are dated 1403. AI model references are two generations stale. The publication needs to be current, factually accurate, and complete.
|
||
2. **Publication features.** Ten features a real tech publication needs but Teknav currently lacks: newsletter campaigns, article series, related articles, comment votes, writer analytics, syntax highlighting, social share, admin time-series charts, editorial review workflow, and a bookmark/reading-list system.
|
||
3. **SEO enrichment.** Every article needs `metaDescription`, Open Graph metadata, JSON-LD structured data, and a canonical URL. OG images generated locally. Sitemap and RSS updated.
|
||
|
||
---
|
||
|
||
## 9.1 — Article Content Expansion
|
||
|
||
### Goal
|
||
Full Persian prose for all 17 stub articles + refresh art1. Every article must be factually current as of Ordibehesht 1405 / April 2026.
|
||
|
||
### Research Process (per article)
|
||
1. **Web search** the topic for the latest benchmarks, releases, and numbers.
|
||
2. **Context7** for articles touching library APIs, language specs, or framework internals (Rust, Kubernetes, WebAssembly).
|
||
3. Apply the model reference mapping below throughout.
|
||
|
||
### Model Reference Update Table
|
||
|
||
| Outdated reference | Replace with (1405 / 2026) |
|
||
|---|---|
|
||
| Claude 3 Opus | Claude Opus 4.7 |
|
||
| GPT-4 / GPT-4 Turbo | GPT-4o / o3 |
|
||
| Gemini Ultra / Gemini 1.5 | Gemini 2.5 Pro |
|
||
| Phi-3 | Phi-4 |
|
||
| Mistral 7B / Mistral Large | Mistral Large 2 |
|
||
| Llama 3.2 | Llama 4 Scout / Maverick |
|
||
| H100 (as cutting-edge) | GB200 / Blackwell |
|
||
| B200 (as upcoming) | B200 (shipping), GB200 NVL72 (current flagship) |
|
||
| NeurIPS 2024 / ICML 2024 | NeurIPS 2025 / ICML 2025 |
|
||
|
||
### Content Requirements (per article)
|
||
- Minimum 800 words of substantive Persian prose.
|
||
- At least two `<h2>` and one `<h3>`.
|
||
- One `<blockquote>` citing a real expert or published finding.
|
||
- One `<div class="insight-box">` with a key takeaway.
|
||
- One `<div class="footnote-box">` citing sources.
|
||
- Factually current as of Ordibehesht 1405.
|
||
|
||
### Article Date Refresh
|
||
|
||
| ID | Slug | New Date (Persian) | New dateEn |
|
||
|---|---|---|---|
|
||
| art1 | ai-clean-data | ۱۴۰۵/۰۲/۰۱ | 2026-04-21 |
|
||
| art2 | custom-ai-chips | ۱۴۰۵/۰۱/۲۵ | 2026-04-14 |
|
||
| art3 | browser-architecture | ۱۴۰۵/۰۱/۲۰ | 2026-04-09 |
|
||
| art4 | software-supply-chain | ۱۴۰۵/۰۱/۱۵ | 2026-04-04 |
|
||
| art5 | iranian-startups-scale | ۱۴۰۵/۰۱/۱۰ | 2026-03-30 |
|
||
| art6 | small-llms | ۱۴۰۵/۰۱/۰۵ | 2026-03-25 |
|
||
| art7 | edge-computing | ۱۴۰۴/۱۲/۲۵ | 2026-03-16 |
|
||
| art8 | synthetic-data | ۱۴۰۴/۱۲/۱۵ | 2026-03-06 |
|
||
| art9 | ai-hardware-roadmap | ۱۴۰۴/۱۲/۰۵ | 2026-02-24 |
|
||
| art10 | quantum-computing | ۱۴۰۴/۱۱/۲۵ | 2026-02-14 |
|
||
| art11 | microservices-vs-monolith | ۱۴۰۴/۱۱/۱۵ | 2026-02-04 |
|
||
| art12 | fintech-regulation | ۱۴۰۴/۱۱/۰۵ | 2026-01-25 |
|
||
| art13 | llm-security | ۱۴۰۴/۱۰/۲۵ | 2026-01-15 |
|
||
| art14 | data-governance | ۱۴۰۴/۱۰/۱۵ | 2026-01-05 |
|
||
| art15 | robotics-warehouse | ۱۴۰۴/۱۰/۰۵ | 2025-12-26 |
|
||
| art16 | open-source-ai | ۱۴۰۴/۰۹/۲۵ | 2025-12-16 |
|
||
| art17 | rust-systems | ۱۴۰۴/۰۹/۱۵ | 2025-12-06 |
|
||
| art18 | venture-capital-ai | ۱۴۰۴/۰۹/۰۵ | 2025-11-26 |
|
||
|
||
Rename art10 slug: `quantum-computing-2024` → `quantum-computing`.
|
||
|
||
---
|
||
|
||
## 9.2 — New Articles (1405 Topics)
|
||
|
||
Five new articles on topics that did not exist or were not prominent in 1403.
|
||
|
||
| ID | Slug | Title | Category | Author | Date |
|
||
|---|---|---|---|---|---|
|
||
| art19 | claude-opus-47-reasoning | کلود اوپوس ۴.۷ و تحول در مدلهای استدلالی | ai | آرسام صباغ | ۱۴۰۵/۰۲/۰۸ |
|
||
| art20 | agentic-ai-production | هوش مصنوعی عاملی از آزمایشگاه تا تولید | ai | آرسام صباغ | ۱۴۰۵/۰۲/۰۵ |
|
||
| art21 | vibe-coding | وایبکدینگ و آینده برنامهنویسی با هوش مصنوعی | software | سیداحمدرضا محجوب | ۱۴۰۵/۰۱/۲۸ |
|
||
| art22 | ai-energy-crisis | بحران انرژی مراکز داده: قیمت واقعی هوش مصنوعی | future | رادمان قلیچی | ۱۴۰۵/۰۱/۲۲ |
|
||
| art23 | llama4-open-frontier | Llama 4 و مرز جدید مدلهای متنباز | ai | آرسام صباغ | ۱۴۰۵/۰۱/۱۸ |
|
||
|
||
**art19:** Claude Opus 4.7 معرفی، مقایسه با o3 و Gemini 2.5 Pro، بنچمارکهای MATH/GPQA/SWE-bench، دلالتهای عملی برای توسعهدهندگان.
|
||
|
||
**art20:** چرخه think-act-observe، معماریهای multi-agent، ریسکهای autonomy، مقایسه Claude Code، OpenAI Operator، Google Mariner.
|
||
|
||
**art21:** تعریف Vibe Coding (اصطلاح Karpathy)، تأثیر بر چرخه توسعه، نقد جدیپذیری، مقایسه Cursor، Copilot، Claude Code.
|
||
|
||
**art22:** مصرف برق مراکز داده AI (500+ TWh در 2026)، چالشهای شبکه، PUE، تأثیر بر اهداف کربن.
|
||
|
||
**art23:** Llama 4 Scout/Maverick/Behemoth، معماری MoE، مقایسه با GPT-4o و Claude Sonnet 4.6، استقرار on-premise.
|
||
|
||
---
|
||
|
||
## 9.3 — SEO Metadata Layer
|
||
|
||
### New Fields per Article in `teknav-data.js`
|
||
|
||
```js
|
||
{
|
||
metaDescription: '...', // 120–155 chars, Persian plain text
|
||
keywords: ['کلمه ۱', ...], // 6–10 Persian terms
|
||
ogTitle: '...', // og:title override (defaults to title)
|
||
ogDescription: '...', // 200–300 chars for og:description
|
||
ogImage: '/images/og/slug.jpg',
|
||
canonicalPath: '/articles/slug',
|
||
dateModified: '2026-...', // ISO — for JSON-LD dateModified and sitemap <lastmod>
|
||
}
|
||
```
|
||
|
||
### Prisma Schema Changes
|
||
|
||
```prisma
|
||
model Article {
|
||
// existing fields unchanged ...
|
||
metaDescription String? @db.VarChar(160)
|
||
keywords String[] @default([])
|
||
ogTitle String? @db.VarChar(200)
|
||
ogDescription String? @db.VarChar(300)
|
||
ogImage String?
|
||
canonicalPath String?
|
||
dateModified DateTime?
|
||
}
|
||
```
|
||
|
||
All nullable/defaulted — no migration failure on existing rows. After change: `prisma:generate` → `prisma:apply`.
|
||
|
||
### `seed.ts` Update
|
||
|
||
Extend the article upsert to include all new SEO fields.
|
||
|
||
### Article Detail Page `<head>` Block
|
||
|
||
```html
|
||
<title>{title} | تکناو</title>
|
||
<meta name="description" content="{metaDescription}" />
|
||
<meta name="keywords" content="{keywords.join(',')}" />
|
||
<link rel="canonical" href="https://www.teknav.ir{canonicalPath}" />
|
||
|
||
<meta property="og:type" content="article" />
|
||
<meta property="og:title" content="{ogTitle || title}" />
|
||
<meta property="og:description" content="{ogDescription || metaDescription}" />
|
||
<meta property="og:image" content="https://www.teknav.ir{ogImage}" />
|
||
<meta property="og:url" content="https://www.teknav.ir{canonicalPath}" />
|
||
<meta property="og:locale" content="fa_IR" />
|
||
<meta property="og:site_name" content="تکناو" />
|
||
<meta property="article:published_time" content="{dateEn}" />
|
||
<meta property="article:modified_time" content="{dateModified}" />
|
||
<meta property="article:author" content="{authorName}" />
|
||
<meta property="article:section" content="{categoryName}" />
|
||
|
||
<meta name="twitter:card" content="summary_large_image" />
|
||
<meta name="twitter:title" content="{ogTitle || title}" />
|
||
<meta name="twitter:description" content="{metaDescription}" />
|
||
<meta name="twitter:image" content="https://www.teknav.ir{ogImage}" />
|
||
```
|
||
|
||
### JSON-LD Structured Data
|
||
|
||
Inject `<script type="application/ld+json">` in article detail page:
|
||
|
||
```json
|
||
{
|
||
"@context": "https://schema.org",
|
||
"@type": "Article",
|
||
"headline": "{title}",
|
||
"description": "{metaDescription}",
|
||
"datePublished": "{dateEn}",
|
||
"dateModified": "{dateModified}",
|
||
"inLanguage": "fa",
|
||
"author": {
|
||
"@type": "Person",
|
||
"name": "{authorName}",
|
||
"url": "https://www.teknav.ir/profile/@{authorUsername}"
|
||
},
|
||
"publisher": {
|
||
"@type": "Organization",
|
||
"name": "تکناو",
|
||
"url": "https://www.teknav.ir",
|
||
"logo": { "@type": "ImageObject", "url": "https://www.teknav.ir/favicon.png" }
|
||
},
|
||
"image": "https://www.teknav.ir{ogImage}",
|
||
"mainEntityOfPage": { "@type": "WebPage", "@id": "https://www.teknav.ir{canonicalPath}" }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 9.4 — OG Image Generation
|
||
|
||
Node script `scripts/generate-og-images.mjs` uses `@napi-rs/canvas` (npm, zero CDN) to produce 23 × 1200×630 JPEG files in `public/images/og/`. Each image: category brand color background, site name top-right, article title centered (Vazirmatn font loaded locally), category badge bottom-right.
|
||
|
||
---
|
||
|
||
## 9.5 — SEO Artifacts Refresh
|
||
|
||
- Update `scripts/generate-seo.mjs` to use `dateModified` in sitemap `<lastmod>` and `metaDescription` in RSS `<description>`.
|
||
- RSS `<content:encoded>` must include the full article HTML.
|
||
- Run `npm run seo:sitemap` after all content changes.
|
||
|
||
---
|
||
|
||
## 9.6 — Article Status Cleanup
|
||
|
||
art17 (`rust-systems`) is `پیشنویس` and art18 (`venture-capital-ai`) is `در انتظار بررسی`. Both get full content in Phase 9 and are promoted to `منتشرشده`.
|
||
|
||
---
|
||
|
||
## 9.7 — Newsletter Campaign System
|
||
|
||
### Goal
|
||
The platform already collects subscriber emails. Phase 9 closes the loop: admins compose and send a newsletter campaign to all confirmed subscribers directly from the admin panel.
|
||
|
||
### Schema
|
||
|
||
```prisma
|
||
model NewsletterCampaign {
|
||
id String @id @default(cuid())
|
||
subject String @db.VarChar(200)
|
||
bodyHtml String @db.Text
|
||
sentAt DateTime?
|
||
recipientCount Int @default(0)
|
||
createdBy User @relation(fields: [createdById], references: [id])
|
||
createdById Int
|
||
createdAt DateTime @default(now())
|
||
}
|
||
```
|
||
|
||
### Backend Routes
|
||
|
||
| Method | Path | Auth | Description |
|
||
|---|---|---|---|
|
||
| `GET` | `/api/v1/admin/newsletter/campaigns` | admin | List all campaigns |
|
||
| `POST` | `/api/v1/admin/newsletter/campaigns` | admin | Create draft campaign |
|
||
| `POST` | `/api/v1/admin/newsletter/campaigns/:id/send` | admin | Send to all active subscribers |
|
||
| `GET` | `/api/v1/admin/newsletter/subscribers` | admin | List subscribers |
|
||
|
||
### Send Logic
|
||
- `POST .../send` is idempotent — once `sentAt` is set, re-sending returns 409.
|
||
- Uses existing `backend/src/lib/email.ts` SMTP sender.
|
||
- Sends in batches of 50 with a 200ms delay between batches to avoid SMTP rate limits.
|
||
- Each email includes a plain-text unsubscribe link: `https://www.teknav.ir/api/v1/newsletter/unsubscribe?token={token}`.
|
||
- Each `NewsletterSubscriber` row gets a unique `unsubscribeToken` (random UUID, generated on subscription).
|
||
- `GET /api/v1/newsletter/unsubscribe?token=` removes the subscriber and returns a plain 200.
|
||
|
||
### Schema Addition to NewsletterSubscriber
|
||
|
||
```prisma
|
||
model NewsletterSubscriber {
|
||
// existing fields ...
|
||
unsubscribeToken String @unique @default(cuid())
|
||
}
|
||
```
|
||
|
||
### Frontend (Admin)
|
||
- New "خبرنامه" tab in admin sidebar (admin role only).
|
||
- Campaign list with status (پیشنویس / ارسالشده), subject, recipient count, send date.
|
||
- Campaign composer: subject input + HTML textarea (same editor controls as article editor).
|
||
- Preview renders the HTML in a sandboxed `<iframe>`.
|
||
- "ارسال به همه مشترکان" button with a count confirmation dialog: "ارسال به ۱٬۲۳۴ مشترک؟".
|
||
- Subscriber list panel: table of emails + subscribe date + unsubscribe button.
|
||
|
||
---
|
||
|
||
## 9.8 — Article Series
|
||
|
||
### Goal
|
||
Group thematically linked articles into ordered series (e.g., «سری: راهنمای کامل LLMها»). Readers see their position in the series and can navigate between parts. Great for long-form coverage and reader retention.
|
||
|
||
### Schema
|
||
|
||
```prisma
|
||
model ArticleSeries {
|
||
id String @id @default(cuid())
|
||
title String @db.VarChar(200)
|
||
slug String @unique
|
||
description String? @db.VarChar(500)
|
||
coverImage String?
|
||
createdAt DateTime @default(now())
|
||
articles SeriesArticle[]
|
||
}
|
||
|
||
model SeriesArticle {
|
||
id String @id @default(cuid())
|
||
series ArticleSeries @relation(fields: [seriesId], references: [id], onDelete: Cascade)
|
||
seriesId String
|
||
article Article @relation(fields: [articleId], references: [id], onDelete: Cascade)
|
||
articleId String
|
||
position Int
|
||
|
||
@@unique([seriesId, articleId])
|
||
@@unique([seriesId, position])
|
||
}
|
||
```
|
||
|
||
### Backend Routes
|
||
|
||
| Method | Path | Auth | Description |
|
||
|---|---|---|---|
|
||
| `GET` | `/api/v1/series` | public | List all series |
|
||
| `GET` | `/api/v1/series/:slug` | public | Series detail with ordered articles |
|
||
| `GET` | `/api/v1/admin/series` | editor+ | Admin list |
|
||
| `POST` | `/api/v1/admin/series` | editor+ | Create series |
|
||
| `PATCH` | `/api/v1/admin/series/:id` | editor+ | Update title/description/cover and ordered article list |
|
||
| `DELETE` | `/api/v1/admin/series/:id` | editor+ | Delete series |
|
||
|
||
### Frontend
|
||
- **Series page** `/series/:slug` — cover, description, ordered article list with part numbers (قسمت ۱، قسمت ۲...).
|
||
- **Article detail** — if the article belongs to a series, show a "این مقاله بخشی از سری..." banner above the content. Banner includes prev/next navigation.
|
||
- **Admin series management** — new "سریها" tab in admin sidebar. Create/edit series, drag-and-drop to reorder articles using a simple up/down button pair (no drag library needed).
|
||
|
||
---
|
||
|
||
## 9.9 — Related Articles Engine
|
||
|
||
### Goal
|
||
Show three related articles at the bottom of every article. Relation is computed by tag overlap — no ML needed. Results cached in Redis.
|
||
|
||
### Backend
|
||
|
||
New route: `GET /api/v1/articles/:slug/related` — returns up to 3 articles.
|
||
|
||
Algorithm:
|
||
1. Get the current article's tag IDs.
|
||
2. Find published articles sharing the most tags (excluding self), limited to 3.
|
||
3. Tiebreak by `views DESC`.
|
||
4. Cache result in Redis `related:{articleId}` with TTL 1 hour.
|
||
5. Clear cache when any article is published, edited, or deleted.
|
||
|
||
```sql
|
||
-- Prisma raw for tag overlap score
|
||
SELECT a.id, COUNT(at.tagId) AS overlap
|
||
FROM Article a
|
||
JOIN ArticleTag at ON at.articleId = a.id
|
||
WHERE at.tagId IN (<current article tag IDs>)
|
||
AND a.id != <current article id>
|
||
AND a.status = 'منتشرشده'
|
||
GROUP BY a.id
|
||
ORDER BY overlap DESC, a.views DESC
|
||
LIMIT 3
|
||
```
|
||
|
||
### Frontend
|
||
- `RelatedArticles` component at the bottom of `ArticleDetail`, after comments.
|
||
- Three cards in a horizontal row (single column on mobile), each showing cover color, title, category, read time, author.
|
||
- Loaded via `GET /api/v1/articles/:slug/related` on article mount.
|
||
|
||
---
|
||
|
||
## 9.10 — Comment Helpfulness Votes
|
||
|
||
### Goal
|
||
Readers can upvote useful comments. Comments with more votes sort higher. Discourages noise and surfaces quality discussion.
|
||
|
||
### Schema
|
||
|
||
```prisma
|
||
model CommentLike {
|
||
id String @id @default(cuid())
|
||
comment Comment @relation(fields: [commentId], references: [id], onDelete: Cascade)
|
||
commentId String
|
||
user User @relation(fields: [userId], references: [id])
|
||
userId Int
|
||
createdAt DateTime @default(now())
|
||
|
||
@@unique([commentId, userId])
|
||
}
|
||
```
|
||
|
||
Add `likeCount Int @default(0)` to the `Comment` model.
|
||
|
||
### Backend Routes
|
||
|
||
| Method | Path | Auth | Description |
|
||
|---|---|---|---|
|
||
| `POST` | `/api/v1/comments/:id/like` | session | Toggle like (like if not liked, unlike if already liked) |
|
||
|
||
`GET /api/v1/articles/:slug/comments` — add `likeCount` and `likedByMe` (bool, based on session) to each comment object. Default sort changes to: top-level comments ordered by `likeCount DESC, createdAt DESC`.
|
||
|
||
### Frontend
|
||
- Each comment card shows a "👍 مفید بود ({likeCount})" button (no emoji in production — use an SVG thumbs-up from global SVG sprite or inline).
|
||
- Authenticated users can click to toggle. The count updates optimistically.
|
||
- The button is teal-accented when liked by the current user, muted otherwise.
|
||
- Guests see the count but cannot vote.
|
||
|
||
---
|
||
|
||
## 9.11 — Writer Analytics Dashboard
|
||
|
||
### Goal
|
||
Writers see per-article performance stats: views over time, reaction breakdown, comment counts, and follower growth. Editors and admins see the same for any article. Data visualised with pure SVG sparklines — no chart library dependency.
|
||
|
||
### Backend Routes
|
||
|
||
| Method | Path | Auth | Description |
|
||
|---|---|---|---|
|
||
| `GET` | `/api/v1/auth/analytics/overview` | session (writer+) | Total views, reactions, comments, followers |
|
||
| `GET` | `/api/v1/auth/analytics/articles` | session (writer+) | Per-article stats list |
|
||
| `GET` | `/api/v1/auth/analytics/article/:slug` | session (writer+, must own or be editor+) | Daily view/reaction time-series for one article (last 30 days) |
|
||
|
||
### Time-Series Data Source
|
||
The existing `ActivityLog` table logs article publishes. Views are buffered in Redis and flushed to the `Article.views` field. For a time-series breakdown, add:
|
||
|
||
```prisma
|
||
model ArticleViewLog {
|
||
id String @id @default(cuid())
|
||
article Article @relation(fields: [articleId], references: [id], onDelete: Cascade)
|
||
articleId String
|
||
date DateTime @db.Date
|
||
views Int @default(0)
|
||
|
||
@@unique([articleId, date])
|
||
}
|
||
```
|
||
|
||
`flushViewCounters()` (which already runs every 60s) is extended to also upsert a `ArticleViewLog` row for today's date. This gives per-day granularity.
|
||
|
||
### Frontend
|
||
- New "آمار" tab in the writer's profile settings panel (visible to own writer/editor/admin).
|
||
- Overview cards: کل بازدیدها، کل واکنشها، کل نظرها، دنبالکنندگان.
|
||
- Article table: slug, title, views, reactions, comments — sortable.
|
||
- Clicking an article row shows a 30-day sparkline (SVG, pure path element) for views.
|
||
- SVG sparkline: 300×60px, line chart, no axes — just the trend shape.
|
||
|
||
---
|
||
|
||
## 9.12 — Local Syntax Highlighting
|
||
|
||
### Goal
|
||
Technical articles with `<pre><code>` blocks render highlighted code. Highlight.js installed via npm (not CDN). Auto-detects language from `class="language-*"`. RTL article layout must not break LTR code blocks.
|
||
|
||
### Implementation
|
||
|
||
Install: `npm install highlight.js` in root package.json.
|
||
|
||
In `teknav-articles.jsx`, after the article HTML is injected via `dangerouslySetInnerHTML`, use a `useEffect` to:
|
||
1. Query all `pre code` elements inside the article body.
|
||
2. Call `hljs.highlightElement(el)` on each.
|
||
3. Force `direction: ltr; text-align: left` on every `<pre>` via inline style so RTL layout doesn't reverse code.
|
||
|
||
Import only the languages needed to keep bundle size small:
|
||
- `javascript`, `typescript`, `python`, `bash`, `sql`, `json`, `rust`, `go`, `yaml`, `docker`
|
||
|
||
Register them with `hljs.registerLanguage(...)`. Do not use `hljs.highlightAll()` (too broad).
|
||
|
||
Add a highlight.js theme (`github-dark.min.css`) to `src/styles/` — copied from the npm package's `styles/` folder, never from a CDN.
|
||
|
||
Add a language label above each code block: read the `class` attribute, strip `language-`, show as a small badge (e.g., `python`).
|
||
|
||
### Zero CDN Check
|
||
`scripts/check-no-cdn.mjs` must not flag highlight.js. Since it's bundled via Vite from npm, it produces a local `/assets/...` chunk — no external URL. Verify after `npm run build`.
|
||
|
||
---
|
||
|
||
## 9.13 — Social Share (URL-Scheme Only)
|
||
|
||
### Goal
|
||
Readers can share articles to Telegram, Twitter/X, and WhatsApp, and copy the article URL to clipboard. Zero CDN — all share actions use URL schemes or the browser Clipboard API. No tracking pixels.
|
||
|
||
### Frontend
|
||
|
||
`SocialShare` component rendered in `ArticleDetail` below the article byline.
|
||
|
||
Share targets:
|
||
|
||
| Platform | URL scheme |
|
||
|---|---|
|
||
| Telegram | `https://t.me/share/url?url={url}&text={title}` |
|
||
| Twitter/X | `https://x.com/intent/tweet?url={url}&text={title}` |
|
||
| WhatsApp | `https://api.whatsapp.com/send?text={title}+{url}` |
|
||
| Copy link | `navigator.clipboard.writeText(url)` → toast "لینک کپی شد" |
|
||
|
||
All share URLs open in a new tab (`target="_blank" rel="noopener"`). Icons are inline SVGs from a local sprite (no CDN icon fonts). Buttons are small and unobtrusive — placed in a compact row right of the article metadata line.
|
||
|
||
Share count is NOT tracked (avoids the need for a counter service or extra storage).
|
||
|
||
---
|
||
|
||
## 9.14 — Admin Time-Series Analytics
|
||
|
||
### Goal
|
||
Admin and editor roles see trend charts in the dashboard: daily active users (7-day), new registrations (30-day), total article views (30-day), and top 5 articles of the week. All charts are pure SVG — no chart.js or D3 dependency.
|
||
|
||
### Backend Routes
|
||
|
||
| Method | Path | Auth | Description |
|
||
|---|---|---|---|
|
||
| `GET` | `/api/v1/admin/analytics` | writer+ | Views by day and top articles |
|
||
| `GET` | `/api/v1/admin/analytics/content` | writer+ | Content performance analytics |
|
||
| `GET` | `/api/v1/admin/analytics/newsletter` | editor+ | Newsletter analytics |
|
||
| `GET` | `/api/v1/admin/analytics/engagement` | writer+ | Engagement event analytics |
|
||
|
||
### Data Source
|
||
|
||
User registrations: aggregate `User.createdAt` by date.
|
||
|
||
Views: sum `ArticleViewLog.views` grouped by date (requires 9.11's `ArticleViewLog` model).
|
||
|
||
Top articles: sum `ArticleViewLog.views` where `date >= 7 days ago`, group by article, top 5.
|
||
|
||
### Frontend
|
||
- New "آمار سایت" panel in admin dashboard (below the live feed widget).
|
||
- Two SVG bar charts: "ثبتنام روزانه (۳۰ روز)" and "بازدید روزانه (۳۰ روز)".
|
||
- Bar chart: 300×120px SVG. Bars drawn as `<rect>` elements, scaled to max value. Day labels below each bar (Persian numerals, rotated 45°). Hover tooltip showing exact value.
|
||
- Top articles table: rank, title (linked), category, 7-day views.
|
||
|
||
### SVG Bar Chart Component
|
||
Pure React component, no dependencies:
|
||
```jsx
|
||
function BarChart({ data, label }) {
|
||
const max = Math.max(...data.map(d => d.value), 1);
|
||
const W = 300, H = 100, BAR_W = Math.floor(W / data.length) - 2;
|
||
return (
|
||
<svg width={W} height={H} style={{ direction: 'ltr' }}>
|
||
{data.map((d, i) => {
|
||
const h = Math.round((d.value / max) * (H - 20));
|
||
return <rect key={i} x={i * (BAR_W + 2)} y={H - 20 - h} width={BAR_W} height={h} fill="#0F6B73" rx={2} />;
|
||
})}
|
||
</svg>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 9.15 — Editorial Submission & Review Workflow
|
||
|
||
### Goal
|
||
Writers submit articles for editorial review. Editors see a "صف بررسی" (review queue) panel. Editors can approve (publish), request revisions, or reject with a note. Writers get an in-app notification of the decision.
|
||
|
||
### Schema
|
||
|
||
```prisma
|
||
enum ReviewStatus {
|
||
pending
|
||
revision_requested
|
||
approved
|
||
rejected
|
||
}
|
||
|
||
model ArticleReview {
|
||
id String @id @default(cuid())
|
||
article Article @relation(fields: [articleId], references: [id], onDelete: Cascade)
|
||
articleId String @unique
|
||
reviewer User? @relation(fields: [reviewerId], references: [id])
|
||
reviewerId Int?
|
||
status ReviewStatus @default(pending)
|
||
note String? @db.VarChar(1000)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
}
|
||
```
|
||
|
||
The existing `Article.status` enum gains a new value: `در_انتظار_بررسی` (already exists in data but now is the formal submission state).
|
||
|
||
### Backend Routes
|
||
|
||
| Method | Path | Auth | Description |
|
||
|---|---|---|---|
|
||
| `GET` | `/api/v1/admin/reviews` | writer+ | List review queue items visible to the current role |
|
||
| `PUT` | `/api/v1/admin/articles/:id/review` | reviewer/editor/admin | Approve, request revision, or reject with note |
|
||
|
||
### Notification Events
|
||
Reuse the Phase 8 `Notification` model with new types:
|
||
|
||
```prisma
|
||
enum NotificationType {
|
||
comment
|
||
comment_reply
|
||
new_article
|
||
review_approved // new
|
||
review_revision // new
|
||
review_rejected // new
|
||
review_submitted // new — sent to editors when writer submits
|
||
}
|
||
```
|
||
|
||
### Frontend
|
||
- **Writer article editor** — "ارسال برای بررسی" button visible when article is in draft or revision_requested state.
|
||
- **Admin sidebar** — new "صف بررسی" tab with a badge showing pending count (editor+ only).
|
||
- **Review queue panel** — article title, author, category, word count, submit date. Action buttons: تأیید و انتشار / درخواست اصلاح / رد کردن. "درخواست اصلاح" and "رد کردن" open a note textarea before sending.
|
||
- **Writer notification** — bell notification with the editor's decision and note.
|
||
- **Article status badge** — `ArticleDetail` in admin view shows the current review status clearly.
|
||
|
||
---
|
||
|
||
## 9.16 — Saved Articles & Reading List Enhancement
|
||
|
||
### Goal
|
||
Phase 4 added a `save` API but the frontend reading list is minimal. Phase 9 elevates it: users can organize saved articles into named lists (e.g., "بعداً بخوانم"، "مقالات مرجع")، reorder items, and access the list from their profile.
|
||
|
||
### Schema
|
||
|
||
```prisma
|
||
model ReadingList {
|
||
id String @id @default(cuid())
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
userId Int
|
||
name String @db.VarChar(80)
|
||
isDefault Boolean @default(false)
|
||
createdAt DateTime @default(now())
|
||
items ReadingListItem[]
|
||
|
||
@@unique([userId, name])
|
||
}
|
||
|
||
model ReadingListItem {
|
||
id String @id @default(cuid())
|
||
list ReadingList @relation(fields: [listId], references: [id], onDelete: Cascade)
|
||
listId String
|
||
article Article @relation(fields: [articleId], references: [id], onDelete: Cascade)
|
||
articleId String
|
||
position Int
|
||
addedAt DateTime @default(now())
|
||
|
||
@@unique([listId, articleId])
|
||
}
|
||
```
|
||
|
||
Each user gets a default list "ذخیرهشده" created on first save. The existing `SavedArticle` model (Phase 4) is migrated to this system.
|
||
|
||
### Backend Routes
|
||
|
||
| Method | Path | Auth | Description |
|
||
|---|---|---|---|
|
||
| `GET` | `/api/v1/auth/lists` | session | Get user's reading lists |
|
||
| `POST` | `/api/v1/auth/lists` | session | Create a list |
|
||
| `DELETE` | `/api/v1/auth/lists/:id` | session | Delete a list (not default) |
|
||
| `POST` | `/api/v1/auth/lists/:id/items` | session | Add article to list |
|
||
| `DELETE` | `/api/v1/auth/lists/:id/items/:articleId` | session | Remove article |
|
||
| `PUT` | `/api/v1/auth/lists/:id/reorder` | session | Reorder items |
|
||
|
||
### Frontend
|
||
- Profile "ذخیرهشده" tab shows reading lists, each expandable to see its articles.
|
||
- "+" button to create a new named list.
|
||
- Save button on articles now opens a small dropdown: pick which list to save to (or create new).
|
||
- Reading list items show article cover color, title, category, and a remove button.
|
||
|
||
---
|
||
|
||
## Dependencies to Add
|
||
|
||
| Package | Location | Purpose |
|
||
|---|---|---|
|
||
| `@napi-rs/canvas` | root (devDependency) | OG image generation script |
|
||
| `highlight.js` | root | Local syntax highlighting |
|
||
|
||
Both installed via npm. Neither violates the zero-CDN policy — they are bundled locally by Vite.
|
||
|
||
---
|
||
|
||
## Implementation Sequence
|
||
|
||
1. Web-search each article topic for 1405/2026 state.
|
||
2. Write full content for art2–art18 + update art1.
|
||
3. Write full content for art19–art23 (new articles).
|
||
4. Add SEO fields to all 23 articles in `teknav-data.js`, update dates.
|
||
5. Rename art10 slug.
|
||
6. Add SEO + `ArticleViewLog` + `NewsletterCampaign` + `ArticleSeries` + `SeriesArticle` + `CommentLike` + `ArticleReview` + `ReviewStatus` + `ReadingList` + `ReadingListItem` + extended `NotificationType` to `schema.prisma`.
|
||
7. Run `prisma:generate` → `prisma:apply`.
|
||
8. Update `seed.ts` to upsert SEO fields and create default reading lists for seeded users.
|
||
9. Implement backend routes: newsletter campaigns (9.7), series (9.8), related articles (9.9), comment likes (9.10), writer analytics + `ArticleViewLog` flush (9.11), admin analytics (9.14), editorial review (9.15), reading lists (9.16).
|
||
10. Update article detail page: full `<head>` meta + JSON-LD + `SocialShare` + `RelatedArticles` (9.3, 9.13, 9.9).
|
||
11. Implement syntax highlighting in article body (9.12).
|
||
12. Admin: newsletter campaign panel (9.7), series management (9.8), review queue (9.15), subscriber list (9.7), analytics charts (9.14).
|
||
13. Writer profile: analytics tab (9.11), reading lists tab (9.16).
|
||
14. Write `scripts/generate-og-images.mjs`, install `@napi-rs/canvas`, run script.
|
||
15. Update `scripts/generate-seo.mjs` for `dateModified` and RSS `<content:encoded>`.
|
||
16. Run `npm run seo:sitemap`.
|
||
17. Run `npm run build` — verify zero CDN, check highlight.js bundle chunk is local.
|
||
18. Run `cd backend && npm run build`.
|
||
|
||
---
|
||
|
||
## Phase 9 Checklist
|
||
|
||
Current implementation status: core schema, seed/reference data, SEO artifacts, public APIs, admin APIs, and primary frontend entry points are implemented. Several originally listed polish items remain intentionally pending for Linux/browser validation or a later UI pass: full researched 800-word rewrites for every legacy article, syntax-highlighting runtime integration, dedicated writer analytics profile tab, richer social-share buttons, article-series prev/next navigation, review notifications, and reading-list picker UI.
|
||
|
||
### Content
|
||
- [ ] Web-search each article topic for 1405/2026 facts
|
||
- [ ] Write full content for art2 (`custom-ai-chips`)
|
||
- [ ] Write full content for art3 (`browser-architecture`)
|
||
- [ ] Write full content for art4 (`software-supply-chain`)
|
||
- [ ] Write full content for art5 (`iranian-startups-scale`)
|
||
- [ ] Write full content for art6 (`small-llms`) — Phi-3→Phi-4, Llama 3.2→Llama 4
|
||
- [ ] Write full content for art7 (`edge-computing`)
|
||
- [ ] Write full content for art8 (`synthetic-data`)
|
||
- [ ] Write full content for art9 (`ai-hardware-roadmap`) — H100→GB200
|
||
- [ ] Write full content for art10 (`quantum-computing`) — rename slug
|
||
- [ ] Write full content for art11 (`microservices-vs-monolith`)
|
||
- [ ] Write full content for art12 (`fintech-regulation`)
|
||
- [ ] Write full content for art13 (`llm-security`) — Claude 3→Claude Opus 4.7
|
||
- [ ] Write full content for art14 (`data-governance`)
|
||
- [ ] Write full content for art15 (`robotics-warehouse`)
|
||
- [ ] Write full content for art16 (`open-source-ai`) — Llama 3.2→Llama 4
|
||
- [ ] Write full content for art17 (`rust-systems`) + promote to `منتشرشده`
|
||
- [ ] Write full content for art18 (`venture-capital-ai`) + promote to `منتشرشده`
|
||
- [ ] Update art1 (`ai-clean-data`) — refresh model refs and 2024 citations
|
||
- [ ] Write full content for art19 (`claude-opus-47-reasoning`)
|
||
- [ ] Write full content for art20 (`agentic-ai-production`)
|
||
- [ ] Write full content for art21 (`vibe-coding`)
|
||
- [ ] Write full content for art22 (`ai-energy-crisis`)
|
||
- [ ] Write full content for art23 (`llama4-open-frontier`)
|
||
|
||
### SEO Metadata
|
||
- [ ] Add SEO fields to all 23 articles in `teknav-data.js`
|
||
- [ ] Update all `date`/`dateEn` fields per date table
|
||
- [ ] Rename `quantum-computing-2024` slug → `quantum-computing`
|
||
- [ ] Article detail page: inject full `<head>` meta block
|
||
- [ ] Article detail page: inject JSON-LD structured data
|
||
- [ ] Verify meta tags in browser DevTools
|
||
|
||
### OG Images
|
||
- [ ] Write `scripts/generate-og-images.mjs`
|
||
- [ ] `npm install --save-dev @napi-rs/canvas`
|
||
- [ ] Run script → verify 23 JPEGs in `public/images/og/`
|
||
|
||
### SEO Artifacts
|
||
- [ ] Update `scripts/generate-seo.mjs` (`dateModified`, `metaDescription`, `content:encoded`)
|
||
- [ ] Run `npm run seo:sitemap`
|
||
- [ ] Verify sitemap `<lastmod>` for all 23 slugs
|
||
- [ ] Verify RSS `<content:encoded>` is populated
|
||
|
||
### Schema & Seed
|
||
- [ ] Add SEO columns to `Article` in `schema.prisma`
|
||
- [ ] Add `ArticleViewLog` model
|
||
- [ ] Add `NewsletterCampaign` model + `unsubscribeToken` to `NewsletterSubscriber`
|
||
- [ ] Add `ArticleSeries` + `SeriesArticle` models
|
||
- [ ] Add `CommentLike` model + `likeCount` to `Comment`
|
||
- [ ] Add `ArticleReview` model + `ReviewStatus` enum
|
||
- [ ] Extend `NotificationType` enum with review notification types
|
||
- [ ] Add `ReadingList` + `ReadingListItem` models
|
||
- [ ] Run `prisma:generate` + `prisma:apply` on Linux host
|
||
- [ ] Update `seed.ts` to upsert SEO fields
|
||
- [ ] Run `npm run seed` on Linux host
|
||
|
||
### Newsletter Campaigns (9.7)
|
||
- [ ] Backend: campaign CRUD + send route with batch SMTP
|
||
- [ ] Backend: `GET /api/v1/newsletter/unsubscribe?token=` route
|
||
- [ ] Admin: "خبرنامه" sidebar tab
|
||
- [ ] Admin: campaign list + composer + HTML preview iframe
|
||
- [ ] Admin: subscriber list with remove action
|
||
|
||
### Article Series (9.8)
|
||
- [ ] Backend: public series routes (`GET /api/v1/series`, `GET /api/v1/series/:slug`)
|
||
- [ ] Backend: admin series CRUD + article add/remove/reorder
|
||
- [ ] Frontend: `/series/:slug` series landing page
|
||
- [ ] Frontend: series banner + prev/next nav in `ArticleDetail`
|
||
- [ ] Admin: "سریها" sidebar tab with series management
|
||
|
||
### Related Articles (9.9)
|
||
- [ ] Backend: `GET /api/v1/articles/:slug/related` with tag-overlap algorithm + Redis cache
|
||
- [ ] Frontend: `RelatedArticles` component in `ArticleDetail`
|
||
|
||
### Comment Likes (9.10)
|
||
- [ ] Backend: `POST /api/v1/comments/:id/like` (toggle)
|
||
- [ ] Backend: add `likeCount` + `likedByMe` to comment listing
|
||
- [ ] Frontend: like button with count on each comment card
|
||
- [ ] Frontend: optimistic update on click
|
||
|
||
### Writer Analytics (9.11)
|
||
- [ ] Backend: extend `flushViewCounters()` to upsert `ArticleViewLog`
|
||
- [ ] Backend: `GET /api/v1/auth/analytics/overview`
|
||
- [ ] Backend: `GET /api/v1/auth/analytics/articles`
|
||
- [ ] Backend: `GET /api/v1/auth/analytics/article/:slug`
|
||
- [ ] Frontend: "آمار" tab in writer profile settings
|
||
- [ ] Frontend: SVG sparkline component for 30-day view trend
|
||
|
||
### Syntax Highlighting (9.12)
|
||
- [ ] `npm install highlight.js`
|
||
- [ ] Copy `github-dark.min.css` theme to `src/styles/`
|
||
- [ ] Import and register 10 languages in `teknav-articles.jsx`
|
||
- [ ] `useEffect` to apply `hljs.highlightElement()` on article body `<code>` blocks
|
||
- [ ] Force `direction: ltr` on all `<pre>` blocks
|
||
- [ ] Add language label badge above each code block
|
||
- [ ] `npm run build` — confirm no CDN URLs in `dist/`
|
||
|
||
### Social Share (9.13)
|
||
- [ ] `SocialShare` component with Telegram, Twitter/X, WhatsApp, copy-link
|
||
- [ ] Inline SVG icons for each platform
|
||
- [ ] Toast "لینک کپی شد" on clipboard copy
|
||
- [ ] Integrate in `ArticleDetail` below byline
|
||
|
||
### Admin Time-Series Analytics (9.14)
|
||
- [ ] Backend: `GET /api/v1/admin/analytics`
|
||
- [ ] Backend: `GET /api/v1/admin/analytics/content`
|
||
- [ ] Backend: `GET /api/v1/admin/analytics/newsletter`
|
||
- [ ] Backend: `GET /api/v1/admin/analytics/engagement`
|
||
- [ ] Frontend: `BarChart` SVG component
|
||
- [ ] Admin dashboard: "آمار سایت" panel with two bar charts + top-articles table
|
||
|
||
### Editorial Review Workflow (9.15)
|
||
- [ ] Backend: `GET /api/v1/admin/reviews`
|
||
- [ ] Backend: `PUT /api/v1/admin/articles/:id/review`
|
||
- [ ] Backend: approve / revise / reject routes
|
||
- [ ] Backend: notification on each decision + on submission
|
||
- [ ] Frontend: "ارسال برای بررسی" button in article editor
|
||
- [ ] Admin: "صف بررسی" sidebar tab with pending badge
|
||
- [ ] Admin: review queue panel with decision buttons + note textarea
|
||
|
||
### Reading Lists (9.16)
|
||
- [ ] Backend: reading list CRUD + item add/remove/reorder
|
||
- [ ] Frontend: profile "ذخیرهشده" tab shows named lists
|
||
- [ ] Frontend: article save button opens list-picker dropdown
|
||
- [ ] Frontend: list item card with remove button
|
||
- [ ] Migrate existing `SavedArticle` rows to default reading list in `seed.ts`
|
||
|
||
### Build & Docs
|
||
- [ ] `npm run build` passes (zero CDN check)
|
||
- [ ] `cd backend && npm run build` passes
|
||
- [ ] Update `MEMORY.md`
|
||
- [ ] Update `ARCH.md`
|
||
- [ ] Update `PHASES.md`
|
||
- [ ] Mark `PHASE9.md` complete
|