Files
anwar-pi-website/docs/audits/2026-10-10-marketing-skills-audit.md
AHMED 0161924088 Free early-access launch: LAUNCH_MODE policy, CRO, pipeline hardening, AI-SEO, ops
- 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)
2026-10-10 18:03:45 +03:00

8.4 KiB
Raw Permalink Blame History

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).