Full site philosophy and structure: memory/site_philosophy.md — read this at the start of any session on this site.
It covers: north star, conversion model, home page, post types, book architecture, feature flags, workers, PWA tiers.
CLAUDE.md has a read-routing table at the top that maps tasks to the right memory file.
Every feature must offer all three conversion paths: Email + Push + PWA Install.
Before building anything new, read PLAN.md at the project root.
Before changing any CTA or gate, confirm it still offers all 3 paths.
Key files:
PLAN.md — full conversion strategy + phase prioritiesTODO.md — current tasks, P0 items are next session’s prioritynode cli/promo.js — manage active promo cards (always run this, never edit JSON directly)/home/ahmed/antigravityapps/ahmedbouchefra2/techiediaries/ahmedbouchefra2, branch mainfeat/chapter-gating (chapter gating implementation — merge to main when done)index.html — monolithic Arabic SPA (feed, overlays, stories, bookmarks); ~2250 lines_layouts/bookpost.html — book chapter layout (~2100 lines); TTS, PDF, sidebar TOC, IAB handling, subscription check, reading progress_layouts/bpost.html — single blog post layout_includes/toolbar.html — unified top bar; all sub-features Liquid-flagged via features.json_data/features.json — central on/off flags for every feature on both index and post pages_layouts/bookpost.html)Front matter control per chapter:
gate: false # free this chapter explicitly (default: gated)
gate_methods: email,pwa # restrict which subscription sources unlock (default: 'any')
gate_page_url: /programming-book/ # override default gate page
gate_teaser: 3 # paragraphs visible before gate (default: 3)
gate_title: "..." # override paywall card title
gate_desc: "..." # override paywall card description
gate_email_btn: "..." # override email button text
gate_push_btn: "..."
gate_pwa_btn: "..."
gate_check_btn: "..."
gate_method_note: "..." # optional note below description
Auto-free rules (no front matter needed):
chapter_number: 0 → always freegate: false → always freeSingle config point — window.BOOK_GATE_CONFIG (injected by Liquid in <head>):
All gating decisions + all text strings live here. Change here = changes everywhere.
isChapterAccessGranted() checks:
window.userIsSubscribed (set by initSubscription())window.userSubscriptionSource vs BOOK_GATE_CONFIG.methodswindow.userSubscriptionSource — set during initSubscription():
'pwa' — standalone PWA mode'native' — Capacitor native app'email' — email subscription via book gate page'pwa_install' — installed PWAlocalStorage.subscription_sourcePaywall card — 3 buttons always:
gatePageUrl (default /programming-book/)gateTriggerPush() → AppUtils.SubscriptionManager.requestNotificationPermission()gateTriggerPWA() → AppUtils.installPWA() or manual instructionsUnlock after “I’m already subscribed” button:
Calls initSubscription() → reruns all checks → calls applyChapterGate() → if now unlocked, removes gate UI without page reload.
bookpost.html now loads app-core.js with this config (in <head>):
window.AppCoreConfig = {
iabEscape: { enabled: true, autoRun: true },
iabWarning: { enabled: false }, // iabEscape takes priority
notify: { enabled: false },
emailGate: { enabled: false },
pushNotify: { enabled: false },
pwaInstall: { enabled: true }, // captures beforeinstallprompt for gate PWA button
paywall: { enabled: false }, // bookpost has its own chapter-level gate
debugPanel: { enabled: true },
};
Removed from bookpost.html (now handled by app-core.js / iab_escape.js):
detectInAppBrowser() JS functiontoggleInAppOverlay() JS function.in-app-warning bar HTML.inapp-instructions-overlay HTMLGlobal array — any page can look up book gate/read URLs by id:
window.AppBooks = [
{ id: 'programmer-book', gateUrl: '/book/', readUrl: 'https://ahmedbouchefra.com/bookprogrammer/', color: '#2563eb' },
{ id: 'pybook', gateUrl: '/book/', readUrl: 'https://ahmedbouchefra.com/pybook', color: '#0891b2' },
{ id: 'promptengineering', gateUrl: '/book/', readUrl: 'https://ahmedbouchefra.com/promptengineering', color: '#7c3aed' },
{ id: 'python-book-english',gateUrl: '/book/', readUrl: 'https://10xdev.blog/pybook', color: '#db2777' },
];
Book page handleEmailSubmit looks up AppBooks.find(b => b.id === AppConfig.bookId) for the download URL.
Active book pages: prompt-book.html (bookId: promptengineering), book.html (multi-book chooser).
programming-book.html (bookId: programming-zero-hero) not yet offered — bookId not in AppBooks.
Rule: iabEscape takes priority over iabWarning. Only one loads at a time (fixed in app-core.js).
iab_escape.js — primary: Android Chrome intent + S.browser_fallback_url + 2.5s fallback overlay; iOS ↗ overlayinapp_detector.js — secondary: only loads when iabEscape.enabled === falseinapp_detector.js console.log removed (was logging on every page load)| File | Role |
|---|---|
app-core.js |
Main orchestrator; AppUtils + SubscriptionManager + window.AppBooks; module loader |
iab_escape.js |
IAB escape: Android Chrome intent + iOS overlay |
inapp_detector.js |
IAB warning (secondary, loaded only when iabEscape disabled) |
notify.js |
Announcement bar/card from notifications-feed.json |
email_gate.js |
Email form overlay/card |
push_subscribe.js |
VAPID web push frontend |
debug-panel.js |
Mobile debug panel (loaded via ?debug URL param) |
window.AppCoreConfig = {
iabWarning: { enabled: true, theme: { bannerBg: '#f59e0b' } },
iabEscape: { enabled: true, autoRun: true, testMode: false },
notify: { enabled: true, fetchUrl: '/notifications-feed.json', mode: 'top-bar', duration: 8000, type: 'info' },
emailGate: { enabled: false, mode: 'overlay', autoShow: 'scroll', scrollPx: 300,
workerUrl: 'https://book-gate.techiediaries.workers.dev/subscribe',
siteId: 'ahmedbouchefra2', bookId: 'general' },
pushNotify: { enabled: false, autoSubscribe: false,
workerUrl: 'https://push-gate.techiediaries.workers.dev', swPath: '/sw-push.js' },
pwaInstall: { enabled: true },
paywall: { enabled: true },
debugPanel: { enabled: true },
};
Set window.AppCoreConfig BEFORE loading app-core.js. Partial overrides merge with defaults.
https://push-gate.techiediaries.workers.dev
POST /subscribe — KV id: d1ce80bd6e2f4720a1b7fcc7f06f7cb6POST /send — admin Bearer token requiredGET /stats — admin Bearer token requiredVAPID_PRIVATE_KEYPATH="$HOME/.nvm/versions/node/v22.14.0/bin:$PATH" wrangler deploy| Worker | URL | Purpose |
|---|---|---|
| book-gate | book-gate.techiediaries.workers.dev |
Email collection; KV id: 0e2c829ce5274fb4b7e45ab32585df6c |
| push-gate | push-gate.techiediaries.workers.dev |
VAPID push; KV id: d1ce80bd6e2f4720a1b7fcc7f06f7cb6 |
| bookmarks | bookmarks.techiediaries.workers.dev |
KV bookmarks; KV id: f4c596b1f3b34e959c8deaa2f0f4f8d5 |
All site management exposed as CLI commands and AI tools:
| Script | Purpose |
|---|---|
cli/gate.js |
Chapter gate manager (list/status/free/lock/reset/set/defaults) |
cli/books.js |
AppBooks registry manager (list/get/add/update/remove) |
cli/config.js |
AppCoreConfig defaults manager (list/get/set/enable/disable) |
cli/features.js |
Build-time features.json + announcements + promo cards + CTAs |
cli/tool-runner.js |
AI tool-use entry point — {"tool":"books_list","input":{}} via stdin |
cli/tools-manifest.json |
25 tool schemas in Anthropic Claude tool-use format |
cli/mcp-server.mjs |
MCP server (stdio) — wire into Claude Desktop / Cursor / Zed |
cli/mcp-config-example.json |
Config snippets for MCP clients |
Dev server: node cli/hub.js dev (alias: ab dev) — always use this, never raw bundle exec jekyll serve
npm scripts: gate, books, config, promo, tool, tools, mcp
MCP wiring (Claude Desktop ~/.config/claude/claude_desktop_config.json):
{ "mcpServers": { "ahmedbouchefra2": {
"command": "node",
"args": ["/home/ahmed/antigravityapps/ahmedbouchefra2/cli/mcp-server.mjs"]
}}}
Both now offer all 3 conversion paths:
AppUtils.SubscriptionManager.requestNotificationPermission() → download screenAppUtils.installPWA() → download screenAppCoreConfig on both: pushNotify:{enabled:true,autoSubscribe:false}, pwaInstall:{enabled:true}
autoSubscribe:false → SW registers + VAPID ready but no popup on page loadprogramming-book.html bookId: programming-zero-hero — NOT in AppBooks (skip for now); downloadUrl still example.com placeholder
prompt-book.html bookId: promptengineering — in AppBooks → readUrl https://ahmedbouchefra.com/promptengineering ✓
feat/chapter-gating → main after testing on deviceprogramming-book.html (AppConfig.book.downloadUrl + add to AppBooks)enabled: true + real bookId)chapter_number: 0 is already present ✓gate: false to front matter when createdCloudflare Worker at https://bookmarks.techiediaries.workers.dev
Anonymous UUID per user (localStorage bm_uid). Max 500 bookmarks.
Local IndexedDB (AhmedBlogDB) as offline store.
_data/features.json has index.* and post.* sections. Set any key false → excluded at build time.
JS features also read window.SITE_FEATURES injected from same file at runtime.