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 inline-JS styles.el (the obfuscated /assets/js/*.js path
broke the chat widget) and threads four UX fixes through it:
- Chat header: drop the all-caps "NEURON / Live Demo" subtitle, keep
just "Neuron" in bold white at 1rem.
- Greeting: replace the canned "Hi. I am Neuron. You get 5 questions"
with "Hey. What is on your mind?" in all three callsites (open,
reset, turnstile-verified). The questions counter is already in
the header, so we don't need to repeat it.
- Avatar: switch chat avatar from neuron-icon.png to the brain mark
(/assets/brand/neuron-brain.png). Thinking state now shows the
pulsing brain plus three-dot animation instead of "thinking..."
text.
- Share button: from a muted t3 link to a navy pill with subtle
background, uppercase letter-spacing, and active-press feedback.
main.el: drop the FOUNDING_SOLD = 47 floor and rewrite
fetch_founding_count_stripe to count succeeded PaymentIntents (the
live Stripe Elements path) AND legacy Checkout Sessions, excluding
refunded charges.
founding_badge.el: rewrite to the Option C "Tag" design picked from
/tmp/founding-badge-preview.html - tall card, navy top stroke, brain
mark prominent, "Neuron, LLC" footer.
terms.el: kill the "Neuron Inference - our own inference layer, live
now" claim. Inference launches Q3 2026 and the wording now says so.
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.
Hand-cuts the marketing surface from Next.js to a native El HTTP server.
The El landing reads the pre-rendered index.html (output of the existing
component pipeline at src/index.html) and serves it directly. ~150
lines of El at server.el; 130 KB binary; no Node, no build step at
serve-time, no runtime JS for the marketing pages.
What's here:
- server.el: dispatcher with /, /health, /api/founding-count, /assets/*,
/brand/*, 404 JSON for everything else. Routes go through fs_read
against LANDING_ROOT (default /srv/landing in the container, ./src
locally).
- Dockerfile: two-stage build for linux/amd64 (Cloud Run target).
Stage 1 — debian:bookworm-slim with build-essential + libcurl-dev,
compiles the binary against el_runtime.c. Stage 2 — slim runtime
image with libcurl4 + ca-certificates, drops the binary at
/usr/local/bin/landing, copies src/index.html and src/assets/ into
/srv/landing/. Uses -rdynamic so the runtime's dlsym(RTLD_DEFAULT,
handler_name) can find handle_request inside the executable on
glibc — macOS exposes executable symbols by default, Linux does
not. Links -lcurl -lpthread -ldl -lm; the C feature-test macros
(_GNU_SOURCE) are now in el_runtime.c itself.
- build.sh: stages the foundation El runtime into ./runtime/, runs
elc to regenerate server.c, builds the docker image. --tag and
--push flags. Push targets us-central1-docker.pkg.dev/neuron-785695/
neuron-marketing/marketing for the Cloud Run flip (still manual).
- .gitignore: runtime/, /server.c, build/ — all build artifacts.
The path here was non-trivial. The original goal was to compile the
full 4325-line landing-combined.el end-to-end; that OOM'd at 8.7 GB
under the always-allocate-fresh el_list_append (the workaround for an
aliasing bug in cg_if_stmt). The runtime ARC scaffolding committed
earlier today got the compile down to 3.5 GB peak in 0.26s, but the
landing-combined still has pre-existing source bugs (http_serve(3001)
arity, neuron_origin bare expression statement) that block the build.
The structurally cleaner path was to render the HTML once, offline, and
serve the static output — which is what this server.el does. The
landing-combined.el can be revisited when those source bugs are fixed;
this server.el is the canonical production surface in the meantime.
Did not commit ./runtime/ (gitignored, staged from foundation by
build.sh on each build), ./server.c (generated by elc from server.el),
or ./build/ (build artifacts). The repo carries the source of truth
only.
- NEURON_ORIGIN env var drives all Stripe redirect URLs (no more localhost)
- Load founding count from persist file or live Stripe PaymentIntents search
- Write count to founding_sold.txt on startup and each webhook increment
- Regenerate index.html with real count before serving
- Startup order: Stripe config → count → HTML → serve
- Nav: responsive hamburger at ≤1060px, full mobile menu with close behaviors
- Nav: all section anchors are absolute (/#...) for correct cross-page routing
- Pillars: flex column cards with margin-top:auto on taglines — all three align
- About footer: image wordmark matches main page (was plain text "Neuron")
- Hero: "Six patents" → "Patented"; sub-copy trimmed to one clean sentence
- Environmental/efficiency/inference: remove all "memory graph" mentions, cite 40% token reduction
- Environmental: savings calculator (slider, live annual savings calculation)
- Nav: all section links are absolute (/#section) - fixes about page back-nav
- Nav: add Environment link
- Safety: "I built" not "we built"; crisis hotline announcement; crisis line coming
- Pillars: rename Learns -> Sharpens with opaque copy that doesn't reveal mechanism
- about, terms, enterprise_terms now all import and use shared nav()
- Any future nav change propagates everywhere automatically
- Remove Docs from nav (no docs yet)
- Safety: Hard Bell is for everyone, not just children - reframe copy,
update cards and bottom callout to reflect universal applicability
- 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
About: rewritten in memoir register — flowing connected sentences,
no staccato, no resignation mention, no employer mention.
Founding counter: polls /api/founding-count every 90s, updates DOM
in place with flash animation on new claims. Webhook now increments
the sold counter when a founding purchase completes.
Badge: founding_badge.el component with guilloché SVG background,
corner rosettes, member number, glow animation. Rendered on success page.
Pricing: "at cost" removed, parent onboarding callout added.
Chat messages are now proper left/right bubbles. Input bar lives inside
the chat window with a top border separator. On return visit, manifesto
goes left, chat center, values/waitlist right in a full-width grid.