- checkout.el: swap stripe_el_script before auth_script so initStripe is
defined when Supabase auth fires onAuthStateChange on page load
- main.el: fix Stripe webhook email extraction for checkout.session.completed
(subscription) events — customer_details is nested at data.object level,
not at root; previous code only worked for payment_intent.succeeded
- page_close.c: replace <input type="text"> with <textarea rows="1"> in
the chat widget input row so long questions are visible as you type
- page_css.c: update #neuron-demo-text CSS for textarea (resize:none,
overflow:hidden, min/max-height, align-items:flex-end on row)
- chat-widget.el: add auto-resize event listener (grows up to ~4 lines),
reset height to auto on send
el_runtime: http_response() JSON-encoded the body via jb_emit_escaped(),
which stops at the first null byte. PNG/binary files contain null bytes
at byte 8 (IHDR chunk length), so only 8 bytes were served — browsers
received a corrupt/truncated image and showed broken icons.
Fix: when _tl_fs_read_len > 0 (binary fs_read), copy raw bytes into a
thread-local side-channel (_tl_binary_body/_tl_binary_size) and write
the sentinel "__el_binary__" into the envelope body field. http_send_response()
detects the sentinel and substitutes the real bytes for sending.
checkout.el: .checkout-shell, .checkout-summary, and .checkout-form-wrap
had no CSS, leaving the page left-aligned and single-column. Added grid
layout (2-col desktop, 1-col mobile), max-width centering, and sticky
order summary.
- checkout.el and main.el: replace raw import of el-html vessel with direct
extern fn declarations; implementations come from dist/elhtml_impl.c (c_source)
- Add src/elhtml.el as reference file (all el-html extern fn declarations; not imported)
- dist/elhtml_impl.c: pre-compiled el-html vessel C (strips int main + sample global)
- dist/page_close.c: pre-compiled page_close implementation; elc OOMs after emitting
the ~71KB page_open body and silently drops page_close, so supply it as c_source
- manifest.el: add elhtml_impl.c and page_close.c as c_source entries
- .gitignore: un-ignore dist/elhtml_impl.c and dist/page_close.c
Extract nav and style blocks into checkout_nav_html() and checkout_style_html()
so the compiler processes each template in isolation rather than one 490-line
function with mixed HTML template AST and BinOp string concat.
Free tier:
- checkout-stripe.el bails out immediately for plan=free (no Stripe init)
- checkout-auth.el skips payment section reveal and initStripe for free plan
- checkout-free.el shows #free-success panel after auth (no card ever shown)
- /api/payment-intent returns early for free plan — no Stripe call
Stripe dedup (all paid plans):
- Stripe init now deferred to window.initStripe(email, name), called by
checkout-auth.el after sign-in — email is known before intent is created
- /api/payment-intent finds-or-creates Stripe Customer by email before
creating the PaymentIntent/SetupIntent and attaches customer upfront
- Eliminates the window between intent creation and /api/link-customer
that was producing duplicate guest customers
Replace all return "..." HTML string literals with native El templates —
removes all \" escapes, converts + interpolations to {expr}/{raw(expr)},
and replaces conditional string concatenation with {#if}/{#else}/{/if}.
No functional changes; output is identical.
Recovers original JS from git history and ports it into proper El source
files under src/js/. Each file wraps the original JS in a native_js call
inside a main() function, making it valid El that compiles to a
self-contained IIFE via elc --target=js --bundle.
Files added:
src/js/account-auth.el - Supabase OTP magic-link (sendMagicLink)
src/js/account-dashboard.el - Account dashboard: session, plan card, family
src/js/chat-widget.el - Demo chat widget (neuronDemoToggle/Send/Reset)
src/js/checkout-auth.el - Checkout auth: OAuth, email sign-in/up
src/js/checkout-free.el - Free plan: auth-badge watch -> payment reveal
src/js/checkout-stripe.el - Stripe Payment Element (reads NEURON_CFG)
src/js/enterprise.el - Enterprise inquiry form + headcount filter
src/js/environmental.el - Efficiency calculator slider
src/js/gallery.el - Gallery nav, search/sort, Supabase voting
src/js/main.el - Share page voting + copyForPlatform
src/js/marketplace.el - Developer interest form
src/js/nav.el - Nav hamburger + Mission dropdown
src/js/styles.el - Landing: nav scroll, reveal, founding counter
Build pipeline
- build-stage.sh replaces the old in-Dockerfile bootstrap.py path. Host
pre-compiles src/*.el into dist/main.c via the canonical native elc at
foundation/el/dist/platform/elc and applies the stub-decl sed before
docker buildx runs.
- Dockerfile.stage drops bootstrap.py + python3 from the builder stage
and just runs cc on the host-supplied dist/main.c.
- Pre-rendered HTML shells under /srv/landing/ are now chowned to the
landing user so the El page-builder's fs_write at startup can rewrite
them — without that, post-COPY edits never reach the served HTML and
the served page stays as the stale build-time fallback.
Chat restore
- session.verified + session.verifiedAt persist through localStorage so
a return visit within 24h skips the Turnstile gate and lands directly
in the restored conversation.
- restoreOrGreet() is the single source of truth for what shows up in
the message pane after the gate clears: replays prior messages with
skipSave, else drops the canned hello once and remembers it.
- applyVerifiedDom() hides the gate / reveals the chat row, called both
from the verified-on-load path (DOMContentLoaded if loading, else
immediate) and from the Turnstile callback.
- neuronDemoReset clears verified + verifiedAt so the gate returns next
open.
Extracted JS assets (src/assets/js/*.js + manifest.json) and the
extract-js.py helper land here too — they match what the new build-stage
flow produces and removes the inline <script> blobs from the served HTML.
The Professional plan's "Hold until product launches" radio was wired
into the markup but ignored by both /api/payment-intent and the JS
submit handler. Buyers who picked it would still get charged
immediately because the server always created a PaymentIntent and
the client always called stripe.confirmPayment.
Fix:
* /api/payment-intent reads body.timing. When plan=professional and
timing=later, it creates a SetupIntent (usage=off_session) instead
of a PaymentIntent and returns {setup_mode:true, client_secret:...}.
Founding stays unconditional (lifetime, charge now).
* checkout JS now reads the radio (currentTiming()), passes timing
to the server, and re-fetches a new client_secret on radio change
so the buyer's choice is honored even if they toggle after first
mount.
* window._neuronMode tracks 'payment' vs 'setup'. The submit handler
branches: stripe.confirmSetup for save-card, stripe.confirmPayment
for charge-now. The submit button label updates to "Save my card -
no charge today" when in setup mode so the buyer sees the
intent before they hit submit.
* /api/link-customer receives timing + mode so the server can
differentiate at attach time.
A future webhook on setup_intent.succeeded will create the actual
Subscription with trial_end at launch (Q3 2026 / 2026-09-01) - that
piece is queued via metadata[hold_until]=launch on the SetupIntent.
For now, the saved payment method sits in Stripe untouched.
The point: a buyer who picks "Hold until launch" is NOT charged. The
flow has to be airtight - no surprise charges.
The /account "Loading..." spinner stayed on forever because the
browser-side waitlist read went through the anon key and didn't reach
the row. Replaced it with a POST /api/my-plan: the server verifies
the user's access_token via Supabase /auth/v1/user, then reads the
waitlist row with the service key. Bypasses RLS without exposing the
service key to the browser.
Stripped implementation comments from the served JS so the browser
doesn't broadcast how internals are shaped.
Build pipeline: declared the supabase_auth_user stub for both
build-local.sh and Dockerfile.stage so the bootstrap-injected forward
declarations match what's actually linked.
Restores the /api/link-customer endpoint that was lost in the stash. It
runs right before stripe.confirmPayment() and:
- searches Stripe for an existing Customer by email
- creates one if missing (URL-encodes + and @ so Gmail aliases work)
- attaches the Customer to the live PaymentIntent
- upserts the Supabase waitlist row with stripe_customer_id + plan
Stripe locks customer on a Charge once set, so the webhook handler is
the second-line backstop for any race where confirmPayment beats the
link call.
Also: change return_url from /marketplace/success to /account?welcome=1
so buyers land where they need to be, and switch /marketplace/success
and /account from str_eq to str_starts_with so Stripe-appended query
strings (?payment_intent=...&redirect_status=succeeded) don't 404.
The auth-first flow blocked Stripe Elements from initialising for any
visitor without an existing Supabase session. Users hit the checkout
page, saw "Sign in to continue", and could not get to a card field at
all. Restored the inline-JS path (HEAD before extraction broke it),
flipped payment-section visible by default, kept the sign-in panel
behind an explicit "Already have an account? Sign in" link.
Build pipeline: added supabase_get stub injection and -lssl/-lcrypto
linker flags (web_stubs.c uses EVP for the AES-256-GCM transport).
Without those the Docker build aborts at link time.
- checkout.el: Custom Stripe Payment Element page at /checkout?plan=...
- safety.el: Hard Bell and emergency routing architecture section
- main.el: Wire checkout + safety, /api/payment-intent, STRIPE_PUBLISHABLE_KEY
- enterprise_terms.el: Add nav bar (was missing, page had no navigation)
- styles.el: Checkout buttons now link to integrated page, not hosted Stripe
- El runtime: Auto-detect HTML responses, serve text/html vs application/json
- footer.el: Wordmark image centered above "Built Different." tagline
- environmental.el: Replace SQLite with custom on-device storage
- enterprise.el: Replace SQLite with custom on-device storage
- pricing.el: Local models degraded performance note near Ollama feature
- about.el: "left home at fifteen", remove programming language mention