- launch.ts policy: LAUNCH_MODE=free_early_access default; effective access = published && (free || early); intended tier (access) never rewritten - Billing off server-side: /api/subscribe 503 honest copy; subscribe CTAs render disabled 'قريباً' from first paint; /subscription early-access panel + /api/notify (LaunchNotifications) with honest 503 when mail transport off - Signup trust: verification email at signup (Users afterChange), no confirm-password, 429 message fidelity, typo suggestion, live password hint - Agent pipeline: pickFields strips access/readingMinutes, sourceId dedup, secret-scan 422 (pattern names only), string-content rejected (was silently replaced), publish stays explicit opt-in - lexical.ts: root unwrap fix — lexicalToText/countLexicalWords returned 0 for real serialized trees (secret scan + readingMinutes were inert) - llms-full.txt: fetchIndexDocs(true) — bodies were never selected - Content honesty: contentType/authorName/sourceId fields + migration, readingMinutes computed at write (160 wpm) + backfill script, byline, 'حُدّث' freshness - SEO/AI-SEO: metadata rewrite (published-only, og:image 1200x630), Article + BreadcrumbList + FAQPage + Organization JSON-LD, isAccessibleForFree honest, table renderer, robots AI-crawler policy (10 named bots), llms.txt + llms-full.txt, /topics/* hubs + sitemap, noindex auth pages, www 301 - Ops: backup-db.sh (restore rehearsed on VPS), docs copies (launch readiness + marketing audit + owner-inputs for L-03), GLOSSARY.md entries Verified: tsc --noEmit 0, npm run build 0, local smoke (ghost VIP read, subscribe/notify/agent APIs, signup token, 429 on 6th, llms/topics/sitemap, og:image/JSON-LD)
74 lines
8.4 KiB
Markdown
74 lines
8.4 KiB
Markdown
# Marketing-skills audit — anwarpy.ai (2026-10-10)
|
||
|
||
> Copy of the local audit of 2026-10-10 (source: agent workspace `marketing-skills-audit-findings.md`), committed for the launch PR. Items marked ❌ were fixed the same day in the launch/early-access branch; see `docs/launch/2026-10-10-launch-readiness.md` for per-item evidence.
|
||
|
||
Audited against installed playbooks: paywalls, signup, ai-seo, programmatic-seo, aso (`.agents/skills/`). Evidence: repo `web/src` (file:line) + live production HTML. Verdicts: ✅ compliant · ⚠️ partial · ❌ gap.
|
||
|
||
## Paywalls (skill: paywalls v2.0)
|
||
|
||
**Current state — technically solid.** Gating = `users.role` (free/vip/admin); VIP body has field-level read ACL (`collections/Posts.ts:57-71`) + page-level select — never served to non-VIP. Teaser paragraphs required for VIP posts (`Posts.ts:39-51`) with fade-out mask; paywall box on locked posts has headline, price-in-CTA («اشترك بـ $10 شهرياً», `lib/format.ts:11`), objection line («تلغي في أي وقت»), login secondary. Per-file byte gate on attachments (`/api/files/[id]`). Stripe: Checkout → `/payment-success` re-verification + webhook activation; deletion webhook downgrades. No dark patterns anywhere.
|
||
|
||
**Gaps**
|
||
- ❌ Value demonstration: locked H2 titles never shown (`posts/[slug]/page.tsx:202` extracts TOC only when unlocked) — paywall promises «الأقسام العملية» generically.
|
||
- ❌ Social proof absent on paywall and subscription (subscription shows guide counts only). No fabricated quotes allowed; needs owner-provided testimonial or real aggregate.
|
||
- ❌ No FAQ link or free-escape-hatch inside the paywall box (FAQ lives only on /subscription).
|
||
- ❌ Coming-soon discovered after failed click (`subscription/subscribe-button.tsx:16-27`): glossary says disabled «قريباً» button; actual renders active «اشترك الآن» and reveals coming-soon only after POST fails.
|
||
- ❌ No measurement (paywall impression/CTA events) — blocked on analytics-tooling owner decision.
|
||
- ⚠️ Pricing: monthly $10 only; skill suggests annual toggle — owner policy.
|
||
- 🧹 Dead code: `styles/post-locked.css`, `styles/post-unlocked.css` imported nowhere (live styles in globals.css:168-190, design.css:271-287).
|
||
|
||
## Signup (skill: signup v2.0)
|
||
|
||
**Current state.** 5 fields (الاسم required, email, password ≥8 with show-toggle + hint, تأكيد كلمة المرور, terms checkbox) + hidden `termsVersion`. Native constraint validation with Arabic custom messages; paste allowed; visible labels; single column mobile. Post-submit: auto-login → `/verify-email` (can explore unverified; only commenting gated, 403 with Arabic copy) → `/onboarding` preserving `anwarpi:signup-next`. `safeNext` redirect hygiene. Rate limits: 5/h signup, 10/15min login per-IP (`middleware.ts`), Payload lockout 5/15min (admin exempt, PBKDF2 timing-equalizer).
|
||
|
||
**Gaps**
|
||
- ❌ **Verification email is never sent at signup** — `sendVerificationEmail` has a single call site (`api/auth/resend-verification/route.ts`); `Users.ts` has no afterChange hook. The verify page claims «أرسلنا رابط التأكيد إلى:» falsely until the user clicks resend. Highest-impact fix.
|
||
- ❌ 429 body mis-parsed: signup reads only `err?.errors?.[0]?.message` (`signup/page.tsx:44-47`), so middleware's `{error}` JSON is ignored → rate-limited users see «قد يكون البريد مستخدماً بالفعل» (wrong). Login shows one generic message for all failures (`login/page.tsx:29-31`). Retry-After never surfaced.
|
||
- ❌ No spam-folder step and no change-email affordance on `/verify-email` (skill post-submit checklist).
|
||
- ❌ Field count above skill's ≤3 guidance (confirm-password cuttable; name kept for comment display names).
|
||
- ❌ No «مجاناً — بلا بطاقة ائتمانية» / privacy microcopy near CTA; no inline duplicate-email error with login link; no email-typo suggestion; static (not live) password hint.
|
||
- ❌ Social auth absent (Google/Apple) — needs owner (OAuth apps).
|
||
- ✅ Already compliant: single email field, no placeholder-only labels, value-story panel, terms timestamped (`termsAcceptedAt`).
|
||
|
||
## AI-SEO (skill: ai-seo v2.0)
|
||
|
||
**Current state — good SSR baseline.** All pages force-dynamic server components (content in initial HTML); `lang="ar" dir="rtl"`; self-canonical everywhere; title template; sitemap (published posts + lastmod, 8 static paths); robots with disallow list + sitemap pointer; WebSite JSON-LD (home) and Article JSON-LD (posts); answer-first seed content; public per-post JSON at `/api/posts/[slug]`.
|
||
|
||
**Gaps**
|
||
- ❌ No `llms.txt` / `llms-full.txt`.
|
||
- ❌ No named AI-crawler robots policy (implied `*` only); `/api` disallow hides the agent-readable post JSON.
|
||
- ❌ Article pages emit **no og:image** — `generateMetadata` (`posts/[slug]/page.tsx:16-33`) defines `openGraph` which *replaces* the root OG object (no siteName/locale/images), though `post.cover.url` exists.
|
||
- ❌ Draft leak: the metadata query lacks the `status: 'published'` filter the page query has → draft titles/deks render in metadata of 404 pages.
|
||
- ❌ Article JSON-LD missing `image`, `dateModified`, `description`, `mainEntityOfPage`, `publisher`.
|
||
- ❌ FAQPage schema absent despite real Q&A (`subscription/page.tsx:16-21` FAQ_ITEMS); BreadcrumbList absent despite breadcrumb UI (`posts/[slug]/page.tsx:179-183`); no Organization schema.
|
||
- ❌ Lexical renderer has no `table` node case (~`page.tsx:373-387`) — comparison tables in content fall through to `<p>`.
|
||
- ⚠️ Freshness: sitemap uses updatedAt but the page shows only publishedAt — add «حُدّث» when they differ.
|
||
- ✅ Answer-first style present; single H1; h2 extraction; agent-accessible free JSON exists.
|
||
|
||
## Programmatic SEO (skill: programmatic-seo)
|
||
|
||
**Current state.** Indexable surface = `/`, `/articles`, `/posts/[slug]`, 7 static pages. All category/topic navigation is client-side DOM filtering on ONE URL (`articles-client.tsx:11-19, 62-70`) — clicks never update the URL; `?topic=` states share one canonical. `/catalog` permanently redirects to /articles; `catalog-filters.tsx` + `catalog-deck.tsx` exported nowhere (dead).
|
||
|
||
**Data available in Postgres** (Posts): title, slug, dek, category (6 values), level (3), access, status, publishedAt/updatedAt, readingMinutes, views, likes, cover, attachments. Public taxonomy already exists: `lib/topics.ts` — 4 topics (`agents` الوكلاء, `models` النماذج, `automation` الأتمتة, `foundations` الأساسيات) mapped from the 6 CMS categories via `topicsFor(post)` + `isTopic()`.
|
||
|
||
**Decision (owner-confirmed 2026-10-10): build 4 topic hub pages `/topics/[topic]`** — server-rendered directories with unique intros, counts, breadcrumbs + BreadcrumbList, sitemap entries, internal links from home/articles/post breadcrumbs. Later candidates (not now): topic×level personas (gate on ≥3 posts/combo), templates directory, glossary (needs new field). Comparisons/locations/translations: no data, N/A.
|
||
|
||
## ASO (transferable items only — site, not an app)
|
||
|
||
- ❌ Home title is brand-only «أنور بآي» — unknown brands need keywords: «أنور بآي — أدلة الذكاء الاصطناعي العملي بالعربية».
|
||
- ❌ Only root + posts have descriptions; give /articles, /subscription, /about their own Arabic descriptions.
|
||
- ❌ One static OG image for everything → per-post `cover` as og:image (same fix as ai-seo list).
|
||
- ❌ Surface «حُدّث» freshness date on articles.
|
||
- ✅ Localization correct (ar locale, RTL, og:locale). Ratings/screenshot fields: N/A.
|
||
|
||
## Needs owner (not in this pass)
|
||
|
||
1. Social auth providers (Google/Apple or magic links) — OAuth apps + keys.
|
||
2. Analytics/experimentation tooling (paywall impressions, CTA CTR, form drop-off) — vendor + privacy stance; code ready once chosen.
|
||
3. Annual plan / money-back guarantee — pricing policy.
|
||
4. Testimonial content — must be real; nothing fabricated.
|
||
5. Brand spelling inconsistency: site uses «أنور بآي» everywhere (title, og:site_name, JSON-LD); docs use «أنور بي آي». Pick one canonical spelling.
|
||
6. Policy: allow free users to comment on VIP posts (currently hard-403) — easy conversion lever if allowed.
|
||
7. Sitemap `limit: 500` with no pagination — fine at 13 posts; revisit at scale. /login + /signup kept in sitemap.
|
||
8. `GLOSSARY.md` says free users are *redirected* from /payment-success; actual behavior is an in-page retry state — doc will be aligned to code (no redirect; no VIP minting either way).
|