Read first

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.

⚠ ALIGNMENT RULE — Read before every session

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:


Project locations


Architecture


Book chapter gating (feat/chapter-gating branch — 2026-05-18)

How it works (implemented in _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):

Single 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:

  1. window.userIsSubscribed (set by initSubscription())
  2. window.userSubscriptionSource vs BOOK_GATE_CONFIG.methods

window.userSubscriptionSource — set during initSubscription():

Paywall card — 3 buttons always:

  1. Email → gatePageUrl (default /programming-book/)
  2. Push → gateTriggerPush()AppUtils.SubscriptionManager.requestNotificationPermission()
  3. PWA → gateTriggerPWA()AppUtils.installPWA() or manual instructions

Unlock after “I’m already subscribed” button: Calls initSubscription() → reruns all checks → calls applyChapterGate() → if now unlocked, removes gate UI without page reload.


app-core.js in bookpost.html

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


AppBooks registry (window.AppBooks in app-core.js)

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


IAB handling (assets/js/)

Rule: iabEscape takes priority over iabWarning. Only one loads at a time (fixed in app-core.js).


JS library ecosystem (assets/js/)

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)

AppCoreConfig full reference

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.


Custom Web Push system


Cloudflare Workers

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

CLI + AI tool layer (feat/chapter-gating branch — 2026-05-18)

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"]
}}}

Book landing pages (programming-book.html, prompt-book.html)

Both now offer all 3 conversion paths:

  1. Email — form → Cloudflare KV worker → download screen
  2. Push (bell) — calls AppUtils.SubscriptionManager.requestNotificationPermission() → download screen
  3. PWA install (pwa) — calls AppUtils.installPWA() → download screen

AppCoreConfig on both: pushNotify:{enabled:true,autoSubscribe:false}, pwaInstall:{enabled:true}

programming-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


Pending


Bookmarks

Cloudflare Worker at https://bookmarks.techiediaries.workers.dev Anonymous UUID per user (localStorage bm_uid). Max 500 bookmarks. Local IndexedDB (AhmedBlogDB) as offline store.


Feature flags (features.json)

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