Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c38a552266 | |||
| b87b0bed24 | |||
| ef352d2b8a | |||
| bb690c4690 | |||
| e148e6987d | |||
| 5812cb0452 | |||
| c99923da1b | |||
| 5885826316 | |||
| 9554430b7e | |||
| 4e35cbe841 | |||
| 62385b53c2 | |||
| 9685a42c7d | |||
| 952b03737b | |||
| d2628ec42e | |||
| cac986c5e1 | |||
| 9650dad951 | |||
| d598fb7b10 | |||
| 1eeb8df04b | |||
| cd93af38fb |
@@ -53,8 +53,9 @@ jobs:
|
|||||||
CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || git diff --name-only HEAD 2>/dev/null || echo "unknown")
|
CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || git diff --name-only HEAD 2>/dev/null || echo "unknown")
|
||||||
echo "Changed files:"
|
echo "Changed files:"
|
||||||
echo "$CHANGED"
|
echo "$CHANGED"
|
||||||
# Asset-only: only src/assets/, src/shares/, src/index.html, src/about.html, src/terms.html, src/enterprise-terms.html, src/llms.txt
|
# Asset-only: files that don't require rebuilding the El binary.
|
||||||
NON_ASSET=$(echo "$CHANGED" | grep -v '^src/assets/' | grep -v '^src/shares/' | grep -v '^src/index\.html' | grep -v '^src/about\.html' | grep -v '^src/terms\.html' | grep -v '^src/enterprise-terms\.html' | grep -v '^src/llms\.txt' | grep -v '^$' || true)
|
# migrations/, scripts/, tests/ are data/infra/test changes — no binary rebuild needed.
|
||||||
|
NON_ASSET=$(echo "$CHANGED" | grep -v '^src/assets/' | grep -v '^src/shares/' | grep -v '^src/index\.html' | grep -v '^src/about\.html' | grep -v '^src/terms\.html' | grep -v '^src/enterprise-terms\.html' | grep -v '^src/llms\.txt' | grep -v '^migrations/' | grep -v '^scripts/' | grep -v '^tests/' | grep -v '^\.gitea/' | grep -v '^$' || true)
|
||||||
if [ -z "$NON_ASSET" ] && [ "$CHANGED" != "unknown" ]; then
|
if [ -z "$NON_ASSET" ] && [ "$CHANGED" != "unknown" ]; then
|
||||||
echo "asset_only=true" >> "$GITHUB_OUTPUT"
|
echo "asset_only=true" >> "$GITHUB_OUTPUT"
|
||||||
echo "=> Asset-only change detected, will use fast path"
|
echo "=> Asset-only change detected, will use fast path"
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ jobs:
|
|||||||
CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || git diff --name-only HEAD 2>/dev/null || echo "unknown")
|
CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || git diff --name-only HEAD 2>/dev/null || echo "unknown")
|
||||||
echo "Changed files:"
|
echo "Changed files:"
|
||||||
echo "$CHANGED"
|
echo "$CHANGED"
|
||||||
NON_ASSET=$(echo "$CHANGED" | grep -v '^src/assets/' | grep -v '^src/shares/' | grep -v '^src/index\.html' | grep -v '^src/about\.html' | grep -v '^src/terms\.html' | grep -v '^src/enterprise-terms\.html' | grep -v '^src/llms\.txt' | grep -v '^$' || true)
|
NON_ASSET=$(echo "$CHANGED" | grep -v '^src/assets/' | grep -v '^src/shares/' | grep -v '^src/index\.html' | grep -v '^src/about\.html' | grep -v '^src/terms\.html' | grep -v '^src/enterprise-terms\.html' | grep -v '^src/llms\.txt' | grep -v '^migrations/' | grep -v '^scripts/' | grep -v '^tests/' | grep -v '^\.gitea/' | grep -v '^$' || true)
|
||||||
if [ -z "$NON_ASSET" ] && [ "$CHANGED" != "unknown" ]; then
|
if [ -z "$NON_ASSET" ] && [ "$CHANGED" != "unknown" ]; then
|
||||||
echo "asset_only=true" >> "$GITHUB_OUTPUT"
|
echo "asset_only=true" >> "$GITHUB_OUTPUT"
|
||||||
echo "=> Asset-only change detected, will use fast path"
|
echo "=> Asset-only change detected, will use fast path"
|
||||||
|
|||||||
+1
-1
@@ -98,7 +98,7 @@ fn checkout_page(plan: String, pub_key: String) -> String {
|
|||||||
el_li("", "Persistent memory - never resets")
|
el_li("", "Persistent memory - never resets")
|
||||||
+ el_li("", "Local inference via Ollama (coming)")
|
+ el_li("", "Local inference via Ollama (coming)")
|
||||||
+ el_li("", "Bring your own API keys")
|
+ el_li("", "Bring your own API keys")
|
||||||
+ el_li("", "3 marketplace plugins included")
|
+ el_li("", "Plugin marketplace access (coming soon)")
|
||||||
+ el_li("", "Core built-in capabilities")
|
+ el_li("", "Core built-in capabilities")
|
||||||
+ el_li("", "2 devices included")
|
+ el_li("", "2 devices included")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
// components/hero.el - Hero section.
|
// components/hero.el - Hero section.
|
||||||
|
// Rebuilt: 2026-05-11.
|
||||||
//
|
//
|
||||||
// Full-bleed hero with headline, sub-copy, and two CTAs.
|
// Full-bleed hero with headline, sub-copy, and two CTAs.
|
||||||
// Glow orbs are pure CSS absolute-positioned divs.
|
// Glow orbs are pure CSS absolute-positioned divs.
|
||||||
@@ -31,7 +32,7 @@ fn hero() -> String {
|
|||||||
|
|
||||||
let sub: String = el_p(
|
let sub: String = el_p(
|
||||||
"class=\"hero-sub animate-up-3\"",
|
"class=\"hero-sub animate-up-3\"",
|
||||||
"Runs on your machine. Remembers everything. Priced below ChatGPT on day one."
|
"Runs on your machine. Remembers everything. The memory is yours — forever."
|
||||||
)
|
)
|
||||||
|
|
||||||
let cta_primary: String = el_a(
|
let cta_primary: String = el_a(
|
||||||
|
|||||||
+1
-1
@@ -77,7 +77,7 @@ fn marketplace_categories() -> String {
|
|||||||
|
|
||||||
el_div(
|
el_div(
|
||||||
"class=\"marketplace-categories reveal\" style=\"transition-delay:320ms\"",
|
"class=\"marketplace-categories reveal\" style=\"transition-delay:320ms\"",
|
||||||
marketplace_tags_block("Connectors - day one", connectors) +
|
marketplace_tags_block("Connectors (rolling out)", connectors) +
|
||||||
marketplace_tags_block("Following launch", following) +
|
marketplace_tags_block("Following launch", following) +
|
||||||
el_div(
|
el_div(
|
||||||
"",
|
"",
|
||||||
|
|||||||
+16
-2
@@ -36,7 +36,7 @@ fn pricing_free_features() -> String {
|
|||||||
el_li("", el_span("class=\"dash\"", "-") + el_span("", "Local inference via Ollama (coming)")) +
|
el_li("", el_span("class=\"dash\"", "-") + el_span("", "Local inference via Ollama (coming)")) +
|
||||||
el_li("", el_span("class=\"dash\"", "-") + el_span("", "Neuron Inference included when it launches - Q3 2026")) +
|
el_li("", el_span("class=\"dash\"", "-") + el_span("", "Neuron Inference included when it launches - Q3 2026")) +
|
||||||
el_li("", el_span("class=\"dash\"", "-") + el_span("", "Unlimited projects")) +
|
el_li("", el_span("class=\"dash\"", "-") + el_span("", "Unlimited projects")) +
|
||||||
el_li("", el_span("class=\"dash\"", "-") + el_span("", "3 marketplace plugins included")) +
|
el_li("", el_span("class=\"dash\"", "-") + el_span("", "Plugin marketplace access (coming soon)")) +
|
||||||
el_li("", el_span("class=\"dash\"", "-") + el_span("", "Core built-in capabilities"))
|
el_li("", el_span("class=\"dash\"", "-") + el_span("", "Core built-in capabilities"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +91,7 @@ fn pricing(sold: Int, total: Int) -> String {
|
|||||||
el_span("class=\"pricing-price\"", "$0") +
|
el_span("class=\"pricing-price\"", "$0") +
|
||||||
el_span("class=\"pricing-cadence\"", "forever")
|
el_span("class=\"pricing-cadence\"", "forever")
|
||||||
) +
|
) +
|
||||||
|
el_p("class=\"pricing-tagline\" style=\"margin-bottom:.5rem\"", "$0 + your AI provider's usage — free provider option available") +
|
||||||
el_p("class=\"pricing-tagline\"", "Start building your memory. No card required.") +
|
el_p("class=\"pricing-tagline\"", "Start building your memory. No card required.") +
|
||||||
el_ul("class=\"pricing-features\"", pricing_free_features()) +
|
el_ul("class=\"pricing-features\"", pricing_free_features()) +
|
||||||
el_div("style=\"flex:1\"", "") +
|
el_div("style=\"flex:1\"", "") +
|
||||||
@@ -108,6 +109,7 @@ fn pricing(sold: Int, total: Int) -> String {
|
|||||||
el_span("class=\"pricing-price\"", "$19") +
|
el_span("class=\"pricing-price\"", "$19") +
|
||||||
el_span("class=\"pricing-cadence\"", "/ month")
|
el_span("class=\"pricing-cadence\"", "/ month")
|
||||||
) +
|
) +
|
||||||
|
el_p("class=\"pricing-tagline\" style=\"margin-bottom:.5rem\"", "+ your AI provider's usage (typically $2–8/mo for everyday chat) · one bill once Neuron Inference launches") +
|
||||||
el_p("class=\"pricing-tagline\"", "Full access. Use your own API keys now. Neuron Inference when it launches.") +
|
el_p("class=\"pricing-tagline\"", "Full access. Use your own API keys now. Neuron Inference when it launches.") +
|
||||||
el_ul("class=\"pricing-features\"", pricing_pro_features()) +
|
el_ul("class=\"pricing-features\"", pricing_pro_features()) +
|
||||||
el_div("style=\"flex:1\"", "") +
|
el_div("style=\"flex:1\"", "") +
|
||||||
@@ -125,6 +127,7 @@ fn pricing(sold: Int, total: Int) -> String {
|
|||||||
el_span("class=\"pricing-price\"", "$199") +
|
el_span("class=\"pricing-price\"", "$199") +
|
||||||
el_span("class=\"pricing-cadence\"", "lifetime")
|
el_span("class=\"pricing-cadence\"", "lifetime")
|
||||||
) +
|
) +
|
||||||
|
el_p("class=\"pricing-tagline\" style=\"margin-bottom:.5rem\"", "+ your AI provider's usage until Neuron Inference launches — then it's included, forever") +
|
||||||
el_p("class=\"pricing-tagline\"", "Pay once. Everything, forever. Including Neuron Inference when it launches.") +
|
el_p("class=\"pricing-tagline\"", "Pay once. Everything, forever. Including Neuron Inference when it launches.") +
|
||||||
spots_html +
|
spots_html +
|
||||||
el_ul("class=\"pricing-features\"", pricing_founding_features()) +
|
el_ul("class=\"pricing-features\"", pricing_founding_features()) +
|
||||||
@@ -135,6 +138,17 @@ fn pricing(sold: Int, total: Int) -> String {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let need_box: String = el_div(
|
||||||
|
"class=\"founding-banner reveal\" style=\"margin:0 auto 2.5rem\"",
|
||||||
|
el_div(
|
||||||
|
"",
|
||||||
|
el_p("class=\"founding-banner-label\"", "What you'll need") +
|
||||||
|
el_p("class=\"pricing-tagline\" style=\"margin-bottom:0\"",
|
||||||
|
"Neuron is the mind — the AI model it thinks with comes from a provider you connect in about two minutes. Google Gemini has a free tier to start (note: Google uses free-tier chats to improve its products — their paid tier doesn't). Anthropic, OpenAI, and xAI bill you directly for what you use: everyday chat typically runs $2–8 a month, and budget models cost under $1. Neuron adds no markup and never touches the bill. When Neuron Inference launches, Neuron includes the AI — one bill, zero setup. Heavy agentic use costs more than everyday chat — you're always billed at your provider's own rates."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
let grid: String = el_div("class=\"pricing-grid\"", card_free + card_pro + card_founding)
|
let grid: String = el_div("class=\"pricing-grid\"", card_free + card_pro + card_founding)
|
||||||
|
|
||||||
let banner_fill: String = el_div(
|
let banner_fill: String = el_div(
|
||||||
@@ -165,6 +179,6 @@ fn pricing(sold: Int, total: Int) -> String {
|
|||||||
|
|
||||||
el_section(
|
el_section(
|
||||||
"id=\"pricing\" aria-label=\"Pricing\"",
|
"id=\"pricing\" aria-label=\"Pricing\"",
|
||||||
el_div("class=\"container\"", header + grid + banner + fine1 + fine2)
|
el_div("class=\"container\"", header + need_box + grid + banner + fine1 + fine2)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,11 +51,13 @@ test.describe('Security headers', () => {
|
|||||||
// - anything else (e.g. evil.com): BLOCKED (403)
|
// - anything else (e.g. evil.com): BLOCKED (403)
|
||||||
|
|
||||||
test.describe('CORS enforcement — /api/supabase-config', () => {
|
test.describe('CORS enforcement — /api/supabase-config', () => {
|
||||||
test('Rejects requests with no Origin header', async () => {
|
test('Allows requests with no Origin header (same-origin browser fetches)', async () => {
|
||||||
// No Origin = not from a browser context — the server treats this as
|
// Same-origin browser fetches (e.g. checkout page fetching supabase-config on
|
||||||
// an unknown caller and returns 403 to prevent server-side exfiltration.
|
// the same domain) do not send an Origin header. The server must pass these
|
||||||
|
// through — blocking them would break the checkout flow on production.
|
||||||
|
// Server-side exfiltration is prevented by the evil-origin 403 below.
|
||||||
const r = await get('/api/supabase-config');
|
const r = await get('/api/supabase-config');
|
||||||
expect(r.status).toBe(403);
|
expect(r.status).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Rejects evil origin', async () => {
|
test('Rejects evil origin', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user