Subscribe URL + GateCard Review — 2026-06-05

Use this doc to verify all changes from the “subscribe URL centralized + GateCard emailMode + subscribe-direct hub API” commit.
All links are absolute paths — click to open the exact line in VS Code.


1. Subscribe URL — single source of truth in app-core.js

What changed: /subscribe-direct is now defined once in AppCoreConfig. All callers read from it.

What Link
subscribeUrl: '/subscribe-direct' added to AppCoreConfig defaults assets/js/app-core.js:45
SUBSCRIBE_URL constant derived from config assets/js/app-core.js:52
requestNotificationPermission() uses SUBSCRIBE_URL assets/js/app-core.js:350
updateUI() redirect uses SUBSCRIBE_URL assets/js/app-core.js:360
Paywall CTA link uses SUBSCRIBE_URL assets/js/app-core.js:562

How to verify:

# Should return ZERO results — no raw /subscribe-direct strings left in app-core.js
grep -n "'/subscribe-direct'\|\"\/subscribe-direct\"" assets/js/app-core.js

How to change the subscribe URL site-wide:
Edit only subscribeUrl at line 45. Everything else follows.


2. GateCard — emailMode option + inline form support

What changed: GateCard now supports emailMode: 'form' which renders an inline EmailSubscribe form instead of a link.

What Link
emailMode in JSDoc comment assets/js/ui/subscribe/GateCard.js:13
_subscribeUrl() helper reads AppCoreConfig.subscribeUrl assets/js/ui/subscribe/GateCard.js:23
emailMode default in _defaults() assets/js/ui/subscribe/GateCard.js:78
emailUrl default reads _subscribeUrl() assets/js/ui/subscribe/GateCard.js:85
render() calls _injectEmailForm when emailMode:'form' assets/js/ui/subscribe/GateCard.js:58
wire() also calls _injectEmailForm assets/js/ui/subscribe/GateCard.js:72
_emailSlot() — returns form placeholder or link assets/js/ui/subscribe/GateCard.js:133
_injectEmailForm() — injects EmailSubscribe, polls 150ms, falls back to link assets/js/ui/subscribe/GateCard.js:141
_showEmailFollowUp() — branches on emailMode (form vs link) assets/js/ui/subscribe/GateCard.js:203

How to use on any page:

// Show inline email form instead of link (gate modal):
GateCard.render(container, { mode: 'sequential', emailMode: 'form' });

// Show push button only:
GateCard.render(container, { mode: 'push-only' });

// Show both push + email form side-by-side:
GateCard.render(container, { mode: 'both', emailMode: 'form' });

Front matter shortcuts (post.html):

gate_mode: sequential       # sequential | push-only | email-only | both
gate_email_mode: form       # link | form
gate_email_url: /custom-subscribe  # override URL for this post only

3. post.html GATE_CONFIG — emailMode + URL from config

What changed: GATE_CONFIG now exposes emailMode and reads emailUrl from AppCoreConfig instead of hardcoding it.

What Link
GATE_CONFIG block with all options _layouts/post.html:888
emailMode field (front matter: gate_email_mode) _layouts/post.html:896
emailUrl reads AppCoreConfig.subscribeUrl as fallback _layouts/post.html:897

4. bookpost.html SUBSCRIPTION_INTENTS — URL from config

What changed: fallbackUrl in both intents now reads AppCoreConfig.subscribeUrl instead of /subscribe-direct hardcoded.

What Link
_subUrl() helper _layouts/bookpost.html:2130
chapterByPush.fallbackUrl uses _subUrl _layouts/bookpost.html:2132
bookPdfDownload.fallbackUrl uses _subUrl _layouts/bookpost.html:2134

5. subscribe-direct.html — new hub API throughout

What changed: page now uses SubscriptionHub for cap checks and sends full grants on push subscribe.

What Link
Load subscription-hub.js as ESM → window.SubscriptionHub subscribe-direct.html:174
FULL_PUSH_GRANTS constant defined inline subscribe-direct.html:179
Early return uses hasCap('read') not localStorage subscribe-direct.html:223
subscribeFor({ grants: FULL_PUSH_GRANTS }) instead of bare subscribe() subscribe-direct.html:256
On-load check uses hasCap('read') not localStorage subscribe-direct.html:302

How to verify:

# Should return ZERO — no direct localStorage push reads left
grep -n "localStorage.*push_subscribed" subscribe-direct.html

# Should return FULL_PUSH_GRANTS — not bare subscribe()
grep -n "subscribeFor\|subscribe()" subscribe-direct.html

Quick sanity checks (run all at once)

cd /home/ahmed/.gemini/antigravity/scratch/ahmedbouchefra2

# 1. No raw /subscribe-direct strings in app-core.js (only SUBSCRIBE_URL)
grep -n "'/subscribe-direct'\|\"\/subscribe-direct\"" assets/js/app-core.js

# 2. No localStorage push reads in subscribe-direct page
grep -n "localStorage.*push_subscribed" subscribe-direct.html

# 3. GateCard reads URL from config, not hardcoded
grep -n "subscribe-direct" assets/js/ui/subscribe/GateCard.js

# 4. bookpost fallbackUrl uses _subUrl helper
grep -n "fallbackUrl" _layouts/bookpost.html

# 5. post.html GATE_CONFIG has emailMode field
grep -n "emailMode" _layouts/post.html

All checks should return either zero results or show the new patterns (no raw /subscribe-direct strings, no localStorage.getItem('push_subscribed')).