Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c38a552266 | |||
| b87b0bed24 | |||
| ef352d2b8a | |||
| bb690c4690 | |||
| e148e6987d | |||
| 5812cb0452 | |||
| c99923da1b | |||
| 5885826316 | |||
| 9554430b7e | |||
| 4e35cbe841 | |||
| 62385b53c2 | |||
| 9685a42c7d | |||
| 952b03737b | |||
| d2628ec42e | |||
| cac986c5e1 | |||
| 9650dad951 | |||
| d598fb7b10 | |||
| 1eeb8df04b | |||
| c3aec8947a | |||
| 9e5d7e55ab | |||
| 5d3b1a3e20 | |||
| 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")
|
||||
echo "Changed files:"
|
||||
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
|
||||
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)
|
||||
# Asset-only: files that don't require rebuilding the El binary.
|
||||
# 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
|
||||
echo "asset_only=true" >> "$GITHUB_OUTPUT"
|
||||
echo "=> Asset-only change detected, will use fast path"
|
||||
|
||||
@@ -52,6 +52,8 @@ jobs:
|
||||
set -euo pipefail
|
||||
COMMIT_MSG=$(git log -1 --pretty=format:"%s" 2>/dev/null || true)
|
||||
echo "Merge commit: $COMMIT_MSG"
|
||||
# Fetch dev so ancestry check works in the shallow clone.
|
||||
git fetch --depth=1 origin dev 2>/dev/null || true
|
||||
# Gitea merge commits: "Merge pull request '...' (#N) from dev into stage"
|
||||
# Direct branch merges: "Merge branch 'dev' into stage"
|
||||
# tea pr merge with custom title: any subject line is possible, so
|
||||
@@ -79,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")
|
||||
echo "Changed files:"
|
||||
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
|
||||
echo "asset_only=true" >> "$GITHUB_OUTPUT"
|
||||
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("", "Local inference via Ollama (coming)")
|
||||
+ 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("", "2 devices included")
|
||||
} else {
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
// components/hero.el - Hero section.
|
||||
// Rebuilt: 2026-05-11.
|
||||
//
|
||||
// Full-bleed hero with headline, sub-copy, and two CTAs.
|
||||
// Glow orbs are pure CSS absolute-positioned divs.
|
||||
@@ -31,7 +32,7 @@ fn hero() -> String {
|
||||
|
||||
let sub: String = el_p(
|
||||
"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(
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ fn marketplace_categories() -> String {
|
||||
|
||||
el_div(
|
||||
"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) +
|
||||
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("", "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("", "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"))
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ fn pricing(sold: Int, total: Int) -> String {
|
||||
el_span("class=\"pricing-price\"", "$0") +
|
||||
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_ul("class=\"pricing-features\"", pricing_free_features()) +
|
||||
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-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_ul("class=\"pricing-features\"", pricing_pro_features()) +
|
||||
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-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.") +
|
||||
spots_html +
|
||||
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 banner_fill: String = el_div(
|
||||
@@ -165,6 +179,6 @@ fn pricing(sold: Int, total: Int) -> String {
|
||||
|
||||
el_section(
|
||||
"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)
|
||||
|
||||
test.describe('CORS enforcement — /api/supabase-config', () => {
|
||||
test('Rejects requests with no Origin header', async () => {
|
||||
// No Origin = not from a browser context — the server treats this as
|
||||
// an unknown caller and returns 403 to prevent server-side exfiltration.
|
||||
test('Allows requests with no Origin header (same-origin browser fetches)', async () => {
|
||||
// Same-origin browser fetches (e.g. checkout page fetching supabase-config on
|
||||
// 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');
|
||||
expect(r.status).toBe(403);
|
||||
expect(r.status).toBe(200);
|
||||
});
|
||||
|
||||
test('Rejects evil origin', async () => {
|
||||
|
||||
Reference in New Issue
Block a user